Skip to content

Commit

Permalink
Adding in MidiHelpers and Sysex classes taken from the Rev2SequencerT…
Browse files Browse the repository at this point in the history
…ool project.
  • Loading branch information
christofmuc committed Dec 1, 2019
1 parent 1bdfac1 commit 83d9693
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ set(Sources
Data.h
MidiClocker.cpp
MidiClocker.h
MidiHelpers.cpp
MidiHelpers.h
MidiNote.cpp
MidiNote.h
MidiRecorder.cpp
Expand All @@ -33,6 +35,8 @@ set(Sources
Settings.h
StreamLogger.cpp
StreamLogger.h
Sysex.cpp
Sysex.h
README.md
LICENSE.md
)
Expand Down
83 changes: 83 additions & 0 deletions MidiHelpers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "MidiHelpers.h"

juce::MidiMessage MidiHelpers::sysexMessage(std::vector<uint8> const &sysEx)
{
return MidiMessage::createSysExMessage(sysEx.data(), (int)sysEx.size());
}

juce::MidiBuffer MidiHelpers::bufferFromMessages(std::vector<MidiMessage> const &messages)
{
// We assume the same timestamp for all messages in this little helper
MidiBuffer buffer;
int num = 0;
for (auto message : messages) {
buffer.addEvent(message, num++);
}
return buffer;
}

std::vector<MidiMessage> MidiHelpers::generateRPN(int midiChannel,
int parameterNumber,
int value,
bool isNRPN,
bool use14BitValue,
bool MSBbeforeLSB)
{
jassert(midiChannel > 0 && midiChannel <= 16);
jassert(parameterNumber >= 0 && parameterNumber < 16384);
jassert(value >= 0 && value < (use14BitValue ? 16384 : 128));

uint8 parameterLSB = uint8(parameterNumber & 0x7f);
uint8 parameterMSB = uint8(parameterNumber >> 7);

uint8 valueLSB = use14BitValue ? uint8(value & 0x7f) : 0x00;
uint8 valueMSB = use14BitValue ? uint8(value >> 7) : uint8(value);

uint8 channelByte = uint8(0xb0 + midiChannel - 1);

std::vector<MidiMessage> buffer;

if (MSBbeforeLSB) {
buffer.push_back(MidiMessage(channelByte, isNRPN ? 0x63 : 0x65, parameterMSB));
buffer.push_back(MidiMessage(channelByte, isNRPN ? 0x62 : 0x64, parameterLSB));
buffer.push_back(MidiMessage(channelByte, 0x06, valueMSB));
if (use14BitValue)
buffer.push_back(MidiMessage(channelByte, 0x26, valueLSB));
}
else {
buffer.push_back(MidiMessage(channelByte, isNRPN ? 0x62 : 0x64, parameterLSB));
buffer.push_back(MidiMessage(channelByte, isNRPN ? 0x63 : 0x65, parameterMSB));
// sending the value LSB is optional, but must come before sending the value MSB:
if (use14BitValue)
buffer.push_back(MidiMessage(channelByte, 0x26, valueLSB));
buffer.push_back(MidiMessage(channelByte, 0x06, valueMSB));
}

return buffer;
}

bool MidiHelpers::equalSysexMessageContent(MidiMessage const &message1, MidiMessage const &message2, int digitsToCompare /* = -1 */)
{
if (!(message1.isSysEx() & message2.isSysEx())) return false;
if (digitsToCompare == -1) {
if (message1.getSysExDataSize() != message2.getSysExDataSize()) return false;
// The complete message should be compared
digitsToCompare = message1.getSysExDataSize();
}
else {
if (message1.getSysExDataSize() < digitsToCompare || message2.getSysExDataSize() < digitsToCompare) return false;
}
for (int i = 0; i < message1.getSysExDataSize() && i < digitsToCompare; i++) {
if (message1.getSysExData()[i] != message2.getSysExData()[i]) {
return false;
}
}
return true;
}

juce::uint8 MidiHelpers::checksum7bit(std::vector<uint8> const &data)
{
int sum = 0;
std::for_each(data.begin(), data.end(), [&](uint8 byte) { sum += byte; });
return sum & 0x7f;
}
13 changes: 13 additions & 0 deletions MidiHelpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

class MidiHelpers {
public:
static MidiMessage sysexMessage(std::vector<uint8> const &data);
static MidiBuffer bufferFromMessages(std::vector<MidiMessage> const &messages);
static std::vector<MidiMessage> generateRPN(int midiChannel, int parameterNumber, int value, bool isNRPN, bool use14BitValue, bool MSBbeforeLSB);
static bool equalSysexMessageContent(MidiMessage const &message1, MidiMessage const &message2, int digitsToCompare = -1);
static uint8 checksum7bit(std::vector<uint8> const &data);
};

106 changes: 106 additions & 0 deletions Sysex.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#include "Sysex.h"

#include "Logger.h"

#include <boost/format.hpp>

std::vector<MidiMessage> Sysex::loadSysex(std::string const &filename)
{
std::vector<MidiMessage> messages;

File sysexFile = File::createFileWithoutCheckingPath(filename);
if (sysexFile.existsAsFile()) {
// This could be a ZIP file?
if (sysexFile.getFileExtension().toLowerCase() == ".zip") {
ZipFile zip(sysexFile);
for (int i = 0; i < zip.getNumEntries(); i++) {
auto entry = zip.getEntry(i);
File zipEntry = File::createFileWithoutCheckingPath(entry->filename);
if (zipEntry.getFileExtension().toLowerCase() == ".mid" || zipEntry.getFileExtension().toLowerCase() == ".syx") {
// That's an interesting entry
SimpleLogger::instance()->postMessage((boost::format("Opening %s") % entry->filename).str());
auto zipStream = zip.createStreamForEntry(i);
if (zipStream) {
auto newMessages = loadSysex(*zipStream);
std::copy(newMessages.cbegin(), newMessages.cend(), std::back_inserter(messages));
delete zipStream;
}
}
}
}
else {
// Single file
FileInputStream inputStream(sysexFile);
return loadSysex(inputStream);
}
}

return messages;
}

std::vector<juce::MidiMessage> Sysex::loadSysex(InputStream &inputStream)
{
std::vector<MidiMessage> messages;

// It could be SMF (Standard Midi File). Then there is a bit more structure we need to parse
MidiFile smfFile;
if (smfFile.readFrom(inputStream, false)) {
// Loop over all tracks
for (int track = 0; track < smfFile.getNumTracks(); track++) {
auto t = smfFile.getTrack(track);
MidiMessageSequence::MidiEventHolder* const* currentMsg;
for (currentMsg = t->begin(); currentMsg != t->end(); currentMsg++) {
// If this is a sysex message, keep it
if ((*currentMsg)->message.isSysEx()) {
messages.push_back((*currentMsg)->message);
}
}
}
}
else {
// More likely, this is a pure sysex file where we read the raw messages from a binary stream
inputStream.setPosition(0);
std::vector<uint8> data(inputStream.getTotalLength());
inputStream.read(&data[0], (int)inputStream.getTotalLength()); // 4 GB Limit

// Now produce the Sysex messages from the file
int inPointer = 0;
uint8 lastStatusByte = 0xf0; // Sysex message
while (inPointer < data.size()) {
int bytesUsed = 0;
//TODO - this crashes in case the data is not well formed sysex (example: Depeche Mode TI sound set, there is a message which ends on 0xff 0xff).
messages.push_back(MidiMessage(&data[inPointer], (int)(data.size()) - inPointer, bytesUsed, lastStatusByte, 0.0, false));
inPointer += bytesUsed;
}
}
return messages;
}

void Sysex::saveSysex(std::string const &filename, std::vector<juce::MidiMessage> const &messages) {
File sysExFile(filename);
if (sysExFile.existsAsFile()) {
sysExFile.deleteFile();
}

File sysexFile = File::createFileWithoutCheckingPath(filename);

FileOutputStream outputStream(sysexFile);
if (sysexFile.existsAsFile()) {
for (auto message : messages) {
outputStream.write(message.getRawData(), message.getRawDataSize());
}
}
}

std::string Sysex::saveSysexIntoNewFile(std::string const &directory, std::string const &desiredFileName, std::vector<juce::MidiMessage> const &messages)
{
File dir(directory);
if (dir.isDirectory()) {
File newFile = dir.getNonexistentChildFile(desiredFileName, ".syx", false);
saveSysex(newFile.getFullPathName().toStdString(), messages);
return newFile.getFullPathName().toStdString();
}
jassert(false);
return "Failure";
}

13 changes: 13 additions & 0 deletions Sysex.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include "JuceHeader.h"


class Sysex {
public:
static std::vector<MidiMessage> loadSysex(std::string const &filename);
static std::vector<MidiMessage> loadSysex(InputStream &inputStream);
static void saveSysex(std::string const &filename, std::vector<juce::MidiMessage> const &messages);
static std::string saveSysexIntoNewFile(std::string const &directory, std::string const &desiredFileName, std::vector<juce::MidiMessage> const &messages);
};

0 comments on commit 83d9693

Please sign in to comment.