--- linuxsampler/trunk/src/engines/EngineBase.h 2011/02/08 18:22:50 2162 +++ linuxsampler/trunk/src/engines/EngineBase.h 2014/06/18 00:14:57 2645 @@ -4,7 +4,7 @@ * * * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * * Copyright (C) 2005-2008 Christian Schoenebeck * - * Copyright (C) 2009-2011 Christian Schoenebeck and Grigor Iliev * + * Copyright (C) 2009-2013 Christian Schoenebeck and Grigor Iliev * * * * 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 +104,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 +143,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; @@ -205,9 +209,9 @@ 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"); @@ -243,14 +247,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"); @@ -265,9 +273,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. @@ -278,7 +286,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! @@ -308,7 +316,7 @@ // lower minimum release time const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate; for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { - iterVoice->pEG1->CalculateFadeOutCoeff(minReleaseTime, SampleRate); + iterVoice->CalculateFadeOutCoeff(minReleaseTime, SampleRate); } pVoicePool->clear(); } @@ -341,11 +349,19 @@ 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(); @@ -356,6 +372,13 @@ 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(); + } /** * Similar to @c Disable() but this method additionally kills all voices @@ -410,11 +433,12 @@ */ 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(); + { + LockGuard lock(SuspendedRegionsMutex); + SuspensionChangeOngoing.Set(true); + pPendingRegionSuspension = pRegion; + SuspensionChangeOngoing.WaitAndUnlockIf(true); + } dmsg(2,("EngineBase: Region %x suspended.",pRegion)); } @@ -426,11 +450,12 @@ */ 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(); + { + LockGuard lock(SuspendedRegionsMutex); + SuspensionChangeOngoing.Set(true); + pPendingRegionResumption = pRegion; + SuspensionChangeOngoing.WaitAndUnlockIf(true); + } dmsg(2,("EngineBase: Region %x resumed.\n",pRegion)); } @@ -553,12 +578,13 @@ 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; @@ -566,7 +592,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 @@ -605,7 +631,46 @@ 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 && pChannel->pScript->bHasValidScript) { + // resume any suspended script executions still hanging + // around of previous audio fragment cycles + for (RTList::Iterator itEvent = pChannel->pScript->pEvents->first(), + end = pChannel->pScript->pEvents->end(); itEvent != end; ++itEvent) + { + ResumeScriptEvent(pChannel, itEvent); //TODO: implement support for actual suspension time (i.e. passed to a script's wait() function call) + } + + // spawn new script executions for the new MIDI events of + // this audio fragment cycle + 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; + } + } + } + + // now process all events regularly { RTList::Iterator itEvent = pChannel->pEvents->first(); RTList::Iterator end = pChannel->pEvents->end(); @@ -623,6 +688,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); @@ -639,6 +712,140 @@ pLastStolenChannel = NULL; } + /** @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 occured + * @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 + ); + } + } + + 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 + ); + + // in case the script was suspended, keep it on the allocated + // ScriptEvent list to be continued on the next audio cycle + if (!(res & VM_EXEC_SUSPENDED)) { // 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 + ); + + // in case the script was suspended, keep it on the allocated + // ScriptEvent list to be continued on the next audio cycle + if (!(res & VM_EXEC_SUSPENDED)) { // 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 @@ -771,9 +978,27 @@ dmsg(5,("Engine: instrument change command received\n")); cmd.bChangeInstrument = false; pEngineChannel->pInstrument = cmd.pInstrument; + pEngineChannel->pScript = cmd.pScript; 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); + } } } @@ -956,8 +1181,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; } @@ -1153,6 +1376,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 @@ -1229,6 +1453,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); @@ -1308,7 +1533,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 @@ -1337,7 +1562,6 @@ pEventQueue->init(); pSysexBuffer->init(); if (sysexDisabled) MidiInputPort::AddSysexListener(this); - ResetInternalMutex.Unlock(); } /** @@ -1433,6 +1657,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.