--- linuxsampler/trunk/src/engines/EngineBase.h 2010/09/14 17:09:08 2121 +++ linuxsampler/trunk/src/engines/EngineBase.h 2014/06/05 00:16:25 2594 @@ -4,7 +4,7 @@ * * * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * * Copyright (C) 2005-2008 Christian Schoenebeck * - * Copyright (C) 2009-2010 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; @@ -190,15 +194,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 +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"); @@ -256,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. @@ -269,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! @@ -299,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(); } @@ -332,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(); @@ -347,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 @@ -401,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)); } @@ -417,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)); } @@ -537,17 +571,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; @@ -555,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 @@ -565,8 +602,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; @@ -596,7 +631,45 @@ 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->script.bHasValidScript) { + // resume any suspended script executions still hanging + // around of previous audio fragment cycles + for (RTList::Iterator itEvent = pChannel->pScriptEvents->first(), + end = pChannel->pScriptEvents->end(); itEvent != end; ++itEvent) + { + ResumeScriptEvent(pChannel, itEvent); + } + + // 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->script.handlerNote) + ProcessEventByScript(pChannel, itEvent, pChannel->script.handlerNote); + break; + case Event::type_note_off: + //TODO: ... + break; + case Event::type_control_change: + case Event::type_channel_pressure: + case Event::type_pitchbend: + if (pChannel->script.handlerController) + ProcessEventByScript(pChannel, itEvent, pChannel->script.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(); @@ -614,6 +687,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); @@ -630,6 +711,77 @@ 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) { + RTList::Iterator itScriptEvent = + pChannel->pScriptEvents->allocAppend(); + + if (!itScriptEvent) return; // no free script event left for execution + + // fill the list of script handlers to be executed by this event + int i = 0; + if (pChannel->script.handlerInit) + itScriptEvent->handlers[i++] = pChannel->script.handlerInit; + 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->currentHandler = 0; + itScriptEvent->executionSlices = 0; + + // run script handler(s) + VMExecStatus_t res = pScriptVM->exec( + pChannel->script.parserContext, &*itScriptEvent + ); + + // in case the script was suspended, keep it on the allocated + // ScriptEvent list to be continued on the next audio cycle, + // otherwise if execution has been finished, free it for a new + // future script event to be triggered from start + if (!(res & VM_EXEC_SUSPENDED)) + pChannel->pScriptEvents->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) { + // run script + VMExecStatus_t res = pScriptVM->exec( + pChannel->script.parserContext, &*itScriptEvent + ); + // in case the script was again suspended, keep it on the allocated + // ScriptEvent list to be continued on the next audio cycle, + // otherwise if execution has been finished, free it for a new + // future script event to be triggered from start + if (!(res & VM_EXEC_SUSPENDED)) + pChannel->pScriptEvents->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 @@ -772,12 +924,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(); - } } /** @@ -953,8 +1099,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; } @@ -1305,7 +1449,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 @@ -1334,7 +1478,6 @@ pEventQueue->init(); pSysexBuffer->init(); if (sysexDisabled) MidiInputPort::AddSysexListener(this); - ResetInternalMutex.Unlock(); } /** @@ -1430,6 +1573,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.