diff --git a/modules/juce_audio_basics/midi/juce_MidiMessage.cpp b/modules/juce_audio_basics/midi/juce_MidiMessage.cpp index 4ec9f61164..36cbd03448 100644 --- a/modules/juce_audio_basics/midi/juce_MidiMessage.cpp +++ b/modules/juce_audio_basics/midi/juce_MidiMessage.cpp @@ -93,7 +93,7 @@ int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) noexcept 1, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; - return messageLengths [firstByte & 0x7f]; + return messageLengths[firstByte & 0x7f]; } //============================================================================== @@ -344,6 +344,11 @@ String MidiMessage::getDescription() const return String::toHexString (getRawData(), getRawDataSize()); } +MidiMessage MidiMessage::withTimeStamp (double newTimestamp) const +{ + return { *this, newTimestamp }; +} + int MidiMessage::getChannel() const noexcept { auto data = getRawData(); @@ -964,7 +969,7 @@ bool MidiMessage::isMidiMachineControlGoto (int& hours, int& minutes, int& secon hours = data[7] % 24; // (that some machines send out hours > 24) minutes = data[8]; seconds = data[9]; - frames = data[10]; + frames = data[10]; return true; } @@ -985,8 +990,8 @@ String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctav if (isPositiveAndBelow (note, 128)) { - String s (useSharps ? sharpNoteNames [note % 12] - : flatNoteNames [note % 12]); + String s (useSharps ? sharpNoteNames[note % 12] + : flatNoteNames [note % 12]); if (includeOctaveNumber) s << (note / 12 + (octaveNumForMiddleC - 5)); @@ -1079,7 +1084,7 @@ const char* MidiMessage::getRhythmInstrumentName (const int n) NEEDS_TRANS("Open Cuica"), NEEDS_TRANS("Mute Triangle"), NEEDS_TRANS("Open Triangle") }; - return (n >= 35 && n <= 81) ? names [n - 35] : nullptr; + return (n >= 35 && n <= 81) ? names[n - 35] : nullptr; } const char* MidiMessage::getControllerName (const int n) diff --git a/modules/juce_audio_basics/midi/juce_MidiMessage.h b/modules/juce_audio_basics/midi/juce_MidiMessage.h index 7fcefe2a2e..867831f5e0 100644 --- a/modules/juce_audio_basics/midi/juce_MidiMessage.h +++ b/modules/juce_audio_basics/midi/juce_MidiMessage.h @@ -171,6 +171,11 @@ public: */ void addToTimeStamp (double delta) noexcept { timeStamp += delta; } + /** Return a copy of this message with a new timestamp. + The units for the timestamp will be application-specific - see the notes for getTimeStamp(). + */ + MidiMessage withTimeStamp (double newTimestamp) const; + //============================================================================== /** Returns the midi channel associated with the message. diff --git a/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp b/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp index 4c8711b693..32b4dd4027 100644 --- a/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp +++ b/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp @@ -107,7 +107,7 @@ int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcep int MidiMessageSequence::getNextIndexAtTime (double timeStamp) const noexcept { - const int numEvents = list.size(); + auto numEvents = list.size(); int i; for (i = 0; i < numEvents; ++i) @@ -338,4 +338,62 @@ void MidiMessageSequence::createControllerUpdatesForTime (int channelNumber, dou } } +#if JUCE_UNIT_TESTS + +struct MidiMessageSequenceTest : public juce::UnitTest +{ + MidiMessageSequenceTest() : juce::UnitTest ("MidiMessageSequence") {} + + void runTest() override + { + MidiMessageSequence s; + + s.addEvent (MidiMessage::noteOn (1, 60, 0.5f).withTimeStamp (0.0)); + s.addEvent (MidiMessage::noteOff (1, 60, 0.5f).withTimeStamp (4.0)); + s.addEvent (MidiMessage::noteOn (1, 30, 0.5f).withTimeStamp (2.0)); + s.addEvent (MidiMessage::noteOff (1, 30, 0.5f).withTimeStamp (8.0)); + + beginTest ("Start & end time"); + expectEquals (s.getStartTime(), 0.0); + expectEquals (s.getEndTime(), 8.0); + expectEquals (s.getEventTime (1), 2.0); + + beginTest ("Matching note off & ons"); + s.updateMatchedPairs(); + expectEquals (s.getTimeOfMatchingKeyUp (0), 4.0); + expectEquals (s.getTimeOfMatchingKeyUp (1), 8.0); + expectEquals (s.getIndexOfMatchingKeyUp (0), 2); + expectEquals (s.getIndexOfMatchingKeyUp (1), 3); + + beginTest ("Time & indeces"); + expectEquals (s.getNextIndexAtTime (0.5), 1); + expectEquals (s.getNextIndexAtTime (2.5), 2); + expectEquals (s.getNextIndexAtTime (9.0), 4); + + beginTest ("Deleting events"); + s.deleteEvent (0, true); + expectEquals (s.getNumEvents(), 2); + + beginTest ("Merging sequences"); + MidiMessageSequence s2; + s2.addEvent (MidiMessage::noteOn (2, 25, 0.5f).withTimeStamp (0.0)); + s2.addEvent (MidiMessage::noteOn (2, 40, 0.5f).withTimeStamp (1.0)); + s2.addEvent (MidiMessage::noteOff (2, 40, 0.5f).withTimeStamp (5.0)); + s2.addEvent (MidiMessage::noteOn (2, 80, 0.5f).withTimeStamp (3.0)); + s2.addEvent (MidiMessage::noteOff (2, 80, 0.5f).withTimeStamp (7.0)); + s2.addEvent (MidiMessage::noteOff (2, 25, 0.5f).withTimeStamp (9.0)); + + s.addSequence (s2, 0.0, 0.0, 8.0); // Intentionally cut off the last note off + s.updateMatchedPairs(); + + expectEquals (s.getNumEvents(), 7); + expectEquals (s.getIndexOfMatchingKeyUp (0), -1); // Truncated note, should be no note off + expectEquals (s.getTimeOfMatchingKeyUp (1), 5.0); + } +}; + +static MidiMessageSequenceTest midiMessageSequenceTests; + +#endif + } // namespace juce