--- linuxsampler/trunk/src/engines/EngineBase.h 2009/10/23 17:53:17 2012 +++ linuxsampler/trunk/src/engines/EngineBase.h 2013/02/02 18:52:15 2410 @@ -3,8 +3,8 @@ * 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 * * * * 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 * @@ -190,6 +190,15 @@ // 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(); @@ -234,8 +243,12 @@ } 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; } @@ -299,7 +312,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 +345,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() { + SuspendAll(); + if (pAudioOutputDevice) Connect(pAudioOutputDevice); + ResumeAll(); } /** @@ -530,6 +565,8 @@ //friend class EngineChannelBase; + static IM instruments; + protected: class SuspensionVoiceHandler : public MidiKeyboardManager::VoiceHandler { public: @@ -558,13 +595,10 @@ } }; - 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; - int VoiceSpawnsLeft; ///< We only allow CONFIG_MAX_VOICES voices to be spawned per audio fragment, we use this variable to ensure this limit. int ActiveVoiceCountTemp; ///< number of currently active voices (for internal usage, will be used for incrementation) VoiceIterator itLastStolenVoice; ///< Only for voice stealing: points to the last voice which was theft in current audio fragment, NULL otherwise. RTList::Iterator iuiLastStolenKey; ///< Only for voice stealing: key number of last key on which the last voice was theft in current audio fragment, NULL otherwise. @@ -766,12 +800,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(); - } } /** @@ -858,6 +886,14 @@ pChannel->ClearEventLists(); } + /** + * 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 @@ -870,18 +906,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=%d).\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=%d).\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 @@ -892,8 +975,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; } @@ -970,11 +1051,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; } @@ -1065,7 +1158,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); } } @@ -1097,7 +1190,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); } @@ -1199,7 +1295,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); } } @@ -1304,6 +1400,57 @@ bool HandleKeyGroupConflicts ) = 0; + virtual int GetMinFadeOutSamples() { return MinFadeOutSamples; } + + int InitNewVoice ( + EngineChannelBase* pChannel, + R* pRegion, + Pool::Iterator& itNoteOnEvent, + Voice::type_t VoiceType, + int iLayer, + int iKeyGroup, + bool ReleaseTriggerVoice, + bool VoiceStealing, + typename Pool::Iterator& itNewVoice + ) { + int key = itNoteOnEvent->Param.Note.Key; + typename MidiKeyboardManager::MidiKey* pKey = &pChannel->pMIDIKeyInfo[key]; + if (itNewVoice) { + // launch the new voice + if (itNewVoice->Trigger(pChannel, itNoteOnEvent, pChannel->Pitch, pRegion, VoiceType, iKeyGroup) < 0) { + dmsg(4,("Voice not triggered\n")); + pKey->pActiveVoices->free(itNewVoice); + } + 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) + return 0; // success + } + } + else if (VoiceStealing) { + // try to steal one voice + int result = StealVoice(pChannel, itNoteOnEvent); + if (!result) { // voice stolen successfully + // put note-on event into voice-stealing queue, so it will be reprocessed after killed voice died + RTList::Iterator itStealEvent = pVoiceStealingQueue->allocAppend(); + if (itStealEvent) { + *itStealEvent = *itNoteOnEvent; // copy event + itStealEvent->Param.Note.Layer = iLayer; + itStealEvent->Param.Note.ReleaseTrigger = ReleaseTriggerVoice; + pKey->VoiceTheftsQueued++; + } + else dmsg(1,("Voice stealing queue full!\n")); + } + } + + return -1; + } + private: Pool* pVoicePool; ///< Contains all voices that can be activated. Pool SuspendedRegions;