--- linuxsampler/trunk/src/engines/EngineBase.h 2009/11/03 19:27:42 2027 +++ linuxsampler/trunk/src/engines/EngineBase.h 2016/04/10 18:22:23 2871 @@ -3,8 +3,9 @@ * LinuxSampler - modular, streaming capable sampler * * * * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * - * Copyright (C) 2005-2009 Christian Schoenebeck * - * Copyright (C) 2009 Grigor Iliev * + * Copyright (C) 2005-2008 Christian Schoenebeck * + * Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev * + * Copyright (C) 2012-2016 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 * @@ -104,7 +105,7 @@ * @param Samples - number of sample points to be rendered * @returns 0 on success */ - virtual int RenderAudio(uint Samples) { + virtual int RenderAudio(uint Samples) OVERRIDE { dmsg(8,("RenderAudio(Samples=%d)\n", Samples)); // return if engine disabled @@ -143,6 +144,10 @@ } } } + + // In case scale tuning has been changed, recalculate pitch for + // all active voices. + ProcessScaleTuningChange(); // reset internal voice counter (just for statistic of active voices) ActiveVoiceCountTemp = 0; @@ -190,15 +195,24 @@ // been deleted by the disk thread if (iPendingStreamDeletions) ProcessPendingStreamDeletions(); + // Release the instrument change command. (This has to + // be done after all voices have been rendered and not + // in HandleInstrumentChanges, as the RegionsInUse + // list has been built up by the voice renderers.) + for (int i = 0; i < engineChannels.size(); i++) { + EngineChannelBase* channel = + static_cast*>(engineChannels[i]); + channel->InstrumentChangeCommandReader.Unlock(); + } FrameTime += Samples; EngineDisabled.RttDone(); return 0; } - virtual int MaxVoices() { return pVoicePool->poolSize(); } + virtual int MaxVoices() OVERRIDE { return pVoicePool->poolSize(); } - virtual void SetMaxVoices(int iVoices) throw (Exception) { + virtual void SetMaxVoices(int iVoices) throw (Exception) OVERRIDE { if (iVoices < 1) throw Exception("Maximum voices for an engine cannot be set lower than 1"); @@ -234,14 +248,18 @@ } pVoicePool->clear(); + PostSetMaxVoices(iVoices); ResumeAll(); } + + /** Called after the new max number of voices is set and before resuming the engine. */ + virtual void PostSetMaxVoices(int iVoices) { } - virtual uint DiskStreamCount() { return (pDiskThread) ? pDiskThread->GetActiveStreamCount() : 0; } - virtual uint DiskStreamCountMax() { return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; } - virtual int MaxDiskStreams() { return iMaxDiskStreams; } + virtual uint DiskStreamCount() OVERRIDE { return (pDiskThread) ? pDiskThread->GetActiveStreamCount() : 0; } + virtual uint DiskStreamCountMax() OVERRIDE { return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; } + virtual int MaxDiskStreams() OVERRIDE { return iMaxDiskStreams; } - virtual void SetMaxDiskStreams(int iStreams) throw (Exception) { + virtual void SetMaxDiskStreams(int iStreams) throw (Exception) OVERRIDE { if (iStreams < 0) throw Exception("Maximum disk streams for an engine cannot be set lower than 0"); @@ -256,9 +274,9 @@ ResumeAll(); } - virtual String DiskStreamBufferFillBytes() { return (pDiskThread) ? pDiskThread->GetBufferFillBytes() : ""; } - virtual String DiskStreamBufferFillPercentage() { return (pDiskThread) ? pDiskThread->GetBufferFillPercentage() : ""; } - virtual InstrumentManager* GetInstrumentManager() { return &instruments; } + virtual String DiskStreamBufferFillBytes() OVERRIDE { return (pDiskThread) ? pDiskThread->GetBufferFillBytes() : ""; } + virtual String DiskStreamBufferFillPercentage() OVERRIDE { return (pDiskThread) ? pDiskThread->GetBufferFillPercentage() : ""; } + virtual InstrumentManager* GetInstrumentManager() OVERRIDE { return &instruments; } /** * Connect this engine instance with the given audio output device. @@ -269,7 +287,7 @@ * * @param pAudioOut - audio output device to connect to */ - virtual void Connect(AudioOutputDevice* pAudioOut) { + virtual void Connect(AudioOutputDevice* pAudioOut) OVERRIDE { // caution: don't ignore if connecting to the same device here, // because otherwise SetMaxDiskStreams() implementation won't work anymore! @@ -299,7 +317,7 @@ // lower minimum release time const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate; for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { - iterVoice->EG1.CalculateFadeOutCoeff(minReleaseTime, SampleRate); + iterVoice->CalculateFadeOutCoeff(minReleaseTime, SampleRate); } pVoicePool->clear(); } @@ -332,13 +350,35 @@ pDiskThread->StartThread(); dmsg(1,("OK\n")); + bool printEqInfo = true; for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { if (!iterVoice->pDiskThread) { dmsg(0,("Engine -> voice::trigger: !pDiskThread\n")); exit(EXIT_FAILURE); } + + iterVoice->CreateEq(); + + if(printEqInfo) { + iterVoice->PrintEqInfo(); + printEqInfo = false; + } } pVoicePool->clear(); + + // (re)create dedicated voice audio buffers + //TODO: we could optimize resource usage a bit by just allocating these dedicated voice buffers when there is at least one engine channel with FX sends, because only in this case those special buffers are used actually, but since it would usually only save couple bytes in total, its probably not worth it + if (pDedicatedVoiceChannelLeft) delete pDedicatedVoiceChannelLeft; + if (pDedicatedVoiceChannelRight) delete pDedicatedVoiceChannelRight; + pDedicatedVoiceChannelLeft = new AudioChannel(0, MaxSamplesPerCycle); + pDedicatedVoiceChannelRight = new AudioChannel(1, MaxSamplesPerCycle); + } + + // Implementattion for abstract method derived from Engine. + virtual void ReconnectAudioOutputDevice() OVERRIDE { + SuspendAll(); + if (pAudioOutputDevice) Connect(pAudioOutputDevice); + ResumeAll(); } /** @@ -393,13 +433,14 @@ * @param pRegion - region the engine shall stop using */ virtual void Suspend(RR* pRegion) { - dmsg(2,("EngineBase: Suspending Region %x ...\n",pRegion)); - SuspendedRegionsMutex.Lock(); - SuspensionChangeOngoing.Set(true); - pPendingRegionSuspension = pRegion; - SuspensionChangeOngoing.WaitAndUnlockIf(true); - SuspendedRegionsMutex.Unlock(); - dmsg(2,("EngineBase: Region %x suspended.",pRegion)); + dmsg(2,("EngineBase: Suspending Region %p ...\n",(void*)pRegion)); + { + LockGuard lock(SuspendedRegionsMutex); + SuspensionChangeOngoing.Set(true); + pPendingRegionSuspension = pRegion; + SuspensionChangeOngoing.WaitAndUnlockIf(true); + } + dmsg(2,("EngineBase: Region %p suspended.",(void*)pRegion)); } /** @@ -409,13 +450,14 @@ * @param pRegion - region the engine shall be allowed to use again */ virtual void Resume(RR* pRegion) { - dmsg(2,("EngineBase: Resuming Region %x ...\n",pRegion)); - SuspendedRegionsMutex.Lock(); - SuspensionChangeOngoing.Set(true); - pPendingRegionResumption = pRegion; - SuspensionChangeOngoing.WaitAndUnlockIf(true); - SuspendedRegionsMutex.Unlock(); - dmsg(2,("EngineBase: Region %x resumed.\n",pRegion)); + dmsg(2,("EngineBase: Resuming Region %p ...\n",(void*)pRegion)); + { + LockGuard lock(SuspendedRegionsMutex); + SuspensionChangeOngoing.Set(true); + pPendingRegionResumption = pRegion; + SuspensionChangeOngoing.WaitAndUnlockIf(true); + } + dmsg(2,("EngineBase: Region %p resumed.\n",(void*)pRegion)); } virtual void ResetSuspendedRegions() { @@ -530,17 +572,20 @@ //friend class EngineChannelBase; + static IM instruments; + protected: class SuspensionVoiceHandler : public MidiKeyboardManager::VoiceHandler { public: int PendingStreamDeletions; RR* pPendingRegionSuspension; + SuspensionVoiceHandler(RR* pPendingRegionSuspension) { PendingStreamDeletions = 0; this->pPendingRegionSuspension = pPendingRegionSuspension; } - virtual bool Process(MidiKey* pMidiKey) { + virtual bool Process(MidiKey* pMidiKey) OVERRIDE { VoiceIterator itVoice = pMidiKey->pActiveVoices->first(); // if current key is not associated with this region, skip this key if (itVoice->GetRegion()->GetParent() != pPendingRegionSuspension) return false; @@ -548,7 +593,7 @@ return true; } - virtual void Process(VoiceIterator& itVoice) { + virtual void Process(VoiceIterator& itVoice) OVERRIDE { // request a notification from disk thread side for stream deletion const Stream::Handle hStream = itVoice->KillImmediately(true); if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream @@ -558,8 +603,6 @@ } }; - static IM instruments; - Pool* pRegionPool[2]; ///< Double buffered pool, used by the engine channels to keep track of regions in use. int MinFadeOutSamples; ///< The number of samples needed to make an instant fade out (e.g. for voice stealing) without leading to clicks. D* pDiskThread; @@ -589,7 +632,88 @@ AbstractEngineChannel* pChannel = static_cast(pEngineChannel); pChannel->ImportEvents(Samples); - // process events + // if a valid real-time instrument script is loaded, pre-process + // the event list by running the script now, since the script + // might filter events or add new ones for this cycle + if (pChannel->pScript) { + const sched_time_t fragmentEndTime = pEventGenerator->schedTimeAtCurrentFragmentEnd(); + + // resume suspended script executions been scheduled for + // this audio fragment cycle (which were suspended in a + // previous audio fragment cycle) + ProcessSuspendedScriptEvents(pChannel, fragmentEndTime); + + // spawn new script executions for the new MIDI events of + // this audio fragment cycle + // + // FIXME: it would probably be better to just schedule newly spawned script executions here and then execute them altogether with already suspended ones all at once in order of all their scheduled timing + for (RTList::Iterator itEvent = pChannel->pEvents->first(), + end = pChannel->pEvents->end(); itEvent != end; ++itEvent) + { + switch (itEvent->Type) { + case Event::type_note_on: + if (pChannel->pScript->handlerNote) + ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerNote); + break; + case Event::type_note_off: + if (pChannel->pScript->handlerRelease) + ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerRelease); + break; + case Event::type_control_change: + case Event::type_channel_pressure: + case Event::type_pitchbend: + if (pChannel->pScript->handlerController) + ProcessEventByScript(pChannel, itEvent, pChannel->pScript->handlerController); + break; + case Event::type_note_pressure: + //TODO: ... + break; + } + } + + // this has to be run again, since the newly spawned scripts + // above may have cause suspended scripts that must be + // resumed within this same audio fragment cycle + // + // FIXME: see FIXME comment above + ProcessSuspendedScriptEvents(pChannel, fragmentEndTime); + } + + // if there are any delayed events scheduled for the current + // audio fragment cycle, then move and sort them into the main + // event list + if (!pChannel->delayedEvents.queue.isEmpty()) { + dmsg(5,("Engine: There are delayed MIDI events (total queue size: %d) ...\n", pChannel->delayedEvents.queue.size())); + const sched_time_t fragmentEndTime = pEventGenerator->schedTimeAtCurrentFragmentEnd(); + RTList::Iterator itEvent = pChannel->pEvents->first(); + while (true) { + RTList::Iterator itDelayedEventNode = + pEventGenerator->popNextScheduledEvent( + pChannel->delayedEvents.queue, + pChannel->delayedEvents.schedulerNodes, + fragmentEndTime + ); + if (!itDelayedEventNode) break; + // get the actual delayed event object and free the used scheduler node + RTList::Iterator itDelayedEvent = itDelayedEventNode->itEvent; + pChannel->delayedEvents.schedulerNodes.free(itDelayedEventNode); + if (!itDelayedEvent) { // should never happen, but just to be sure ... + dmsg(1,("Engine: Oops, invalid delayed event!\n")); + continue; + } + // skip all events on main event list which have a time + // before (or equal to) the delayed event to be inserted + for (; itEvent && itEvent->FragmentPos() <= itDelayedEvent->FragmentPos(); + ++itEvent); + // now move delayed event from delayedEvents.pList to + // the current position on the main event list + itEvent = itDelayedEvent.moveBefore(itEvent); + dmsg(5,("Engine: Inserted event of type %d into main event list (queue size: %d).\n", itEvent->Type, pChannel->delayedEvents.queue.size())); + } + dmsg(5,("Engine: End of delayed events (total queue size: %d).\n", pChannel->delayedEvents.queue.size())); + } + + // now process all events regularly { RTList::Iterator itEvent = pChannel->pEvents->first(); RTList::Iterator end = pChannel->pEvents->end(); @@ -607,6 +731,14 @@ dmsg(5,("Engine: MIDI CC received\n")); ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent); break; + case Event::type_channel_pressure: + dmsg(5,("Engine: MIDI Chan. Pressure received\n")); + ProcessChannelPressure((EngineChannel*)itEvent->pEngineChannel, itEvent); + break; + case Event::type_note_pressure: + dmsg(5,("Engine: MIDI Note Pressure received\n")); + ProcessPolyphonicKeyPressure((EngineChannel*)itEvent->pEngineChannel, itEvent); + break; case Event::type_pitchbend: dmsg(5,("Engine: Pitchbend received\n")); ProcessPitchbend(static_cast(itEvent->pEngineChannel), itEvent); @@ -624,12 +756,198 @@ } /** + * Run all suspended script execution instances which are scheduled + * to be resumed for the current audio fragment cycle. + * + * @param pChannel - engine channel on which suspended events occurred + */ + void ProcessSuspendedScriptEvents(AbstractEngineChannel* pChannel, const sched_time_t fragmentEndTime) { + while (true) { + RTList::Iterator itEvent = + pEventGenerator->popNextScheduledScriptEvent( + pChannel->pScript->suspendedEvents, + *pChannel->pScript->pEvents, fragmentEndTime + ); + if (!itEvent) break; + ResumeScriptEvent(pChannel, itEvent); + } + } + + /** @brief Call instrument script's event handler for this event. + * + * Causes a new execution instance of the currently loaded real-time + * instrument script's event handler (callback) to be spawned for + * the given MIDI event. + * + * @param pChannel - engine channel on which the MIDI event occurred + * @param itEvent - MIDI event that causes this new script execution + * @param pEventHandler - script's event handler to be executed + */ + void ProcessEventByScript(AbstractEngineChannel* pChannel, RTList::Iterator& itEvent, VMEventHandler* pEventHandler) { + const int key = itEvent->Param.Note.Key; // even if this is not a note on/off event, accessing it does not mean any harm + // check if polyphonic data is passed from "note" to "release" + // script event handlers + if (pEventHandler == pChannel->pScript->handlerRelease && + pChannel->pScript->handlerNote && + pChannel->pScript->handlerNote->isPolyphonic() && + pChannel->pScript->handlerRelease->isPolyphonic() && + !pChannel->pScript->pKeyEvents[key]->isEmpty()) + { + // polyphonic variable data is used/passed from "note" to + // "release" script callback, so we have to recycle the + // original "note on" script event(s) + RTList::Iterator it = pChannel->pScript->pKeyEvents[key]->first(); + RTList::Iterator end = pChannel->pScript->pKeyEvents[key]->end(); + for (; it != end; ++it) { + ProcessScriptEvent( + pChannel, itEvent, pEventHandler, it + ); + } + } else { + // no polyphonic data is used/passed from "note" to + // "release" script callback, so just use a new fresh + // script event object + RTList::Iterator itScriptEvent = + pChannel->pScript->pEvents->allocAppend(); + ProcessScriptEvent( + pChannel, itEvent, pEventHandler, itScriptEvent + ); + } + } + + /** @brief Spawn new execution instance of an instrument script handler. + * + * Will be called to initiate a new execution of a real-time + * instrument script event right from the start of the script's + * respective handler. If script execution did not complete after + * calling this method, the respective script exeuction is then + * suspended and a call to ResumeScriptEvent() will be used next + * time to continue its execution. + * + * @param pChannel - engine channel this script is running for + * @param itEvent - event which caused execution of this script + * event handler + * @param pEventHandler - VM representation of event handler to be + * executed + * @param itScriptEvent - script event that shall be processed + */ + void ProcessScriptEvent(AbstractEngineChannel* pChannel, RTList::Iterator& itEvent, VMEventHandler* pEventHandler, RTList::Iterator& itScriptEvent) { + if (!itScriptEvent) return; // not a valid script event (i.e. because no free script event was left in the script event pool) + + // fill the list of script handlers to be executed by this event + int i = 0; + itScriptEvent->handlers[i++] = pEventHandler; // actual event handler (i.e. note, controller) + itScriptEvent->handlers[i] = NULL; // NULL termination of list + + // initialize/reset other members + itScriptEvent->cause = *itEvent; + itScriptEvent->id = pEventPool->getID(itEvent); + itScriptEvent->currentHandler = 0; + itScriptEvent->executionSlices = 0; + + // run script handler(s) + VMExecStatus_t res = pScriptVM->exec( + pChannel->pScript->parserContext, &*itScriptEvent + ); + + // was the script suspended? + if (res & VM_EXEC_SUSPENDED) { // script was suspended ... + // in case the script was suspended, keep it on the allocated + // ScriptEvent list to be resume at the scheduled time in future, + // additionally insert it into a sorted time queue + pEventGenerator->scheduleAheadMicroSec( + pChannel->pScript->suspendedEvents, // scheduler queue + *itScriptEvent, // script event + itScriptEvent->cause.FragmentPos(), // current time of script event (basis for its next execution) + itScriptEvent->execCtx->suspensionTimeMicroseconds() // how long shall it be suspended + ); + } else { // script execution has finished without 'suspended' status ... + // if "polyphonic" variable data is passed from script's + // "note" event handler to its "release" event handler, then + // the script event must be kept and recycled for the later + // occuring "release" script event ... + if (pEventHandler == pChannel->pScript->handlerNote && + pChannel->pScript->handlerRelease && + pChannel->pScript->handlerNote->isPolyphonic() && + pChannel->pScript->handlerRelease->isPolyphonic()) + { + const int key = itEvent->Param.Note.Key; + itScriptEvent.moveToEndOf(pChannel->pScript->pKeyEvents[key & 127]); + } else { + // ... otherwise if no polyphonic data is passed and + // script's execution has finished without suspension + // status, then free the script event for a new future + // script event to be triggered from start + pChannel->pScript->pEvents->free(itScriptEvent); + } + } + } + + /** @brief Resume execution of instrument script. + * + * Will be called to resume execution of a real-time instrument + * script event which has been suspended in a previous audio + * fragment cycle. + * + * Script execution might be suspended for various reasons. Usually + * a script will be suspended if the script called the built-in + * "wait()" function, but it might also be suspended automatically + * if the script took too much execution time in an audio fragment + * cycle. So in the latter case automatic suspension is performed in + * order to avoid harm for the sampler's overall real-time + * requirements. + * + * @param pChannel - engine channel this script is running for + * @param itScriptEvent - script execution that shall be resumed + */ + void ResumeScriptEvent(AbstractEngineChannel* pChannel, RTList::Iterator& itScriptEvent) { + VMEventHandler* handler = itScriptEvent->handlers[itScriptEvent->currentHandler]; + + // run script + VMExecStatus_t res = pScriptVM->exec( + pChannel->pScript->parserContext, &*itScriptEvent + ); + + // was the script suspended? + if (res & VM_EXEC_SUSPENDED) { + // in case the script was suspended, keep it on the allocated + // ScriptEvent list to be resume at the scheduled time in future, + // additionally insert it into a sorted time queue + pEventGenerator->scheduleAheadMicroSec( + pChannel->pScript->suspendedEvents, // scheduler queue + *itScriptEvent, // script event + itScriptEvent->cause.FragmentPos(), // current time of script event (basis for its next execution) + itScriptEvent->execCtx->suspensionTimeMicroseconds() // how long shall it be suspended + ); + } else { // script execution has finished without 'suspended' status ... + // if "polyphonic" variable data is passed from script's + // "note" event handler to its "release" event handler, then + // the script event must be kept and recycled for the later + // occuring "release" script event ... + if (handler && handler == pChannel->pScript->handlerNote && + pChannel->pScript->handlerRelease && + pChannel->pScript->handlerNote->isPolyphonic() && + pChannel->pScript->handlerRelease->isPolyphonic()) + { + const int key = itScriptEvent->cause.Param.Note.Key; + itScriptEvent.moveToEndOf(pChannel->pScript->pKeyEvents[key & 127]); + } else { + // ... otherwise if no polyphonic data is passed and + // script's execution has finished without suspension + // status, then free the script event for a new future + // script event to be triggered from start + pChannel->pScript->pEvents->free(itScriptEvent); + } + } + } + + /** * Will be called by LaunchVoice() method in case there are no free * voices left. This method will select and kill one old voice for * voice stealing and postpone the note-on event until the selected * voice actually died. * - * @param pEngineChannel - engine channel on which this event occured on + * @param pEngineChannel - engine channel on which this event occurred on * @param itNoteOnEvent - key, velocity and time stamp of the event * @returns 0 on success, a value < 0 if no active voice could be picked for voice stealing */ @@ -755,9 +1073,28 @@ dmsg(5,("Engine: instrument change command received\n")); cmd.bChangeInstrument = false; pEngineChannel->pInstrument = cmd.pInstrument; + pEngineChannel->pScript = + cmd.pScript->bHasValidScript ? cmd.pScript : NULL; instrumentChanged = true; pEngineChannel->MarkAllActiveVoicesAsOrphans(); + + // the script's "init" event handler is only executed + // once (when the script is loaded or reloaded) + if (pEngineChannel->pScript && pEngineChannel->pScript->handlerInit) { + RTList::Iterator itScriptEvent = + pEngineChannel->pScript->pEvents->allocAppend(); + + itScriptEvent->cause.pEngineChannel = pEngineChannel; + itScriptEvent->handlers[0] = pEngineChannel->pScript->handlerInit; + itScriptEvent->handlers[1] = NULL; + + VMExecStatus_t res = pScriptVM->exec( + pEngineChannel->pScript->parserContext, &*itScriptEvent + ); + + pEngineChannel->pScript->pEvents->free(itScriptEvent); + } } } @@ -765,12 +1102,6 @@ //TODO: this is a lazy solution ATM and not safe in case somebody is currently editing the instrument we're currently switching to (we should store all suspended regions on instrument manager side and when switching to another instrument copy that list to the engine's local list of suspensions ResetSuspendedRegions(); } - - for (int i = 0; i < engineChannels.size(); i++) { - EngineChannelBase* channel = - static_cast*>(engineChannels[i]); - channel->InstrumentChangeCommandReader.Unlock(); - } } /** @@ -854,9 +1185,18 @@ pChannel->FreeAllInactiveKyes(); // empty the engine channel's own event lists - pChannel->ClearEventLists(); + // (only events of the current audio fragment cycle) + pChannel->ClearEventListsOfCurrentFragment(); } + /** + * Process MIDI control change events with hard coded behavior, + * that is controllers whose behavior is defined independently + * of the actual sampler engine type and instrument. + * + * @param pEngineChannel - engine channel on which the MIDI CC event was received + * @param itControlChangeEvent - the actual MIDI CC event + */ void ProcessHardcodedControllers ( EngineChannel* pEngineChannel, Pool::Iterator& itControlChangeEvent @@ -869,18 +1209,65 @@ 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 controllers) - 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); + 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; + 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; + break; + } + } + // to prevent other MIDI CC #6 messages to be misenterpreted as NRPN controller data + pChannel->ResetMidiNrpnController(); } - // to avoid other MIDI CC #6 messages to be misenterpreted as RPN controller data - pChannel->ResetMidiRpnController(); break; } case 7: { // volume @@ -891,8 +1278,6 @@ } case 10: { // panpot //TODO: not sample accurate yet - pChannel->GlobalPanLeft = PanCurve[128 - itControlChangeEvent->Param.CC.Value]; - pChannel->GlobalPanRight = PanCurve[itControlChangeEvent->Param.CC.Value]; pChannel->iLastPanRequest = itControlChangeEvent->Param.CC.Value; break; } @@ -969,11 +1354,23 @@ } break; } + case 98: { // NRPN controller LSB + dmsg(4,("NRPN LSB %d\n", itControlChangeEvent->Param.CC.Value)); + pEngineChannel->SetMidiNrpnControllerLsb(itControlChangeEvent->Param.CC.Value); + break; + } + case 99: { // NRPN controller MSB + dmsg(4,("NRPN MSB %d\n", itControlChangeEvent->Param.CC.Value)); + pEngineChannel->SetMidiNrpnControllerMsb(itControlChangeEvent->Param.CC.Value); + break; + } case 100: { // RPN controller LSB + dmsg(4,("RPN LSB %d\n", itControlChangeEvent->Param.CC.Value)); pEngineChannel->SetMidiRpnControllerLsb(itControlChangeEvent->Param.CC.Value); break; } case 101: { // RPN controller MSB + dmsg(4,("RPN MSB %d\n", itControlChangeEvent->Param.CC.Value)); pEngineChannel->SetMidiRpnControllerMsb(itControlChangeEvent->Param.CC.Value); break; } @@ -1015,7 +1412,7 @@ /** * Assigns and triggers a new voice for the respective MIDI key. * - * @param pEngineChannel - engine channel on which this event occured on + * @param pEngineChannel - engine channel on which this event occurred on * @param itNoteOnEvent - key, velocity and time stamp of the event */ virtual void ProcessNoteOn(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) { @@ -1064,7 +1461,7 @@ VoiceIterator itVoiceToBeKilled = pOtherKey->pActiveVoices->first(); VoiceIterator end = pOtherKey->pActiveVoices->end(); for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { - if (itVoiceToBeKilled->Type != Voice::type_release_trigger) + if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger)) itVoiceToBeKilled->Kill(itNoteOnEventOnKeyList); } } @@ -1076,6 +1473,7 @@ pChannel->ProcessKeySwitchChange(key); pKey->KeyPressed = true; // the MIDI key was now pressed down + pChannel->KeyDown[key] = true; // just used as built-in %KEY_DOWN script variable pKey->Velocity = itNoteOnEventOnKeyList->Param.Note.Velocity; pKey->NoteOnTime = FrameTime + itNoteOnEventOnKeyList->FragmentPos(); // will be used to calculate note length @@ -1096,7 +1494,10 @@ pKey->pEvents->free(itNoteOnEventOnKeyList); if (!pChannel->SoloMode || pChannel->PortamentoPos < 0.0f) pChannel->PortamentoPos = (float) key; - pKey->RoundRobinIndex++; + if (pKey->pRoundRobinIndex) { + (*pKey->pRoundRobinIndex)++; // counter specific for the key or region + pChannel->RoundRobinIndex++; // common counter for the channel + } pChannel->listeners.PostProcessNoteOn(key, vel); } @@ -1123,7 +1524,7 @@ * sustain pedal will be released or voice turned inactive by itself (e.g. * due to completion of sample playback). * - * @param pEngineChannel - engine channel on which this event occured on + * @param pEngineChannel - engine channel on which this event occurred on * @param itNoteOffEvent - key, velocity and time stamp of the event */ virtual void ProcessNoteOff(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOffEvent) { @@ -1149,6 +1550,7 @@ #endif pKey->KeyPressed = false; // the MIDI key was now released + pChannel->KeyDown[iKey] = false; // just used as built-in %KEY_DOWN script variable // move event to the key's own event list RTList::Iterator itNoteOffEventOnKeyList = itNoteOffEvent.moveToEndOf(pKey->pEvents); @@ -1198,7 +1600,7 @@ VoiceIterator itVoiceToBeKilled = pKey->pActiveVoices->first(); VoiceIterator end = pKey->pActiveVoices->end(); for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { - if (itVoiceToBeKilled->Type != Voice::type_release_trigger) + if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger)) itVoiceToBeKilled->Kill(itNoteOffEventOnKeyList); } } @@ -1228,7 +1630,7 @@ * control and status variables. This method is protected by a mutex. */ virtual void ResetInternal() { - ResetInternalMutex.Lock(); + LockGuard lock(ResetInternalMutex); // make sure that the engine does not get any sysex messages // while it's reseting @@ -1250,6 +1652,13 @@ } pVoicePool->clear(); + // reset all engine channels + for (int i = 0; i < engineChannels.size(); i++) { + AbstractEngineChannel* pEngineChannel = + static_cast(engineChannels[i]); + pEngineChannel->ResetInternal(false/*don't reset engine*/); + } + // reset disk thread if (pDiskThread) pDiskThread->Reset(); @@ -1257,7 +1666,6 @@ pEventQueue->init(); pSysexBuffer->init(); if (sysexDisabled) MidiInputPort::AddSysexListener(this); - ResetInternalMutex.Unlock(); } /** @@ -1279,7 +1687,7 @@ * called by the ProcessNoteOn() method and by the voices itself * (e.g. to spawn further voices on the same key for layered sounds). * - * @param pEngineChannel - engine channel on which this event occured on + * @param pEngineChannel - engine channel on which this event occurred on * @param itNoteOnEvent - key, velocity and time stamp of the event * @param iLayer - layer index for the new voice (optional - only * in case of layered sounds of course) @@ -1331,11 +1739,7 @@ pKey->itSelf = pChannel->pActiveKeys->allocAppend(); *pKey->itSelf = itNoteOnEvent->Param.Note.Key; } - if (itNewVoice->KeyGroup) { - uint** ppKeyGroup = &pChannel->ActiveKeyGroups[itNewVoice->KeyGroup]; - *ppKeyGroup = &*pKey->itSelf; // put key as the (new) active key to its key group - } - if (itNewVoice->Type == Voice::type_release_trigger_required) pKey->ReleaseTrigger = true; // mark key for the need of release triggered voice(s) + if (itNewVoice->Type & Voice::type_release_trigger_required) pKey->ReleaseTrigger = true; // mark key for the need of release triggered voice(s) return 0; // success } } @@ -1357,6 +1761,22 @@ return -1; } + + /** + * Checks whether scale tuning setting has been changed since last + * time this method was called, if yes, it recalculates the pitch + * for all active voices. + */ + void ProcessScaleTuningChange() { + const bool changed = ScaleTuningChanged.readAndReset(); + if (!changed) return; + + for (int i = 0; i < engineChannels.size(); i++) { + EngineChannelBase* channel = + static_cast*>(engineChannels[i]); + channel->OnScaleTuningChanged(); + } + } private: Pool* pVoicePool; ///< Contains all voices that can be activated.