MidiBuffer MPEMessages::setZoneLayout (const MPEZoneLayout& layout) { MidiBuffer buffer; buffer.addEvents (clearAllZones(), 0, -1, 0); for (int i = 0; i < layout.getNumZones(); ++i) buffer.addEvents (addZone (*layout.getZoneByIndex (i)), 0, -1, 0); return buffer; }
MidiBuffer MPEMessages::addZone (MPEZone zone) { MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(), zoneLayoutMessagesRpnNumber, zone.getNumNoteChannels(), false, false)); buffer.addEvents (perNotePitchbendRange (zone), 0, -1, 0); buffer.addEvents (masterPitchbendRange (zone), 0, -1, 0); return buffer; }
void DemoJuceFilter::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { if (isSyncedToHost) { AudioPlayHead::CurrentPositionInfo pos; if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (pos)) { if (memcmp (&pos, &lastPosInfo, sizeof (pos)) != 0) { lastPosInfo = pos; const int ppqPerBar = (pos.timeSigNumerator * 4 / pos.timeSigDenominator); const double beats = (fmod (pos.ppqPosition, ppqPerBar) / ppqPerBar) * pos.timeSigNumerator; const double position = beats*4; const int beat = (int)position; currentBpm = (int)pos.bpm; if (_p != beat) { for (int x=0; x<64; x++) { if (activePatterns[x]) { patterns[x]->forward(beat+1); } } currentBeat = currentPatternPtr->getCurrentPosition(); if (currentBeat > 16) currentBeat = currentBeat - 16; /* process midi events to their devices */ midiMessages.addEvents (midiManager.getVstMidiEvents(),0,-1,0); /* clean the buffers */ midiManager.clear(); sendChangeMessage (this); } _p = beat; } } else { zeromem (&lastPosInfo, sizeof (lastPosInfo)); lastPosInfo.timeSigNumerator = 4; lastPosInfo.timeSigDenominator = 4; lastPosInfo.bpm = 120; } } }
void perform (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead) { auto numSamples = buffer.getNumSamples(); auto maxSamples = renderingBuffer.getNumSamples(); if (numSamples > maxSamples) { // being asked to render more samples than our buffers have, so slice things up... tempMIDI.clear(); tempMIDI.addEvents (midiMessages, maxSamples, numSamples, -maxSamples); { AudioBuffer<FloatType> startAudio (buffer.getArrayOfWritePointers(), buffer.getNumChannels(), maxSamples); midiMessages.clear (maxSamples, numSamples); perform (startAudio, midiMessages, audioPlayHead); } AudioBuffer<FloatType> endAudio (buffer.getArrayOfWritePointers(), buffer.getNumChannels(), maxSamples, numSamples - maxSamples); perform (endAudio, tempMIDI, audioPlayHead); return; } currentAudioInputBuffer = &buffer; currentAudioOutputBuffer.setSize (jmax (1, buffer.getNumChannels()), numSamples); currentAudioOutputBuffer.clear(); currentMidiInputBuffer = &midiMessages; currentMidiOutputBuffer.clear(); { const Context context { renderingBuffer.getArrayOfWritePointers(), midiBuffers.begin(), audioPlayHead, numSamples }; for (auto* op : renderOps) op->perform (context); } for (int i = 0; i < buffer.getNumChannels(); ++i) buffer.copyFrom (i, 0, currentAudioOutputBuffer, i, 0, numSamples); midiMessages.clear(); midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); currentAudioInputBuffer = nullptr; }
void runTest() override { beginTest ("initialisation"); { MPEZoneLayout layout; expectEquals (layout.getNumZones(), 0); } beginTest ("adding zones"); { MPEZoneLayout layout; expect (layout.addZone (MPEZone (1, 7))); expectEquals (layout.getNumZones(), 1); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); expect (layout.addZone (MPEZone (9, 7))); expectEquals (layout.getNumZones(), 2); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); expect (! layout.addZone (MPEZone (5, 3))); expectEquals (layout.getNumZones(), 3); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); expectEquals (layout.getZoneByIndex (2)->getMasterChannel(), 5); expectEquals (layout.getZoneByIndex (2)->getNumNoteChannels(), 3); expect (! layout.addZone (MPEZone (5, 4))); expectEquals (layout.getNumZones(), 2); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 5); expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 4); expect (! layout.addZone (MPEZone (6, 4))); expectEquals (layout.getNumZones(), 2); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 6); expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 4); } beginTest ("querying zones"); { MPEZoneLayout layout; layout.addZone (MPEZone (2, 5)); layout.addZone (MPEZone (9, 4)); expect (layout.getZoneByMasterChannel (1) == nullptr); expect (layout.getZoneByMasterChannel (2) != nullptr); expect (layout.getZoneByMasterChannel (3) == nullptr); expect (layout.getZoneByMasterChannel (8) == nullptr); expect (layout.getZoneByMasterChannel (9) != nullptr); expect (layout.getZoneByMasterChannel (10) == nullptr); expectEquals (layout.getZoneByMasterChannel (2)->getNumNoteChannels(), 5); expectEquals (layout.getZoneByMasterChannel (9)->getNumNoteChannels(), 4); expect (layout.getZoneByFirstNoteChannel (2) == nullptr); expect (layout.getZoneByFirstNoteChannel (3) != nullptr); expect (layout.getZoneByFirstNoteChannel (4) == nullptr); expect (layout.getZoneByFirstNoteChannel (9) == nullptr); expect (layout.getZoneByFirstNoteChannel (10) != nullptr); expect (layout.getZoneByFirstNoteChannel (11) == nullptr); expectEquals (layout.getZoneByFirstNoteChannel (3)->getNumNoteChannels(), 5); expectEquals (layout.getZoneByFirstNoteChannel (10)->getNumNoteChannels(), 4); expect (layout.getZoneByNoteChannel (2) == nullptr); expect (layout.getZoneByNoteChannel (3) != nullptr); expect (layout.getZoneByNoteChannel (4) != nullptr); expect (layout.getZoneByNoteChannel (6) != nullptr); expect (layout.getZoneByNoteChannel (7) != nullptr); expect (layout.getZoneByNoteChannel (8) == nullptr); expect (layout.getZoneByNoteChannel (9) == nullptr); expect (layout.getZoneByNoteChannel (10) != nullptr); expect (layout.getZoneByNoteChannel (11) != nullptr); expect (layout.getZoneByNoteChannel (12) != nullptr); expect (layout.getZoneByNoteChannel (13) != nullptr); expect (layout.getZoneByNoteChannel (14) == nullptr); expectEquals (layout.getZoneByNoteChannel (5)->getNumNoteChannels(), 5); expectEquals (layout.getZoneByNoteChannel (13)->getNumNoteChannels(), 4); } beginTest ("clear all zones"); { MPEZoneLayout layout; expect (layout.addZone (MPEZone (1, 7))); expect (layout.addZone (MPEZone (10, 2))); layout.clearAllZones(); expectEquals (layout.getNumZones(), 0); } beginTest ("process MIDI buffers"); { MPEZoneLayout layout; MidiBuffer buffer; buffer = MPEMessages::addZone (MPEZone (1, 7)); layout.processNextMidiBuffer (buffer); expectEquals (layout.getNumZones(), 1); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); buffer = MPEMessages::addZone (MPEZone (9, 7)); layout.processNextMidiBuffer (buffer); expectEquals (layout.getNumZones(), 2); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7); expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9); expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7); MPEZone zone (1, 10); buffer = MPEMessages::addZone (zone); layout.processNextMidiBuffer (buffer); expectEquals (layout.getNumZones(), 1); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 10); zone.setPerNotePitchbendRange (33); zone.setMasterPitchbendRange (44); buffer = MPEMessages::masterPitchbendRange (zone); buffer.addEvents (MPEMessages::perNotePitchbendRange (zone), 0, -1, 0); layout.processNextMidiBuffer (buffer); expectEquals (layout.getZoneByIndex (0)->getPerNotePitchbendRange(), 33); expectEquals (layout.getZoneByIndex (0)->getMasterPitchbendRange(), 44); } beginTest ("process individual MIDI messages"); { MPEZoneLayout layout; layout.processNextMidiEvent (MidiMessage (0x80, 0x59, 0xd0)); // unrelated note-off msg layout.processNextMidiEvent (MidiMessage (0xb1, 0x64, 0x06)); // RPN part 1 layout.processNextMidiEvent (MidiMessage (0xb1, 0x65, 0x00)); // RPN part 2 layout.processNextMidiEvent (MidiMessage (0xb8, 0x0b, 0x66)); // unrelated CC msg layout.processNextMidiEvent (MidiMessage (0xb1, 0x06, 0x03)); // RPN part 3 layout.processNextMidiEvent (MidiMessage (0x90, 0x60, 0x00)); // unrelated note-on msg expectEquals (layout.getNumZones(), 1); expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1); expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3); } }
void PatternRecording::recordPattern(MidiBuffer &midiMessages, const int &numSamples) { // if we are recording the pattern, just rip the // MIDI messages from the incoming buffer if (isPatternRecording) { // get the iterator for the incoming buffer // so we can check through the messages MidiBuffer::Iterator i(midiMessages); MidiMessage message (0xf4, 0.0); int time; // if we are still during the precount, do nothing if (patternPrecountPosition > numSamples) patternPrecountPosition -= numSamples; // if the precount finishes during this buffer else if (patternPrecountPosition > 0) { // TODO: overdub could be option here? midiPattern.clear(); const int numSamplesToAdd = numSamples - patternPrecountPosition; // DBG("#1 recording " << numSamplesToAdd << " of midi, pos: " << patternPrecountPosition); // get messages from the main MIDI message queue while(i.getNextEvent(message, time)) { // and add them if they occur after the precount runs out if (time > patternPrecountPosition) { midiPattern.addEvent(message, time - patternPrecountPosition); // store noteOffs to fire at end if (message.isNoteOn()) { MidiMessage tempNoteOff = MidiMessage::noteOff(message.getChannel(), message.getNoteNumber(), message.getVelocity()); noteOffs.addEvent(tempNoteOff, 0); } } } // the precount has finished patternPrecountPosition = 0; // we are now numSamplesToAdd into the buffer patternPosition = numSamplesToAdd; } else { // if we are during recording (and not near the end), just // add the current input into the record buffer if (numSamples + patternPosition < patternLengthInSamples) { // DBG("#2 recording " << numSamples << " of midi, pos: " << patternPosition); // get messages from the main MIDI message queue while(i.getNextEvent(message, time)) { midiPattern.addEvent(message, time + patternPosition); // store noteOffs to fire at end if (message.isNoteOn()) { MidiMessage tempNoteOff = MidiMessage::noteOff(message.getChannel(), message.getNoteNumber(), message.getVelocity()); noteOffs.addEvent(tempNoteOff, 0); } } //midiPattern.addEvents(midiMessages, 0, numSamples, -patternPosition); patternPosition += numSamples; } // otherwise we are finishing up else { const int numSamplesLeftToRecord = patternLengthInSamples - patternPosition; // add remaining messages from the main MIDI message queue while(i.getNextEvent(message, time)) { if (time < numSamplesLeftToRecord) midiPattern.addEvent(message, time + patternPosition); // store noteOffs to fire at end if (message.isNoteOn()) { MidiMessage tempNoteOff = MidiMessage::noteOff(message.getChannel(), message.getNoteNumber(), message.getVelocity()); noteOffs.addEvent(tempNoteOff, 0); } } // add the note offs to clear any "phantom notes" midiPattern.addEvents(noteOffs, 0, 1, patternLengthInSamples - 1); // we are no longer recording isPatternRecording = false; // if we finish recording, let any listeners know // so they can redraw representations of the pattern sendChangeMessage(); DBG("pattern " << patternBank << " finished recording."); // start playing back from the start straight away isPatternStopping = false; isPatternPlaying = true; patternPosition = 0; // fill the remaining buffer with the newly recorded sequence const int samplesRemaining = numSamples - numSamplesLeftToRecord; if (patternPosition + samplesRemaining < patternLengthInSamples) { midiMessages.addEvents(midiPattern, patternPosition, samplesRemaining, numSamplesLeftToRecord); patternPosition += samplesRemaining; } } // Let the PatternStripControl know to recache pattern sendChangeMessage(); } } }
void PatternRecording::playPattern(MidiBuffer &midiMessages, const int &numSamples) { // if the pattern is playing, add its events to the // Midi message queue if (isPatternPlaying) { // if we are stopping if (isPatternStopping) { // add the note off events to the end off the buffer // to avoid any hanging notes midiMessages.addEvents(noteOffs, 0, 1, numSamples - 1); isPatternPlaying = false; isPatternStopping = false; // we are finished here return; } // calculate the correction to timesteps relative // to the current MIDI buffer const int correction = -patternPosition; if (patternPosition + numSamples < patternLengthInSamples) { midiMessages.addEvents(midiPattern, patternPosition, numSamples, correction); patternPosition += numSamples; } else { // see how many samples of the pattern we have left to play back const int numToAdd = patternLengthInSamples - patternPosition; // and add them to the main MIDI buffer midiMessages.addEvents(midiPattern, patternPosition, numToAdd, correction); // reset position patternPosition = 0; // if we are looping the pattern if (doesPatternLoop) { isPatternStopping = false; isPatternPlaying = true; // go back to the start of the pattern and add these samples // to what remains of the main MIDI buffer const int samplesRemainingInBuffer = numSamples - numToAdd; // remembering that these are offset so as to start where // the previously playing pattern finished const int offset = numToAdd; midiMessages.addEvents(midiPattern, patternPosition, samplesRemainingInBuffer, offset); patternPosition += samplesRemainingInBuffer; } else { isPatternPlaying = false; } } } }
void MidiControllerAutomationHandler::handleParameterData(MidiBuffer &b) { const bool bufferEmpty = b.isEmpty(); const bool noCCsUsed = !anyUsed && !unlearnedData.used; if (bufferEmpty || noCCsUsed) return; tempBuffer.clear(); MidiBuffer::Iterator mb(b); MidiMessage m; int samplePos; while (mb.getNextEvent(m, samplePos)) { bool consumed = false; if (m.isController()) { const int number = m.getControllerNumber(); if (isLearningActive()) { setUnlearndedMidiControlNumber(number, sendNotification); } for (auto& a : automationData[number]) { if (a.used) { jassert(a.processor.get() != nullptr); auto normalizedValue = (double)m.getControllerValue() / 127.0; if (a.inverted) normalizedValue = 1.0 - normalizedValue; const double value = a.parameterRange.convertFrom0to1(normalizedValue); const float snappedValue = (float)a.parameterRange.snapToLegalValue(value); if (a.macroIndex != -1) { a.processor->getMainController()->getMacroManager().getMacroChain()->setMacroControl(a.macroIndex, (float)m.getControllerValue(), sendNotification); } else { if (a.lastValue != snappedValue) { a.processor->setAttribute(a.attribute, snappedValue, sendNotification); a.lastValue = snappedValue; } } consumed = true; } } } if (!consumed) tempBuffer.addEvent(m, samplePos); } b.clear(); b.addEvents(tempBuffer, 0, -1, 0); }
void MidiOutFilter::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { for (int i = 0; i < getNumOutputChannels(); ++i) { buffer.clear (i, 0, buffer.getNumSamples()); } const double SR=getSampleRate(); const double iSR=1.0/SR; AudioPlayHead::CurrentPositionInfo pos; if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (pos)) { if (memcmp (&pos, &lastPosInfo, sizeof (pos)) != 0) { if(param[kMTC]>=0.5f) { double frameRate=24.0; int mtcFrameRate=0; const double samplesPerPpq=60.0*SR/pos.bpm; const double samplesPerClock = SR/(4.0*frameRate); const long double seconds = (long double)(pos.ppqPosition*60.0f/pos.bpm) /*+ smpteOffset*/; const long double absSecs = fabs (seconds); const bool neg = seconds < 0.0; int hours, mins, secs, frames; if (frameRate==29.97) { int64 frameNumber = int64(absSecs*29.97); frameNumber += 18*(frameNumber/17982) + 2*(((frameNumber%17982) - 2) / 1798); hours = int((((frameNumber / 30) / 60) / 60) % 24); mins = int(((frameNumber / 30) / 60) % 60); secs = int((frameNumber / 30) % 60); frames = int(frameNumber % 30); } else { hours = (int) (absSecs / (60.0 * 60.0)); mins = ((int) (absSecs / 60.0)) % 60; secs = ((int) absSecs) % 60; frames = (int)(int64(absSecs*frameRate) % (int)frameRate); } if (pos.isPlaying) { double i=0.0; const double clockppq = fmod(absSecs*frameRate*4.0,(long double)1.0); samplesToNextMTC = (int)(samplesPerClock * (clockppq+i)); i+=1.0; if (!wasPlaying) { //this is so the song position pointer will be sent before any //other data at the beginning of the song MidiBuffer temp = midiMessages; midiMessages.clear(); if (samplesToNextMTC<buffer.getNumSamples()) { int mtcData; switch (mtcNumber) { case 0: mtcData=frames&0x0f; break; case 1: mtcData=(frames&0xf0)>>4; break; case 2: mtcData=secs&0x0f; break; case 3: mtcData=(secs&0xf0)>>4; break; case 4: mtcData=mins&0x0f; break; case 5: mtcData=(mins&0xf0)>>4; break; case 6: mtcData=hours&0x0f; break; case 7: mtcData=(hours&0x10)>>4 | mtcFrameRate; break; } MidiMessage midiclock(0xf1,(mtcNumber<<4)|(mtcData)); ++mtcNumber; mtcNumber&=0x07; midiMessages.addEvent(midiclock,samplesToNextMTC); samplesToNextMTC = (int)(samplesPerClock * (clockppq+i)); i+=1.0; startMTCAt=-999.0; sendmtc=true; } midiMessages.addEvents(temp,0,buffer.getNumSamples(),0); } if (startMTCAt >-999.0 && (int)(samplesPerPpq*(startMTCAt-pos.ppqPosition))<buffer.getNumSamples()) { samplesToNextMTC = (int)(samplesPerPpq*(startMTCAt-pos.ppqPosition)); int mtcData; switch (mtcNumber) { case 0: mtcData=frames&0x0f; break; case 1: mtcData=(frames&0xf0)>>4; break; case 2: mtcData=secs&0x0f; break; case 3: mtcData=(secs&0xf0)>>4; break; case 4: mtcData=mins&0x0f; break; case 5: mtcData=(mins&0xf0)>>4; break; case 6: mtcData=hours&0x0f; break; case 7: mtcData=(hours&0x10)>>4 | mtcFrameRate; break; } MidiMessage midiclock(0xf1,(mtcNumber<<4)|(mtcData)); ++mtcNumber; mtcNumber&=0x07; midiMessages.addEvent(midiclock,samplesToNextMTC); samplesToNextMTC = (int)(samplesPerClock * (clockppq+i)); i+=1.0; startMTCAt=-999.0; sendmtc=true; }