--- linuxsampler/trunk/src/engines/EngineBase.h 2016/12/15 12:47:45 3054 +++ linuxsampler/trunk/src/engines/EngineBase.h 2020/01/04 12:09:45 3697 @@ -5,7 +5,7 @@ * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * * Copyright (C) 2005-2008 Christian Schoenebeck * * Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev * - * Copyright (C) 2012-2016 Christian Schoenebeck and Andreas Persson * + * Copyright (C) 2012-2017 Christian Schoenebeck and Andreas Persson * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -202,6 +202,23 @@ PostProcess(engineChannels[i]); } + // Just for debugging: dump the amount of free Note objects to + // the terminal (note due to the static variables being used, + // this is currently just intended for debugging with only one + // engine channel). + #if (CONFIG_DEBUG_LEVEL >= 3) + { + static int slice = 0; + static int noteCount = -1; + if (slice++ % 10 == 0) { + int n = pNotePool->countFreeElements(); + if (n != noteCount) { + noteCount = n; + dmsg(1,("[%d] free Note objects count = %d\n", slice / 10, n)); + } + } + } + #endif // empty the engine's event list for the next audio fragment ClearEventLists(); @@ -379,9 +396,8 @@ } pVoicePool->clear(); - // (re)create event generator - if (pEventGenerator) delete pEventGenerator; - pEventGenerator = new EventGenerator(pAudioOut->SampleRate()); + // update event generator + pEventGenerator->SetSampleRate(pAudioOut->SampleRate()); dmsg(1,("Starting disk thread...")); pDiskThread->StartThread(); @@ -605,7 +621,7 @@ // implementation of abstract methods derived from class 'LinuxSampler::NotePool' virtual Pool* GetVoicePool() OVERRIDE { return pVoicePool; } virtual Pool< Note >* GetNotePool() OVERRIDE { return pNotePool; } - virtual Pool* GetNodeIDPool() OVERRIDE { return ¬eIDPool; } + virtual Pool* GetNoteIDPool() OVERRIDE { return ¬eIDPool; } D* GetDiskThread() { return pDiskThread; } @@ -676,7 +692,7 @@ * @param pNoteOnEvent - event which caused this * @returns new note's unique ID (or zero on error) */ - note_id_t LaunchNewNote(LinuxSampler::EngineChannel* pEngineChannel, Event* pNoteOnEvent) OVERRIDE { + note_id_t LaunchNewNote(LinuxSampler::EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) OVERRIDE { EngineChannelBase* pChannel = static_cast*>(pEngineChannel); Pool< Note >* pNotePool = GetNotePool(); @@ -691,7 +707,7 @@ const note_id_t newNoteID = pNotePool->getID(itNewNote); // remember the engine's time when this note was triggered exactly - itNewNote->triggerSchedTime = pNoteOnEvent->SchedTime(); + itNewNote->triggerSchedTime = itNoteOnEvent->SchedTime(); // usually the new note (and its subsequent voices) will be // allocated on the key provided by the event's note number, @@ -699,11 +715,11 @@ // note, but rather a child note, then this new note will be // allocated on the parent note's key instead in order to // release the child note simultaniously with its parent note - itNewNote->hostKey = pNoteOnEvent->Param.Note.Key; + itNewNote->hostKey = itNoteOnEvent->Param.Note.Key; // in case this new note was requested to be a child note, // then retrieve its parent note and link them with each other - const note_id_t parentNoteID = pNoteOnEvent->Param.Note.ParentNoteID; + const note_id_t parentNoteID = itNoteOnEvent->Param.Note.ParentNoteID; if (parentNoteID) { NoteIterator itParentNote = pNotePool->fromID(parentNoteID); if (itParentNote) { @@ -731,15 +747,19 @@ dmsg(2,("Launched new note on host key %d\n", itNewNote->hostKey)); // copy event which caused this note - itNewNote->cause = *pNoteOnEvent; - itNewNote->eventID = pEventPool->getID(pNoteOnEvent); + itNewNote->cause = *itNoteOnEvent; + itNewNote->eventID = pEventPool->getID(itNoteOnEvent); + if (!itNewNote->eventID) { + dmsg(0,("Engine: No valid event ID resolved for note. This is a bug!!!\n")); + } // move new note to its host key MidiKey* pKey = &pChannel->pMIDIKeyInfo[itNewNote->hostKey]; itNewNote.moveToEndOf(pKey->pActiveNotes); + pChannel->markKeyAsActive(pKey); // assign unique note ID of this new note to the original note on event - pNoteOnEvent->Param.Note.ID = newNoteID; + itNoteOnEvent->Param.Note.ID = newNoteID; return newNoteID; // success } @@ -809,6 +829,7 @@ case Event::type_release_note: case Event::type_play_note: case Event::type_stop_note: + case Event::type_kill_note: case Event::type_note_synth_param: break; // noop } @@ -864,6 +885,7 @@ RTList::Iterator itEvent = pChannel->pEvents->first(); RTList::Iterator end = pChannel->pEvents->end(); for (; itEvent != end; ++itEvent) { + bool bIsCC = false; // just for resetting RPN/NRPN below switch (itEvent->Type) { case Event::type_note_on: dmsg(5,("Engine: Note on received\n")); @@ -881,9 +903,24 @@ dmsg(5,("Engine: Stop Note received\n")); ProcessNoteOff((EngineChannel*)itEvent->pEngineChannel, itEvent); break; + case Event::type_kill_note: + dmsg(5,("Engine: Kill Note received\n")); + ProcessKillNote((EngineChannel*)itEvent->pEngineChannel, itEvent); + break; case Event::type_control_change: dmsg(5,("Engine: MIDI CC received\n")); ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent); + bIsCC = true; + break; + case Event::type_rpn: // this can only be reached here by an instrument script having called set_rpn() + dmsg(5,("Engine: MIDI RPN received\n")); + ProcessHardcodedRpn((EngineChannel*)itEvent->pEngineChannel, itEvent); + bIsCC = true; + break; + case Event::type_nrpn: // this can only be reached here by an instrument script having called set_nrpn() + dmsg(5,("Engine: MIDI NRPN received\n")); + ProcessHardcodedNrpn((EngineChannel*)itEvent->pEngineChannel, itEvent); + bIsCC = true; break; case Event::type_channel_pressure: dmsg(5,("Engine: MIDI Chan. Pressure received\n")); @@ -909,6 +946,14 @@ case Event::type_release_note: break; // noop } + // reset cached RPN/NRPN parameter number and data in + // case this event was not a control change event + if (!bIsCC) { + if (pChannel->GetMidiRpnParameter() >= 0) + pChannel->ResetMidiRpnParameter(); + if (pChannel->GetMidiNrpnParameter() >= 0) + pChannel->ResetMidiNrpnParameter(); + } } } @@ -976,6 +1021,10 @@ // script event object RTList::Iterator itScriptEvent = pChannel->pScript->pEvents->allocAppend(); + // if event handler uses polyphonic variables, reset them + // to zero values before starting to execute the handler + if (pEventHandler->isPolyphonic()) + itScriptEvent->execCtx->resetPolyphonicData(); ProcessScriptEvent( pChannel, itEvent, pEventHandler, itScriptEvent ); @@ -1008,10 +1057,15 @@ // initialize/reset other members itScriptEvent->cause = *itEvent; + itScriptEvent->scheduleTime = itEvent->SchedTime(); itScriptEvent->currentHandler = 0; itScriptEvent->executionSlices = 0; itScriptEvent->ignoreAllWaitCalls = false; itScriptEvent->handlerType = pEventHandler->eventHandlerType(); + itScriptEvent->parentHandlerID = 0; + itScriptEvent->childHandlerID[0] = 0; + itScriptEvent->autoAbortByParent = false; + itScriptEvent->forkIndex = 0; // this is the native representation of the $EVENT_ID script variable itScriptEvent->id = (itEvent->Type == Event::type_note_on) @@ -1124,6 +1178,7 @@ * @returns 0 on success, a value < 0 if no active voice could be picked for voice stealing */ int StealVoice(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) { + dmsg(3,("StealVoice()\n")); if (VoiceSpawnsLeft <= 0) { dmsg(1,("Max. voice thefts per audio fragment reached (you may raise CONFIG_MAX_VOICES).\n")); return -1; @@ -1148,6 +1203,10 @@ int iChannelIndex; VoiceIterator itSelectedVoice; + #if CONFIG_DEVMODE + EngineChannel* pBegin = NULL; // to detect endless loop + #endif + // select engine channel if (pLastStolenChannel) { pSelectedChannel = pLastStolenChannel; @@ -1190,7 +1249,7 @@ } #if CONFIG_DEVMODE - EngineChannel* pBegin = pSelectedChannel; // to detect endless loop + pBegin = pSelectedChannel; // to detect endless loop #endif // CONFIG_DEVMODE while (true) { // iterate through engine channels @@ -1283,17 +1342,36 @@ RTList::Iterator itScriptEvent = pEngineChannel->pScript->pEvents->allocAppend(); + itScriptEvent->cause = pEventGenerator->CreateEvent(0); + itScriptEvent->cause.Type = (Event::type_t) -1; // some invalid type to avoid random event processing itScriptEvent->cause.pEngineChannel = pEngineChannel; + itScriptEvent->cause.pMidiInputPort = pEngineChannel->GetMidiInputPort(); + itScriptEvent->id = 0; itScriptEvent->handlers[0] = pEngineChannel->pScript->handlerInit; itScriptEvent->handlers[1] = NULL; itScriptEvent->currentHandler = 0; itScriptEvent->executionSlices = 0; itScriptEvent->ignoreAllWaitCalls = false; itScriptEvent->handlerType = VM_EVENT_HANDLER_INIT; - - /*VMExecStatus_t res = */ pScriptVM->exec( - pEngineChannel->pScript->parserContext, &*itScriptEvent - ); + itScriptEvent->parentHandlerID = 0; + itScriptEvent->childHandlerID[0] = 0; + itScriptEvent->autoAbortByParent = false; + itScriptEvent->forkIndex = 0; + + VMExecStatus_t res; + size_t instructionsCount = 0; + const size_t maxInstructions = 200000; // aiming approx. 1 second max. (based on very roughly 5us / instruction) + bool bWarningShown = false; + do { + res = pScriptVM->exec( + pEngineChannel->pScript->parserContext, &*itScriptEvent + ); + instructionsCount += itScriptEvent->execCtx->instructionsPerformed(); + if (instructionsCount > maxInstructions && !bWarningShown) { + bWarningShown = true; + dmsg(0,("[ScriptVM] WARNING: \"init\" event handler of instrument script executing for long time!\n")); + } + } while (res & VM_EXEC_SUSPENDED && !(res & VM_EXEC_ERROR)); pEngineChannel->pScript->pEvents->free(itScriptEvent); } @@ -1354,7 +1432,8 @@ // usually there should already be a new Note object NoteIterator itNote = GetNotePool()->fromID(itVoiceStealEvent->Param.Note.ID); if (!itNote) { // should not happen, but just to be sure ... - const note_id_t noteID = LaunchNewNote(pEngineChannel, &*itVoiceStealEvent); + dmsg(2,("Engine: No Note object for stolen voice!\n")); + const note_id_t noteID = LaunchNewNote(pEngineChannel, itVoiceStealEvent); if (!noteID) { dmsg(1,("Engine: Voice stealing failed; No Note object and Note pool empty!\n")); continue; @@ -1398,7 +1477,7 @@ void PostProcess(EngineChannel* pEngineChannel) { EngineChannelBase* pChannel = static_cast*>(pEngineChannel); - pChannel->FreeAllInactiveKyes(); + pChannel->FreeAllInactiveKeys(); // empty the engine channel's own event lists // (only events of the current audio fragment cycle) @@ -1420,69 +1499,95 @@ EngineChannelBase* pChannel = static_cast*>(pEngineChannel); + // will be set to true if this CC event has anything to do with RPN/NRPN + bool bIsRpn = false, bIsNrpn = false; + switch (itControlChangeEvent->Param.CC.Controller) { case 5: { // portamento time pChannel->PortamentoTime = (float) itControlChangeEvent->Param.CC.Value / 127.0f * (float) CONFIG_PORTAMENTO_TIME_MAX + (float) CONFIG_PORTAMENTO_TIME_MIN; break; } - case 6: { // data entry (currently only used for RPN and NRPN controllers) - //dmsg(1,("DATA ENTRY %d\n", itControlChangeEvent->Param.CC.Value)); - if (pChannel->GetMidiRpnController() >= 0) { // RPN controller number was sent previously ... - dmsg(4,("Guess it's an RPN ...\n")); - if (pChannel->GetMidiRpnController() == 2) { // coarse tuning in half tones - int transpose = (int) itControlChangeEvent->Param.CC.Value - 64; - // limit to +- two octaves for now - transpose = RTMath::Min(transpose, 24); - transpose = RTMath::Max(transpose, -24); - pChannel->GlobalTranspose = transpose; - // workaround, so we won't have hanging notes - pChannel->ReleaseAllVoices(itControlChangeEvent); - } - // to prevent other MIDI CC #6 messages to be misenterpreted as RPN controller data - pChannel->ResetMidiRpnController(); - } else if (pChannel->GetMidiNrpnController() >= 0) { // NRPN controller number was sent previously ... - dmsg(4,("Guess it's an NRPN ...\n")); - const int NrpnCtrlMSB = pChannel->GetMidiNrpnController() >> 8; - const int NrpnCtrlLSB = pChannel->GetMidiNrpnController() & 0xff; - dmsg(4,("NRPN MSB=%d LSB=%d Data=%d\n", NrpnCtrlMSB, NrpnCtrlLSB, itControlChangeEvent->Param.CC.Value)); - switch (NrpnCtrlMSB) { - case 0x1a: { // volume level of note (Roland GS NRPN) - const uint note = NrpnCtrlLSB; - const uint vol = itControlChangeEvent->Param.CC.Value; - dmsg(4,("Note Volume NRPN received (note=%d,vol=%d).\n", note, vol)); - if (note < 128 && vol < 128) - pChannel->pMIDIKeyInfo[note].Volume = VolumeCurve[vol]; - break; - } - case 0x1c: { // panpot of note (Roland GS NRPN) - const uint note = NrpnCtrlLSB; - const uint pan = itControlChangeEvent->Param.CC.Value; - dmsg(4,("Note Pan NRPN received (note=%d,pan=%d).\n", note, pan)); - if (note < 128 && pan < 128) { - pChannel->pMIDIKeyInfo[note].PanLeft = PanCurve[128 - pan]; - pChannel->pMIDIKeyInfo[note].PanRight = PanCurve[pan]; - } - break; - } - case 0x1d: { // reverb send of note (Roland GS NRPN) - const uint note = NrpnCtrlLSB; - const float reverb = float(itControlChangeEvent->Param.CC.Value) / 127.0f; - dmsg(4,("Note Reverb Send NRPN received (note=%d,send=%f).\n", note, reverb)); - if (note < 128) - pChannel->pMIDIKeyInfo[note].ReverbSend = reverb; + case 6: { // data entry (MSB) + //dmsg(1,("DATA ENTRY MSB %d\n", itControlChangeEvent->Param.CC.Value)); + if (pChannel->GetMidiRpnParameter() >= 0) { // RPN parameter number was sent previously ... + pChannel->SetMidiRpnDataMsb( + itControlChangeEvent->Param.CC.Value + ); + bIsRpn = true; + + // look-ahead: if next MIDI event is data entry LSB, + // then skip this event here for now (to avoid double + // handling of what's supposed to be one RPN event) + if (isNextEventCCNr(itControlChangeEvent, 38)) + break; + + int ch = itControlChangeEvent->Param.CC.Channel; + int param = pChannel->GetMidiRpnParameter(); + int value = pChannel->GetMidiRpnData(); + + // transform event type: CC event -> RPN event + itControlChangeEvent->Type = Event::type_rpn; + itControlChangeEvent->Param.RPN.Channel = ch; + itControlChangeEvent->Param.RPN.Parameter = param; + itControlChangeEvent->Param.RPN.Value = value; + + // if there's a RPN script handler, run it ... + if (pChannel->pScript->handlerRpn) { + const event_id_t eventID = + pEventPool->getID(itControlChangeEvent); + // run the RPN script handler + ProcessEventByScript( + pChannel, itControlChangeEvent, + pChannel->pScript->handlerRpn + ); + // if RPN event was dropped by script, abort + // here to avoid hard coded RPN processing below + if (!pEventPool->fromID(eventID)) break; - } - case 0x1e: { // chorus send of note (Roland GS NRPN) - const uint note = NrpnCtrlLSB; - const float chorus = float(itControlChangeEvent->Param.CC.Value) / 127.0f; - dmsg(4,("Note Chorus Send NRPN received (note=%d,send=%f).\n", note, chorus)); - if (note < 128) - pChannel->pMIDIKeyInfo[note].ChorusSend = chorus; + } + + // do the actual (hard-coded) RPN value change processing + ProcessHardcodedRpn(pEngineChannel, itControlChangeEvent); + + } else if (pChannel->GetMidiNrpnParameter() >= 0) { // NRPN parameter number was sent previously ... + pChannel->SetMidiNrpnDataMsb( + itControlChangeEvent->Param.CC.Value + ); + bIsNrpn = true; + + // look-ahead: if next MIDI event is data entry LSB, + // then skip this event here for now (to avoid double + // handling of what's supposed to be one NRPN event) + if (isNextEventCCNr(itControlChangeEvent, 38)) + break; + + int ch = itControlChangeEvent->Param.CC.Channel; + int param = pChannel->GetMidiNrpnParameter(); + int value = pChannel->GetMidiNrpnData(); + + // transform event type: CC event -> NRPN event + itControlChangeEvent->Type = Event::type_nrpn; + itControlChangeEvent->Param.NRPN.Channel = ch; + itControlChangeEvent->Param.NRPN.Parameter = param; + itControlChangeEvent->Param.NRPN.Value = value; + + // if there's a NRPN script handler, run it ... + if (pChannel->pScript->handlerNrpn) { + const event_id_t eventID = + pEventPool->getID(itControlChangeEvent); + // run the NRPN script handler + ProcessEventByScript( + pChannel, itControlChangeEvent, + pChannel->pScript->handlerNrpn + ); + // if NRPN event was dropped by script, abort + // here to avoid hard coded NRPN processing below + if (!pEventPool->fromID(eventID)) break; - } } - // to prevent other MIDI CC #6 messages to be misenterpreted as NRPN controller data - pChannel->ResetMidiNrpnController(); + + // do the actual (hard-coded) NRPN value change processing + ProcessHardcodedNrpn(pEngineChannel, itControlChangeEvent); } break; } @@ -1497,6 +1602,78 @@ pChannel->iLastPanRequest = itControlChangeEvent->Param.CC.Value; break; } + case 38: { // data entry (LSB) + //dmsg(1,("DATA ENTRY LSB %d\n", itControlChangeEvent->Param.CC.Value)); + if (pChannel->GetMidiRpnParameter() >= 0) { // RPN parameter number was sent previously ... + pChannel->SetMidiRpnDataLsb( + itControlChangeEvent->Param.CC.Value + ); + bIsRpn = true; + + int ch = itControlChangeEvent->Param.CC.Channel; + int param = pChannel->GetMidiRpnParameter(); + int value = pChannel->GetMidiRpnData(); + + // transform event type: CC event -> RPN event + itControlChangeEvent->Type = Event::type_rpn; + itControlChangeEvent->Param.RPN.Channel = ch; + itControlChangeEvent->Param.RPN.Parameter = param; + itControlChangeEvent->Param.RPN.Value = value; + + // if there's a RPN script handler, run it ... + if (pChannel->pScript->handlerRpn) { + const event_id_t eventID = + pEventPool->getID(itControlChangeEvent); + // run the RPN script handler + ProcessEventByScript( + pChannel, itControlChangeEvent, + pChannel->pScript->handlerRpn + ); + // if RPN event was dropped by script, abort + // here to avoid hard coded RPN processing below + if (!pEventPool->fromID(eventID)) + break; + } + + // do the actual (hard-coded) RPN value change processing + ProcessHardcodedRpn(pEngineChannel, itControlChangeEvent); + + } else if (pChannel->GetMidiNrpnParameter() >= 0) { // NRPN parameter number was sent previously ... + pChannel->SetMidiNrpnDataLsb( + itControlChangeEvent->Param.CC.Value + ); + bIsNrpn = true; + + int ch = itControlChangeEvent->Param.CC.Channel; + int param = pChannel->GetMidiNrpnParameter(); + int value = pChannel->GetMidiNrpnData(); + + // transform event type: CC event -> NRPN event + itControlChangeEvent->Type = Event::type_nrpn; + itControlChangeEvent->Param.NRPN.Channel = ch; + itControlChangeEvent->Param.NRPN.Parameter = param; + itControlChangeEvent->Param.NRPN.Value = value; + + // if there's a NRPN script handler, run it ... + if (pChannel->pScript->handlerNrpn) { + const event_id_t eventID = + pEventPool->getID(itControlChangeEvent); + // run the NRPN script handler + ProcessEventByScript( + pChannel, itControlChangeEvent, + pChannel->pScript->handlerNrpn + ); + // if NRPN event was dropped by script, abort + // here to avoid hard coded NRPN processing below + if (!pEventPool->fromID(eventID)) + break; + } + + // do the actual (hard-coded) NRPN value change processing + ProcessHardcodedNrpn(pEngineChannel, itControlChangeEvent); + } + break; + } case 64: { // sustain if (itControlChangeEvent->Param.CC.Value >= 64 && !pChannel->SustainPedal) { dmsg(4,("DAMPER (RIGHT) PEDAL DOWN\n")); @@ -1570,24 +1747,28 @@ } break; } - case 98: { // NRPN controller LSB + case 98: { // NRPN parameter LSB dmsg(4,("NRPN LSB %d\n", itControlChangeEvent->Param.CC.Value)); - pEngineChannel->SetMidiNrpnControllerLsb(itControlChangeEvent->Param.CC.Value); + bIsNrpn = true; + pEngineChannel->SetMidiNrpnParameterLsb(itControlChangeEvent->Param.CC.Value); break; } - case 99: { // NRPN controller MSB + case 99: { // NRPN parameter MSB dmsg(4,("NRPN MSB %d\n", itControlChangeEvent->Param.CC.Value)); - pEngineChannel->SetMidiNrpnControllerMsb(itControlChangeEvent->Param.CC.Value); + bIsNrpn = true; + pEngineChannel->SetMidiNrpnParameterMsb(itControlChangeEvent->Param.CC.Value); break; } - case 100: { // RPN controller LSB + case 100: { // RPN parameter LSB dmsg(4,("RPN LSB %d\n", itControlChangeEvent->Param.CC.Value)); - pEngineChannel->SetMidiRpnControllerLsb(itControlChangeEvent->Param.CC.Value); + bIsRpn = true; + pEngineChannel->SetMidiRpnParameterLsb(itControlChangeEvent->Param.CC.Value); break; } - case 101: { // RPN controller MSB + case 101: { // RPN parameter MSB dmsg(4,("RPN MSB %d\n", itControlChangeEvent->Param.CC.Value)); - pEngineChannel->SetMidiRpnControllerMsb(itControlChangeEvent->Param.CC.Value); + bIsRpn = true; + pEngineChannel->SetMidiRpnParameterMsb(itControlChangeEvent->Param.CC.Value); break; } @@ -1621,6 +1802,88 @@ break; } } + + // reset cached RPN/NRPN parameter number and data in case this + // CC event had nothing to do with RPN/NRPN + if (!bIsRpn && pChannel->GetMidiRpnParameter() >= 0) + pChannel->ResetMidiRpnParameter(); + if (!bIsNrpn && pChannel->GetMidiNrpnParameter() >= 0) + pChannel->ResetMidiNrpnParameter(); + } + + /** + * Process MIDI RPN events with hard coded behavior. + * + * @param pEngineChannel - engine channel on which the MIDI RPN + * event was received + * @param itRpnEvent - the actual MIDI RPN event + */ + void ProcessHardcodedRpn(EngineChannel* pEngineChannel, + Pool::Iterator& itRpnEvent) + { + EngineChannelBase* pChannel = + static_cast*>(pEngineChannel); + + if (itRpnEvent->Param.RPN.Parameter == 2) { // coarse tuning in half tones + int transpose = (int) itRpnEvent->Param.RPN.ValueMSB() - 64; + // limit to +- two octaves for now + transpose = RTMath::Min(transpose, 24); + transpose = RTMath::Max(transpose, -24); + pChannel->GlobalTranspose = transpose; + // workaround, so we won't have hanging notes + pChannel->ReleaseAllVoices(itRpnEvent); + } + } + + /** + * Process MIDI NRPN events with hard coded behavior. + * + * @param pEngineChannel - engine channel on which the MIDI NRPN + * event was received + * @param itRpnEvent - the actual MIDI NRPN event + */ + void ProcessHardcodedNrpn(EngineChannel* pEngineChannel, + Pool::Iterator& itNrpnEvent) + { + EngineChannelBase* pChannel = + static_cast*>(pEngineChannel); + + switch (itNrpnEvent->Param.NRPN.ParameterMSB()) { + case 0x1a: { // volume level of note (Roland GS NRPN) + const uint note = itNrpnEvent->Param.NRPN.ParameterLSB(); + const uint vol = itNrpnEvent->Param.NRPN.ValueMSB(); + dmsg(4,("Note Volume NRPN received (note=%d,vol=%d).\n", note, vol)); + if (note < 128 && vol < 128) + pChannel->pMIDIKeyInfo[note].Volume = VolumeCurve[vol]; + break; + } + case 0x1c: { // panpot of note (Roland GS NRPN) + const uint note = itNrpnEvent->Param.NRPN.ParameterLSB(); + const uint pan = itNrpnEvent->Param.NRPN.ValueMSB(); + dmsg(4,("Note Pan NRPN received (note=%d,pan=%d).\n", note, pan)); + if (note < 128 && pan < 128) { + pChannel->pMIDIKeyInfo[note].PanLeft = PanCurve[128 - pan]; + pChannel->pMIDIKeyInfo[note].PanRight = PanCurve[pan]; + } + break; + } + case 0x1d: { // reverb send of note (Roland GS NRPN) + const uint note = itNrpnEvent->Param.NRPN.ParameterLSB(); + const float reverb = float(itNrpnEvent->Param.NRPN.Value) / 16383.f; + dmsg(4,("Note Reverb Send NRPN received (note=%d,send=%f).\n", note, reverb)); + if (note < 128) + pChannel->pMIDIKeyInfo[note].ReverbSend = reverb; + break; + } + case 0x1e: { // chorus send of note (Roland GS NRPN) + const uint note = itNrpnEvent->Param.NRPN.ParameterLSB(); + const float chorus = float(itNrpnEvent->Param.NRPN.Value) / 16383.f; + dmsg(4,("Note Chorus Send NRPN received (note=%d,send=%f).\n", note, chorus)); + if (note < 128) + pChannel->pMIDIKeyInfo[note].ChorusSend = chorus; + break; + } + } } virtual D* CreateDiskThread() = 0; @@ -1831,7 +2094,7 @@ itPseudoNoteOnEvent->Param.Note.Key = i; itPseudoNoteOnEvent->Param.Note.Velocity = pOtherKey->Velocity; // assign a new note to this note-on event - if (LaunchNewNote(pChannel, &*itPseudoNoteOnEvent)) { + if (LaunchNewNote(pChannel, itPseudoNoteOnEvent)) { // allocate and trigger new voice(s) for the other key TriggerNewVoices(pChannel, itPseudoNoteOnEvent, false); } @@ -1863,7 +2126,8 @@ if (bShouldRelease) { itNoteOffEventOnKeyList->Type = Event::type_release_key; // transform event type // spawn release triggered voice(s) if needed - ProcessReleaseTrigger(pChannel, itNoteOffEventOnKeyList, pKey); + if (pKey->ReleaseTrigger & release_trigger_noteoff) + ProcessReleaseTrigger(pChannel, itNoteOffEventOnKeyList, pKey); } } else if (itNoteOffEventOnKeyList->Type == Event::type_stop_note) { // This programmatically caused event is caused by a call to @@ -1894,7 +2158,7 @@ * @param pEngineChannel - engine channel on which this event occurred on * @param itEvent - release trigger event (contains note number) */ - virtual void ProcessReleaseTrigger(EngineChannel* pEngineChannel, RTList::Iterator& itEvent) OVERRIDE { + virtual void ProcessReleaseTriggerBySustain(EngineChannel* pEngineChannel, RTList::Iterator& itEvent) OVERRIDE { EngineChannelBase* pChannel = static_cast*>(pEngineChannel); const int iKey = itEvent->Param.Note.Key; @@ -1918,18 +2182,36 @@ // spawn release triggered voice(s) if needed if (pKey->ReleaseTrigger && pChannel->pInstrument) { // assign a new note to this release event - if (LaunchNewNote(pChannel, &*itEvent)) { + if (LaunchNewNote(pChannel, itEvent)) { // allocate and trigger new release voice(s) TriggerReleaseVoices(pChannel, itEvent); } - pKey->ReleaseTrigger = false; + pKey->ReleaseTrigger = release_trigger_none; } } /** + * Called on "kill note" events, which currently only happens on + * built-in real-time instrument script function fade_out(). This + * method only fulfills one task: moving the even to the Note's own + * event list so that its voices can process the kill event sample + * accurately. + */ + void ProcessKillNote(EngineChannel* pEngineChannel, RTList::Iterator& itEvent) { + EngineChannelBase* pChannel = static_cast*>(pEngineChannel); + + NoteBase* pNote = pChannel->pEngine->NoteByID( itEvent->Param.Note.ID ); + if (!pNote || pNote->hostKey < 0 || pNote->hostKey >= 128) return; + + // move note kill event to its MIDI key + MidiKey* pKey = &pChannel->pMIDIKeyInfo[pNote->hostKey]; + itEvent.moveToEndOf(pKey->pEvents); + } + + /** * Called on note synthesis parameter change events. These are * internal events caused by calling built-in real-time instrument - * script functions like change_vol(), change_pitch(), etc. + * script functions like change_vol(), change_tune(), etc. * * This method performs two tasks: * @@ -1950,46 +2232,86 @@ NoteBase* pNote = pChannel->pEngine->NoteByID( itEvent->Param.NoteSynthParam.NoteID ); if (!pNote || pNote->hostKey < 0 || pNote->hostKey >= 128) return; - const bool& relative = itEvent->Param.NoteSynthParam.Relative; - switch (itEvent->Param.NoteSynthParam.Type) { case Event::synth_param_volume: - if (relative) - pNote->Override.Volume *= itEvent->Param.NoteSynthParam.Delta; - else - pNote->Override.Volume = itEvent->Param.NoteSynthParam.Delta; - itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Volume; + pNote->apply(itEvent, &NoteBase::_Override::Volume); + break; + case Event::synth_param_volume_time: + pNote->Override.VolumeTime = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_volume_curve: + itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->Override.VolumeCurve = (fade_curve_t) itEvent->Param.NoteSynthParam.AbsValue; break; case Event::synth_param_pitch: - if (relative) - pNote->Override.Pitch *= itEvent->Param.NoteSynthParam.Delta; - else - pNote->Override.Pitch = itEvent->Param.NoteSynthParam.Delta; - itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Pitch; + pNote->apply(itEvent, &NoteBase::_Override::Pitch); + break; + case Event::synth_param_pitch_time: + pNote->Override.PitchTime = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_pitch_curve: + itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->Override.PitchCurve = (fade_curve_t) itEvent->Param.NoteSynthParam.AbsValue; break; case Event::synth_param_pan: - if (relative) { - pNote->Override.Pan = RTMath::RelativeSummedAvg(pNote->Override.Pan, itEvent->Param.NoteSynthParam.Delta, ++pNote->Override.PanSources); - } else { - pNote->Override.Pan = itEvent->Param.NoteSynthParam.Delta; - pNote->Override.PanSources = 1; // only relevant on subsequent change_pan() instrument script calls on same note with 'relative' argument being set - } - itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Pan; + pNote->apply(itEvent, &NoteBase::_Override::Pan); + break; + case Event::synth_param_pan_time: + pNote->Override.PanTime = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_pan_curve: + itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->Override.PanCurve = (fade_curve_t) itEvent->Param.NoteSynthParam.AbsValue; break; case Event::synth_param_cutoff: - pNote->Override.Cutoff = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->apply(itEvent, &NoteBase::_Override::Cutoff); break; case Event::synth_param_resonance: - pNote->Override.Resonance = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->apply(itEvent, &NoteBase::_Override::Resonance); break; case Event::synth_param_attack: - pNote->Override.Attack = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->apply(itEvent, &NoteBase::_Override::Attack); break; case Event::synth_param_decay: - pNote->Override.Decay = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->apply(itEvent, &NoteBase::_Override::Decay); + break; + case Event::synth_param_sustain: + pNote->apply(itEvent, &NoteBase::_Override::Sustain); break; case Event::synth_param_release: - pNote->Override.Release = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->apply(itEvent, &NoteBase::_Override::Release); + break; + + case Event::synth_param_cutoff_attack: + pNote->apply(itEvent, &NoteBase::_Override::CutoffAttack); + break; + case Event::synth_param_cutoff_decay: + pNote->apply(itEvent, &NoteBase::_Override::CutoffDecay); + break; + case Event::synth_param_cutoff_sustain: + pNote->apply(itEvent, &NoteBase::_Override::CutoffSustain); + break; + case Event::synth_param_cutoff_release: + pNote->apply(itEvent, &NoteBase::_Override::CutoffRelease); + break; + + case Event::synth_param_amp_lfo_depth: + pNote->apply(itEvent, &NoteBase::_Override::AmpLFODepth); + break; + case Event::synth_param_amp_lfo_freq: + pNote->apply(itEvent, &NoteBase::_Override::AmpLFOFreq); + break; + case Event::synth_param_cutoff_lfo_depth: + pNote->apply(itEvent, &NoteBase::_Override::CutoffLFODepth); + break; + case Event::synth_param_cutoff_lfo_freq: + pNote->apply(itEvent, &NoteBase::_Override::CutoffLFOFreq); + break; + case Event::synth_param_pitch_lfo_depth: + pNote->apply(itEvent, &NoteBase::_Override::PitchLFODepth); + break; + case Event::synth_param_pitch_lfo_freq: + pNote->apply(itEvent, &NoteBase::_Override::PitchLFOFreq); break; } @@ -2119,12 +2441,13 @@ } else { // on success --VoiceSpawnsLeft; - if (!pKey->Active) { // mark as active key - pKey->Active = true; - pKey->itSelf = pChannel->pActiveKeys->allocAppend(); - *pKey->itSelf = itNoteOnEvent->Param.Note.Key; - } - if (itNewVoice->Type & Voice::type_release_trigger_required) pKey->ReleaseTrigger = true; // mark key for the need of release triggered voice(s) + + // should actually be superfluous now, since this is + // already done in LaunchNewNote() + pChannel->markKeyAsActive(pKey); + + if (itNewVoice->Type & Voice::type_release_trigger_required) + pKey->ReleaseTrigger |= itNewVoice->GetReleaseTriggerFlags(); // mark key for the need of release triggered voice(s) return 0; // success } }