forked from christofmuc/juce-utils
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding in MidiHelpers and Sysex classes taken from the Rev2SequencerT…
…ool project.
- Loading branch information
1 parent
1bdfac1
commit 83d9693
Showing
5 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}; | ||
|