--- linuxsampler/trunk/src/engines/gig/Engine.cpp 2005/03/17 20:13:08 473 +++ linuxsampler/trunk/src/engines/gig/Engine.cpp 2008/07/10 15:00:38 1750 @@ -2,8 +2,8 @@ * * * LinuxSampler - modular, streaming capable sampler * * * - * Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck * - * Copyright (C) 2005 Christian Schoenebeck * + * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * + * Copyright (C) 2005-2008 Christian Schoenebeck * * * * 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 * @@ -29,11 +29,7 @@ #include "Engine.h" -#if defined(__APPLE__) -# include -#else -# include -#endif +#include "../../common/global_private.h" namespace LinuxSampler { namespace gig { @@ -57,6 +53,11 @@ if (engines.count(pDevice)) { dmsg(4,("Using existing gig::Engine.\n")); pEngine = engines[pDevice]; + + // Disable the engine while the new engine channel is + // added and initialized. The engine will be enabled again + // in EngineChannel::Connect. + pEngine->DisableAndLock(); } else { // create a new engine (and disk thread) instance for the given audio output device dmsg(4,("Creating new gig::Engine.\n")); pEngine = (Engine*) EngineFactory::Create("gig"); @@ -73,7 +74,7 @@ /** * Once an engine channel is disconnected from an audio output device, - * it wil immediately call this method to unregister itself from the + * it will immediately call this method to unregister itself from the * engine instance and if that engine instance is not used by any other * engine channel anymore, then that engine instance will be destroyed. * @@ -99,32 +100,34 @@ /** * Constructor */ - Engine::Engine() { + Engine::Engine() : SuspendedRegions(128) { pAudioOutputDevice = NULL; pDiskThread = NULL; pEventGenerator = NULL; - pSysexBuffer = new RingBuffer(SYSEX_BUFFER_SIZE, 0); - pEventQueue = new RingBuffer(MAX_EVENTS_PER_FRAGMENT, 0); - pEventPool = new Pool(MAX_EVENTS_PER_FRAGMENT); - pVoicePool = new Pool(MAX_AUDIO_VOICES); + pSysexBuffer = new RingBuffer(CONFIG_SYSEX_BUFFER_SIZE, 0); + pEventQueue = new RingBuffer(CONFIG_MAX_EVENTS_PER_FRAGMENT, 0); + pEventPool = new Pool(CONFIG_MAX_EVENTS_PER_FRAGMENT); + pVoicePool = new Pool(CONFIG_MAX_VOICES); + pDimRegionPool[0] = new Pool< ::gig::DimensionRegion*>(CONFIG_MAX_VOICES); + pDimRegionPool[1] = new Pool< ::gig::DimensionRegion*>(CONFIG_MAX_VOICES); pVoiceStealingQueue = new RTList(pEventPool); pGlobalEvents = new RTList(pEventPool); + for (RTList::Iterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { iterVoice->SetEngine(this); } pVoicePool->clear(); - pSynthesisParameters[0] = NULL; // we allocate when an audio device is connected - pBasicFilterParameters = NULL; - pMainFilterParameters = NULL; - ResetInternal(); + ResetScaleTuning(); + ResetSuspendedRegions(); } /** * Destructor */ Engine::~Engine() { + MidiInputPort::RemoveSysexListener(this); if (pDiskThread) { dmsg(1,("Stopping disk thread...")); pDiskThread->StopThread(); @@ -138,12 +141,13 @@ delete pVoicePool; } if (pEventGenerator) delete pEventGenerator; - if (pMainFilterParameters) delete[] pMainFilterParameters; - if (pBasicFilterParameters) delete[] pBasicFilterParameters; - if (pSynthesisParameters[0]) free(pSynthesisParameters[0]); if (pVoiceStealingQueue) delete pVoiceStealingQueue; if (pSysexBuffer) delete pSysexBuffer; - EngineFactory::Destroy(this); + if (pGlobalEvents) delete pGlobalEvents; + if (pDimRegionPool[0]) delete pDimRegionPool[0]; + if (pDimRegionPool[1]) delete pDimRegionPool[1]; + ResetSuspendedRegions(); + Unregister(); } void Engine::Enable() { @@ -152,6 +156,18 @@ dmsg(3,("gig::Engine: enabled (val=%d)\n", EngineDisabled.GetUnsafe())); } + /** + * Temporarily stop the engine to not do anything. The engine will just be + * frozen during that time, that means after enabling it again it will + * continue where it was, with all its voices and playback state it had at + * the point of disabling. Notice that the engine's (audio) thread will + * continue to run, it just remains in an inactive loop during that time. + * + * If you need to be sure that all voices and disk streams are killed as + * well, use @c SuspendAll() instead. + * + * @see Enable(), SuspendAll() + */ void Engine::Disable() { dmsg(3,("gig::Engine: disabling\n")); bool* pWasDisabled = EngineDisabled.PushAndUnlock(true, 2); // wait max. 2s @@ -165,31 +181,126 @@ } /** + * Similar to @c Disable() but this method additionally kills all voices + * and disk streams and blocks until all voices and disk streams are actually + * killed / deleted. + * + * @e Note: only the original calling thread is able to re-enable the + * engine afterwards by calling @c ResumeAll() later on! + */ + void Engine::SuspendAll() { + dmsg(2,("gig::Engine: Suspending all ...\n")); + // stop the engine, so we can safely modify the engine's + // data structures from this foreign thread + DisableAndLock(); + // we could also use the respective class member variable here, + // but this is probably safer and cleaner + int iPendingStreamDeletions = 0; + // kill all voices on all engine channels the *die hard* way + for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) { + EngineChannel* pEngineChannel = engineChannels[iChannel]; + RTList::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); + RTList::Iterator end = pEngineChannel->pActiveKeys->end(); + for (; iuiKey != end; ++iuiKey) { // iterate through all active keys + midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; + RTList::Iterator itVoice = pKey->pActiveVoices->first(); + RTList::Iterator itVoicesEnd = pKey->pActiveVoices->end(); + for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key + // 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 + iPendingStreamDeletions++; + } + } + } + } + // wait until all streams were actually deleted by the disk thread + while (iPendingStreamDeletions) { + while ( + iPendingStreamDeletions && + pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE + ) iPendingStreamDeletions--; + if (!iPendingStreamDeletions) break; + usleep(10000); // sleep for 10ms + } + dmsg(2,("gig::Engine: Everything suspended.\n")); + } + + /** + * At the moment same as calling @c Enable() directly, but this might + * change in future, so better call this method as counterpart to + * @c SuspendAll() instead of @c Enable() ! + */ + void Engine::ResumeAll() { + Enable(); + } + + /** + * Order the engine to stop rendering audio for the given region. + * Additionally this method will block until all voices and their disk + * streams associated with that region are actually killed / deleted, so + * one can i.e. safely modify the region with an instrument editor after + * returning from this method. + * + * @param pRegion - region the engine shall stop using + */ + void Engine::Suspend(::gig::Region* pRegion) { + dmsg(2,("gig::Engine: Suspending Region %x ...\n",pRegion)); + SuspendedRegionsMutex.Lock(); + SuspensionChangeOngoing.Set(true); + pPendingRegionSuspension = pRegion; + SuspensionChangeOngoing.WaitAndUnlockIf(true); + SuspendedRegionsMutex.Unlock(); + dmsg(2,("gig::Engine: Region %x suspended.",pRegion)); + } + + /** + * Orders the engine to resume playing back the given region, previously + * suspended with @c Suspend() . + * + * @param pRegion - region the engine shall be allowed to use again + */ + void Engine::Resume(::gig::Region* pRegion) { + dmsg(2,("gig::Engine: Resuming Region %x ...\n",pRegion)); + SuspendedRegionsMutex.Lock(); + SuspensionChangeOngoing.Set(true); + pPendingRegionResumption = pRegion; + SuspensionChangeOngoing.WaitAndUnlockIf(true); + SuspendedRegionsMutex.Unlock(); + dmsg(2,("gig::Engine: Region %x resumed.\n",pRegion)); + } + + /** * Reset all voices and disk thread and clear input event queue and all * control and status variables. */ void Engine::Reset() { DisableAndLock(); ResetInternal(); + ResetScaleTuning(); Enable(); } /** * Reset all voices and disk thread and clear input event queue and all - * control and status variables. This method is not thread safe! + * control and status variables. This method is protected by a mutex. */ void Engine::ResetInternal() { + ResetInternalMutex.Lock(); + + // make sure that the engine does not get any sysex messages + // while it's reseting + bool sysexDisabled = MidiInputPort::RemoveSysexListener(this); ActiveVoiceCount = 0; ActiveVoiceCountMax = 0; // reset voice stealing parameters pVoiceStealingQueue->clear(); - itLastStolenVoice = RTList::Iterator(); - iuiLastStolenKey = RTList::Iterator(); - pLastStolenChannel = NULL; - - // reset to normal chromatic scale (means equal temper) - memset(&ScaleTuning[0], 0x00, 12); + itLastStolenVoice = RTList::Iterator(); + itLastStolenVoiceGlobally = RTList::Iterator(); + iuiLastStolenKey = RTList::Iterator(); + iuiLastStolenKeyGlobally = RTList::Iterator(); + pLastStolenChannel = NULL; // reset all voices for (RTList::Iterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { @@ -202,6 +313,23 @@ // delete all input events pEventQueue->init(); + pSysexBuffer->init(); + if (sysexDisabled) MidiInputPort::AddSysexListener(this); + ResetInternalMutex.Unlock(); + } + + /** + * Reset to normal, chromatic scale (means equal tempered). + */ + void Engine::ResetScaleTuning() { + memset(&ScaleTuning[0], 0x00, 12); + } + + void Engine::ResetSuspendedRegions() { + SuspendedRegions.clear(); + iPendingStreamDeletions = 0; + pPendingRegionSuspension = pPendingRegionResumption = NULL; + SuspensionChangeOngoing.Set(false); } /** @@ -224,16 +352,26 @@ } catch (AudioOutputException e) { String msg = "Audio output device unable to provide 2 audio channels, cause: " + e.Message(); - throw LinuxSamplerException(msg); + throw Exception(msg); } this->MaxSamplesPerCycle = pAudioOutputDevice->MaxSamplesPerCycle(); this->SampleRate = pAudioOutputDevice->SampleRate(); - // FIXME: audio drivers with varying fragment sizes might be a problem here - MaxFadeOutPos = MaxSamplesPerCycle - int(double(SampleRate) * EG_MIN_RELEASE_TIME) - 1; - if (MaxFadeOutPos < 0) - throw LinuxSamplerException("EG_MIN_RELEASE_TIME in EGADSR.h too big for current audio fragment size / sampling rate!"); + MinFadeOutSamples = int(double(SampleRate) * CONFIG_EG_MIN_RELEASE_TIME) - 1; + if (MaxSamplesPerCycle < MinFadeOutSamples) { + std::cerr << "gig::Engine: WARNING, CONFIG_EG_MIN_RELEASE_TIME " + << "too big for current audio fragment size & sampling rate! " + << "May lead to click sounds if voice stealing chimes in!\n" << std::flush; + // force volume ramp downs at the beginning of each fragment + MinFadeOutSamples = MaxSamplesPerCycle; + // lower minimum release time + const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate; + for (RTList::Iterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { + iterVoice->EG1.CalculateFadeOutCoeff(minReleaseTime, SampleRate); + } + pVoicePool->clear(); + } // (re)create disk thread if (this->pDiskThread) { @@ -242,7 +380,8 @@ delete this->pDiskThread; dmsg(1,("OK\n")); } - this->pDiskThread = new DiskThread(((pAudioOut->MaxSamplesPerCycle() << MAX_PITCH) << 1) + 6); //FIXME: assuming stereo + this->pDiskThread = new DiskThread(((pAudioOut->MaxSamplesPerCycle() << CONFIG_MAX_PITCH) << 1) + 6, //FIXME: assuming stereo + &instruments); if (!pDiskThread) { dmsg(0,("gig::Engine new diskthread = NULL\n")); exit(EXIT_FAILURE); @@ -258,23 +397,6 @@ if (pEventGenerator) delete pEventGenerator; pEventGenerator = new EventGenerator(pAudioOut->SampleRate()); - // (re)allocate synthesis parameter matrix - if (pSynthesisParameters[0]) free(pSynthesisParameters[0]); - - #if defined(__APPLE__) - pSynthesisParameters[0] = (float *) malloc(Event::destination_count * sizeof(float) * pAudioOut->MaxSamplesPerCycle()); - #else - pSynthesisParameters[0] = (float *) memalign(16,(Event::destination_count * sizeof(float) * pAudioOut->MaxSamplesPerCycle())); - #endif - for (int dst = 1; dst < Event::destination_count; dst++) - pSynthesisParameters[dst] = pSynthesisParameters[dst - 1] + pAudioOut->MaxSamplesPerCycle(); - - // (re)allocate biquad filter parameter sequence - if (pBasicFilterParameters) delete[] pBasicFilterParameters; - if (pMainFilterParameters) delete[] pMainFilterParameters; - pBasicFilterParameters = new biquad_param_t[pAudioOut->MaxSamplesPerCycle()]; - pMainFilterParameters = new biquad_param_t[pAudioOut->MaxSamplesPerCycle()]; - dmsg(1,("Starting disk thread...")); pDiskThread->StartThread(); dmsg(1,("OK\n")); @@ -288,6 +410,110 @@ } /** + * Called by the engine's (audio) thread once per cycle to process requests + * from the outer world to suspend or resume a given @c gig::Region . + */ + void Engine::ProcessSuspensionsChanges() { + // process request for suspending one region + if (pPendingRegionSuspension) { + // kill all voices on all engine channels that use this region + for (int iChannel = 0; iChannel < engineChannels.size(); iChannel++) { + EngineChannel* pEngineChannel = engineChannels[iChannel]; + RTList::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); + RTList::Iterator end = pEngineChannel->pActiveKeys->end(); + for (; iuiKey != end; ++iuiKey) { // iterate through all active keys + midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; + RTList::Iterator itVoice = pKey->pActiveVoices->first(); + // if current key is not associated with this region, skip this key + if (itVoice->pDimRgn->GetParent() != pPendingRegionSuspension) continue; + RTList::Iterator itVoicesEnd = pKey->pActiveVoices->end(); + for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key + // 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 + iPendingStreamDeletions++; + } + } + } + } + // make sure the region is not yet on the list + bool bAlreadySuspended = false; + RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.first(); + RTList< ::gig::Region*>::Iterator end = SuspendedRegions.end(); + for (; iter != end; ++iter) { // iterate through all suspended regions + if (*iter == pPendingRegionSuspension) { // found + bAlreadySuspended = true; + dmsg(1,("gig::Engine: attempt to suspend an already suspended region !!!\n")); + break; + } + } + if (!bAlreadySuspended) { + // put the region on the list of suspended regions + RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.allocAppend(); + if (iter) { + *iter = pPendingRegionSuspension; + } else std::cerr << "gig::Engine: Could not suspend Region, list is full. This is a bug!!!\n" << std::flush; + } + // free request slot for next caller (and to make sure that + // we're not going to process the same request in the next cycle) + pPendingRegionSuspension = NULL; + // if no disk stream deletions are pending, awaken other side, as + // we're done in this case + if (!iPendingStreamDeletions) SuspensionChangeOngoing.Set(false); + } + + // process request for resuming one region + if (pPendingRegionResumption) { + // remove region from the list of suspended regions + RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.first(); + RTList< ::gig::Region*>::Iterator end = SuspendedRegions.end(); + for (; iter != end; ++iter) { // iterate through all suspended regions + if (*iter == pPendingRegionResumption) { // found + SuspendedRegions.free(iter); + break; // done + } + } + // free request slot for next caller + pPendingRegionResumption = NULL; + // awake other side as we're done + SuspensionChangeOngoing.Set(false); + } + } + + /** + * Called by the engine's (audio) thread once per cycle to check if + * streams of voices that were killed due to suspension request have + * finally really been deleted by the disk thread. + */ + void Engine::ProcessPendingStreamDeletions() { + if (!iPendingStreamDeletions) return; + //TODO: or shall we better store a list with stream handles instead of a scalar amount of streams to be deleted? might be safer + while ( + iPendingStreamDeletions && + pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE + ) iPendingStreamDeletions--; + // just for safety ... + while (pDiskThread->AskForDeletedStream() != Stream::INVALID_HANDLE); + // now that all disk streams are deleted, awake other side as + // we're finally done with suspending the requested region + if (!iPendingStreamDeletions) SuspensionChangeOngoing.Set(false); + } + + /** + * Returns @c true if the given region is currently set to be suspended + * from being used, @c false otherwise. + */ + bool Engine::RegionSuspended(::gig::Region* pRegion) { + if (SuspendedRegions.isEmpty()) return false; + //TODO: or shall we use a sorted container instead of the RTList? might be faster ... or trivial ;-) + RTList< ::gig::Region*>::Iterator iter = SuspendedRegions.first(); + RTList< ::gig::Region*>::Iterator end = SuspendedRegions.end(); + for (; iter != end; ++iter) // iterate through all suspended regions + if (*iter == pRegion) return true; + return false; + } + + /** * Clear all engine global event lists. */ void Engine::ClearEventLists() { @@ -308,7 +534,7 @@ * current audio cycle */ void Engine::ImportEvents(uint Samples) { - RingBuffer::NonVolatileReader eventQueueReader = pEventQueue->get_non_volatile_reader(); + RingBuffer::NonVolatileReader eventQueueReader = pEventQueue->get_non_volatile_reader(); Event* pEvent; while (true) { // get next event from input event queue @@ -331,17 +557,18 @@ } /** - * Let this engine proceed to render the given amount of sample points. The - * calculated audio data of all voices of this engine will be placed into - * the engine's audio sum buffer which has to be copied and eventually be - * converted to the appropriate value range by the audio output class (e.g. - * AlsaIO or JackIO) right after. + * Let this engine proceed to render the given amount of sample points. + * The engine will iterate through all engine channels and render audio + * for each engine channel independently. The calculated audio data of + * all voices of each engine channel will be placed into the audio sum + * buffers of the respective audio output device, connected to the + * respective engine channel. * * @param Samples - number of sample points to be rendered * @returns 0 on success */ int Engine::RenderAudio(uint Samples) { - dmsg(5,("RenderAudio(Samples=%d)\n", Samples)); + dmsg(8,("RenderAudio(Samples=%d)\n", Samples)); // return if engine disabled if (EngineDisabled.Pop()) { @@ -349,9 +576,18 @@ return 0; } + // process requests for suspending / resuming regions (i.e. to avoid + // crashes while these regions are modified by an instrument editor) + ProcessSuspensionsChanges(); + // update time of start and end of this audio fragment (as events' time stamps relate to this) pEventGenerator->UpdateFragmentTime(Samples); + // We only allow a maximum of CONFIG_MAX_VOICES voices to be spawned + // in each audio fragment. All subsequent request for spawning new + // voices in the same audio fragment will be ignored. + VoiceSpawnsLeft = CONFIG_MAX_VOICES; + // get all events from the engine's global input event queue which belong to the current fragment // (these are usually just SysEx messages) ImportEvents(Samples); @@ -370,33 +606,74 @@ } } - // We only allow a maximum of MAX_AUDIO_VOICES voices to be stolen - // in each audio fragment. All subsequent request for spawning new - // voices in the same audio fragment will be ignored. - VoiceTheftsLeft = MAX_AUDIO_VOICES; - // reset internal voice counter (just for statistic of active voices) ActiveVoiceCountTemp = 0; + // handle instrument change commands + bool instrumentChanged = false; + for (int i = 0; i < engineChannels.size(); i++) { + EngineChannel* pEngineChannel = engineChannels[i]; + + // as we're going to (carefully) write some status to the + // synchronized struct, we cast away the const + EngineChannel::instrument_change_command_t& cmd = + const_cast(pEngineChannel->InstrumentChangeCommandReader.Lock()); + + pEngineChannel->pDimRegionsInUse = cmd.pDimRegionsInUse; + pEngineChannel->pDimRegionsInUse->clear(); + + if (cmd.bChangeInstrument) { + // change instrument + dmsg(5,("Engine: instrument change command received\n")); + cmd.bChangeInstrument = false; + pEngineChannel->pInstrument = cmd.pInstrument; + instrumentChanged = true; + + // Iterate through all active voices and mark them as + // "orphans", which means that the dimension regions + // and samples they use should be released to the + // instrument resource manager when the voices die. + int i = 0; + RTList::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); + RTList::Iterator end = pEngineChannel->pActiveKeys->end(); + while (iuiKey != end) { // iterate through all active keys + midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; + ++iuiKey; + + RTList::Iterator itVoice = pKey->pActiveVoices->first(); + RTList::Iterator itVoicesEnd = pKey->pActiveVoices->end(); + for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key + itVoice->Orphan = true; + } + } + } + } + if (instrumentChanged) { + //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(); + } // handle events on all engine channels for (int i = 0; i < engineChannels.size(); i++) { - if (!engineChannels[i]->pInstrument) continue; // ignore if no instrument loaded ProcessEvents(engineChannels[i], Samples); } // render all 'normal', active voices on all engine channels for (int i = 0; i < engineChannels.size(); i++) { - if (!engineChannels[i]->pInstrument) continue; // ignore if no instrument loaded RenderActiveVoices(engineChannels[i], Samples); } // now that all ordinary voices on ALL engine channels are rendered, render new stolen voices RenderStolenVoices(Samples); + // handle audio routing for engine channels with FX sends + for (int i = 0; i < engineChannels.size(); i++) { + if (engineChannels[i]->fxSends.empty()) continue; // ignore if no FX sends + RouteAudio(engineChannels[i], Samples); + } + // handle cleanup on all engine channels for the next audio fragment for (int i = 0; i < engineChannels.size(); i++) { - if (!engineChannels[i]->pInstrument) continue; // ignore if no instrument loaded PostProcess(engineChannels[i]); } @@ -406,14 +683,21 @@ // reset voice stealing for the next audio fragment pVoiceStealingQueue->clear(); - itLastStolenVoice = RTList::Iterator(); - iuiLastStolenKey = RTList::Iterator(); - pLastStolenChannel = NULL; // just some statistics about this engine instance ActiveVoiceCount = ActiveVoiceCountTemp; if (ActiveVoiceCount > ActiveVoiceCountMax) ActiveVoiceCountMax = ActiveVoiceCount; + // in case regions were previously suspended and we killed voices + // with disk streams due to that, check if those streams have finally + // been deleted by the disk thread + if (iPendingStreamDeletions) ProcessPendingStreamDeletions(); + + for (int i = 0; i < engineChannels.size(); i++) { + engineChannels[i]->InstrumentChangeCommandReader.Unlock(); + } + FrameTime += Samples; + return 0; } @@ -456,6 +740,13 @@ } } } + + // reset voice stealing for the next engine channel (or next audio fragment) + itLastStolenVoice = RTList::Iterator(); + itLastStolenVoiceGlobally = RTList::Iterator(); + iuiLastStolenKey = RTList::Iterator(); + iuiLastStolenKeyGlobally = RTList::Iterator(); + pLastStolenChannel = NULL; } /** @@ -468,6 +759,12 @@ * this audio fragment cycle */ void Engine::RenderActiveVoices(EngineChannel* pEngineChannel, uint Samples) { + #if !CONFIG_PROCESS_MUTED_CHANNELS + if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted + #endif + + uint voiceCount = 0; + uint streamCount = 0; RTList::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); RTList::Iterator end = pEngineChannel->pActiveKeys->end(); while (iuiKey != end) { // iterate through all active keys @@ -479,12 +776,24 @@ for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key // now render current voice itVoice->Render(Samples); - if (itVoice->IsActive()) ActiveVoiceCountTemp++; // still active - else { // voice reached end, is now inactive + if (itVoice->IsActive()) { // still active + if (!itVoice->Orphan) { + *(pEngineChannel->pDimRegionsInUse->allocAppend()) = itVoice->pDimRgn; + } + ActiveVoiceCountTemp++; + voiceCount++; + + if (itVoice->PlaybackState == Voice::playback_state_disk) { + if ((itVoice->DiskStreamRef).State == Stream::state_active) streamCount++; + } + } else { // voice reached end, is now inactive FreeVoice(pEngineChannel, itVoice); // remove voice from the list of active voices } } } + + pEngineChannel->SetVoiceCount(voiceCount); + pEngineChannel->SetDiskStreamCount(streamCount); } /** @@ -505,12 +814,22 @@ RTList::Iterator end = pVoiceStealingQueue->end(); for (; itVoiceStealEvent != end; ++itVoiceStealEvent) { EngineChannel* pEngineChannel = (EngineChannel*) itVoiceStealEvent->pEngineChannel; + if (!pEngineChannel->pInstrument) continue; // ignore if no instrument loaded Pool::Iterator itNewVoice = - LaunchVoice(pEngineChannel, itVoiceStealEvent, itVoiceStealEvent->Param.Note.Layer, itVoiceStealEvent->Param.Note.ReleaseTrigger, false); + LaunchVoice(pEngineChannel, itVoiceStealEvent, itVoiceStealEvent->Param.Note.Layer, itVoiceStealEvent->Param.Note.ReleaseTrigger, false, false); if (itNewVoice) { itNewVoice->Render(Samples); - if (itNewVoice->IsActive()) ActiveVoiceCountTemp++; // still active - else { // voice reached end, is now inactive + if (itNewVoice->IsActive()) { // still active + *(pEngineChannel->pDimRegionsInUse->allocAppend()) = itNewVoice->pDimRgn; + ActiveVoiceCountTemp++; + pEngineChannel->SetVoiceCount(pEngineChannel->GetVoiceCount() + 1); + + if (itNewVoice->PlaybackState == Voice::playback_state_disk) { + if (itNewVoice->DiskStreamRef.State == Stream::state_active) { + pEngineChannel->SetDiskStreamCount(pEngineChannel->GetDiskStreamCount() + 1); + } + } + } else { // voice reached end, is now inactive FreeVoice(pEngineChannel, itNewVoice); // remove voice from the list of active voices } } @@ -524,6 +843,77 @@ } /** + * Will be called in case the respective engine channel sports FX send + * channels. In this particular case, engine channel local buffers are + * used to render and mix all voices to. This method is responsible for + * copying the audio data from those local buffers to the master audio + * output channels as well as to the FX send audio output channels with + * their respective FX send levels. + * + * @param pEngineChannel - engine channel from which audio should be + * routed + * @param Samples - amount of sample points to be routed in + * this audio fragment cycle + */ + void Engine::RouteAudio(EngineChannel* pEngineChannel, uint Samples) { + // route dry signal + { + AudioChannel* pDstL = pAudioOutputDevice->Channel(pEngineChannel->AudioDeviceChannelLeft); + AudioChannel* pDstR = pAudioOutputDevice->Channel(pEngineChannel->AudioDeviceChannelRight); + pEngineChannel->pChannelLeft->MixTo(pDstL, Samples); + pEngineChannel->pChannelRight->MixTo(pDstR, Samples); + } + // route FX send signal + { + for (int iFxSend = 0; iFxSend < pEngineChannel->GetFxSendCount(); iFxSend++) { + FxSend* pFxSend = pEngineChannel->GetFxSend(iFxSend); + for (int iChan = 0; iChan < 2; ++iChan) { + AudioChannel* pSource = + (iChan) + ? pEngineChannel->pChannelRight + : pEngineChannel->pChannelLeft; + const int iDstChan = pFxSend->DestinationChannel(iChan); + if (iDstChan < 0) { + dmsg(1,("Engine::RouteAudio() Error: invalid FX send (%s) destination channel (%d->%d)", ((iChan) ? "R" : "L"), iChan, iDstChan)); + goto channel_cleanup; + } + AudioChannel* pDstChan = NULL; + if (pFxSend->DestinationMasterEffectChain() >= 0) { // fx send routed to an internal master effect + EffectChain* pEffectChain = + pAudioOutputDevice->MasterEffectChain( + pFxSend->DestinationMasterEffectChain() + ); + if (!pEffectChain) { + dmsg(1,("Engine::RouteAudio() Error: invalid FX send (%s) destination effect chain %d", ((iChan) ? "R" : "L"), pFxSend->DestinationMasterEffectChain())); + goto channel_cleanup; + } + Effect* pEffect = + pEffectChain->GetEffect( + pFxSend->DestinationMasterEffect() + ); + if (!pEffect) { + dmsg(1,("Engine::RouteAudio() Error: invalid FX send (%s) destination effect %d of effect chain %d", ((iChan) ? "R" : "L"), pFxSend->DestinationMasterEffect(), pFxSend->DestinationMasterEffectChain())); + goto channel_cleanup; + } + pDstChan = pEffect->InputChannel(iDstChan); + } else { // FX send routed directly to an audio output channel + pDstChan = pAudioOutputDevice->Channel(iDstChan); + } + if (!pDstChan) { + dmsg(1,("Engine::RouteAudio() Error: invalid FX send (%s) destination channel (%d->%d)", ((iChan) ? "R" : "L"), iChan, iDstChan)); + goto channel_cleanup; + } + pSource->MixTo(pDstChan, Samples, pFxSend->Level()); + } + } + } + channel_cleanup: + // reset buffers with silence (zero out) for the next audio cycle + pEngineChannel->pChannelLeft->Clear(); + pEngineChannel->pChannelRight->Clear(); + } + + /** * Free all keys which have turned inactive in this audio fragment, from * the list of active keys and clear all event lists on that engine * channel. @@ -539,8 +929,8 @@ midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; ++iuiKey; if (pKey->pActiveVoices->isEmpty()) FreeKey(pEngineChannel, pKey); - #if DEVMODE - else { // FIXME: should be removed before the final release (purpose: just a sanity check for debugging) + #if CONFIG_DEVMODE + else { // just a sanity check for debugging RTList::Iterator itVoice = pKey->pActiveVoices->first(); RTList::Iterator itVoicesEnd = pKey->pActiveVoices->end(); for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key @@ -549,7 +939,7 @@ } } } - #endif // DEVMODE + #endif // CONFIG_DEVMODE } } @@ -584,7 +974,7 @@ // finally place sysex event into input event queue pEventQueue->push(&event); } - else dmsg(1,("Engine: Sysex message too large (%d byte) for input buffer (%d byte)!",Size,SYSEX_BUFFER_SIZE)); + else dmsg(1,("Engine: Sysex message too large (%d byte) for input buffer (%d byte)!",Size,CONFIG_SYSEX_BUFFER_SIZE)); } else dmsg(1,("Engine: Input event queue full!")); } @@ -596,43 +986,77 @@ * @param itNoteOnEvent - key, velocity and time stamp of the event */ void Engine::ProcessNoteOn(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) { + #if !CONFIG_PROCESS_MUTED_CHANNELS + if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted + #endif + + if (!pEngineChannel->pInstrument) return; // ignore if no instrument loaded + + //HACK: we should better add the transpose value only to the most mandatory places (like for retrieving the region and calculating the tuning), because otherwise voices will unintendedly survive when changing transpose while playing + itNoteOnEvent->Param.Note.Key += pEngineChannel->GlobalTranspose; const int key = itNoteOnEvent->Param.Note.Key; + midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[key]; + + // move note on event to the key's own event list + RTList::Iterator itNoteOnEventOnKeyList = itNoteOnEvent.moveToEndOf(pKey->pEvents); + + // if Solo Mode then kill all already active voices + if (pEngineChannel->SoloMode) { + Pool::Iterator itYoungestKey = pEngineChannel->pActiveKeys->last(); + if (itYoungestKey) { + const int iYoungestKey = *itYoungestKey; + const midi_key_info_t* pOtherKey = &pEngineChannel->pMIDIKeyInfo[iYoungestKey]; + if (pOtherKey->Active) { + // get final portamento position of currently active voice + if (pEngineChannel->PortamentoMode) { + RTList::Iterator itVoice = pOtherKey->pActiveVoices->last(); + if (itVoice) itVoice->UpdatePortamentoPos(itNoteOnEventOnKeyList); + } + // kill all voices on the (other) key + RTList::Iterator itVoiceToBeKilled = pOtherKey->pActiveVoices->first(); + RTList::Iterator end = pOtherKey->pActiveVoices->end(); + for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { + if (itVoiceToBeKilled->Type != Voice::type_release_trigger) + itVoiceToBeKilled->Kill(itNoteOnEventOnKeyList); + } + } + } + // set this key as 'currently active solo key' + pEngineChannel->SoloKey = key; + } // Change key dimension value if key is in keyswitching area { const ::gig::Instrument* pInstrument = pEngineChannel->pInstrument; if (key >= pInstrument->DimensionKeyRange.low && key <= pInstrument->DimensionKeyRange.high) - pEngineChannel->CurrentKeyDimension = ((key - pInstrument->DimensionKeyRange.low) * 128) / + pEngineChannel->CurrentKeyDimension = float(key - pInstrument->DimensionKeyRange.low) / (pInstrument->DimensionKeyRange.high - pInstrument->DimensionKeyRange.low + 1); } - midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[key]; - pKey->KeyPressed = true; // the MIDI key was now pressed down + pKey->Velocity = itNoteOnEventOnKeyList->Param.Note.Velocity; + pKey->NoteOnTime = FrameTime + itNoteOnEventOnKeyList->FragmentPos(); // will be used to calculate note length // cancel release process of voices on this key if needed if (pKey->Active && !pEngineChannel->SustainPedal) { RTList::Iterator itCancelReleaseEvent = pKey->pEvents->allocAppend(); if (itCancelReleaseEvent) { - *itCancelReleaseEvent = *itNoteOnEvent; // copy event + *itCancelReleaseEvent = *itNoteOnEventOnKeyList; // copy event itCancelReleaseEvent->Type = Event::type_cancel_release; // transform event type } else dmsg(1,("Event pool emtpy!\n")); } - // move note on event to the key's own event list - RTList::Iterator itNoteOnEventOnKeyList = itNoteOnEvent.moveToEndOf(pKey->pEvents); - // allocate and trigger new voice(s) for the key { // first, get total amount of required voices (dependant on amount of layers) ::gig::Region* pRegion = pEngineChannel->pInstrument->GetRegion(itNoteOnEventOnKeyList->Param.Note.Key); - if (pRegion) { + if (pRegion && !RegionSuspended(pRegion)) { int voicesRequired = pRegion->Layers; // now launch the required amount of voices for (int i = 0; i < voicesRequired; i++) - LaunchVoice(pEngineChannel, itNoteOnEventOnKeyList, i, false, true); + LaunchVoice(pEngineChannel, itNoteOnEventOnKeyList, i, false, true, true); } } @@ -640,6 +1064,7 @@ if (!pKey->Active && !pKey->VoiceTheftsQueued) pKey->pEvents->free(itNoteOnEventOnKeyList); + if (!pEngineChannel->SoloMode || pEngineChannel->PortamentoPos < 0.0f) pEngineChannel->PortamentoPos = (float) key; pKey->RoundRobinIndex++; } @@ -653,46 +1078,118 @@ * @param itNoteOffEvent - key, velocity and time stamp of the event */ void Engine::ProcessNoteOff(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOffEvent) { - midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[itNoteOffEvent->Param.Note.Key]; + #if !CONFIG_PROCESS_MUTED_CHANNELS + if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted + #endif - pKey->KeyPressed = false; // the MIDI key was now released + //HACK: we should better add the transpose value only to the most mandatory places (like for retrieving the region and calculating the tuning), because otherwise voices will unintendedly survive when changing transpose while playing + itNoteOffEvent->Param.Note.Key += pEngineChannel->GlobalTranspose; - // release voices on this key if needed - if (pKey->Active && !pEngineChannel->SustainPedal) { - itNoteOffEvent->Type = Event::type_release; // transform event type - } + const int iKey = itNoteOffEvent->Param.Note.Key; + midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[iKey]; + pKey->KeyPressed = false; // the MIDI key was now released // move event to the key's own event list RTList::Iterator itNoteOffEventOnKeyList = itNoteOffEvent.moveToEndOf(pKey->pEvents); - // spawn release triggered voice(s) if needed - if (pKey->ReleaseTrigger) { - // first, get total amount of required voices (dependant on amount of layers) - ::gig::Region* pRegion = pEngineChannel->pInstrument->GetRegion(itNoteOffEventOnKeyList->Param.Note.Key); - if (pRegion) { - int voicesRequired = pRegion->Layers; - // now launch the required amount of voices - for (int i = 0; i < voicesRequired; i++) - LaunchVoice(pEngineChannel, itNoteOffEventOnKeyList, i, true, false); //FIXME: for the moment we don't perform voice stealing for release triggered samples + bool bShouldRelease = pKey->Active && ShouldReleaseVoice(pEngineChannel, itNoteOffEventOnKeyList->Param.Note.Key); + + // in case Solo Mode is enabled, kill all voices on this key and respawn a voice on the highest pressed key (if any) + if (pEngineChannel->SoloMode && pEngineChannel->pInstrument) { //TODO: this feels like too much code just for handling solo mode :P + bool bOtherKeysPressed = false; + if (iKey == pEngineChannel->SoloKey) { + pEngineChannel->SoloKey = -1; + // if there's still a key pressed down, respawn a voice (group) on the highest key + for (int i = 127; i > 0; i--) { + midi_key_info_t* pOtherKey = &pEngineChannel->pMIDIKeyInfo[i]; + if (pOtherKey->KeyPressed) { + bOtherKeysPressed = true; + // make the other key the new 'currently active solo key' + pEngineChannel->SoloKey = i; + // get final portamento position of currently active voice + if (pEngineChannel->PortamentoMode) { + RTList::Iterator itVoice = pKey->pActiveVoices->first(); + if (itVoice) itVoice->UpdatePortamentoPos(itNoteOffEventOnKeyList); + } + // create a pseudo note on event + RTList::Iterator itPseudoNoteOnEvent = pOtherKey->pEvents->allocAppend(); + if (itPseudoNoteOnEvent) { + // copy event + *itPseudoNoteOnEvent = *itNoteOffEventOnKeyList; + // transform event to a note on event + itPseudoNoteOnEvent->Type = Event::type_note_on; + itPseudoNoteOnEvent->Param.Note.Key = i; + itPseudoNoteOnEvent->Param.Note.Velocity = pOtherKey->Velocity; + // allocate and trigger new voice(s) for the other key + { + // first, get total amount of required voices (dependant on amount of layers) + ::gig::Region* pRegion = pEngineChannel->pInstrument->GetRegion(i); + if (pRegion) { + int voicesRequired = pRegion->Layers; + // now launch the required amount of voices + for (int iLayer = 0; iLayer < voicesRequired; iLayer++) + LaunchVoice(pEngineChannel, itPseudoNoteOnEvent, iLayer, false, true, false); + } + } + // if neither a voice was spawned or postponed then remove note on event from key again + if (!pOtherKey->Active && !pOtherKey->VoiceTheftsQueued) + pOtherKey->pEvents->free(itPseudoNoteOnEvent); + + } else dmsg(1,("Could not respawn voice, no free event left\n")); + break; // done + } + } } - pKey->ReleaseTrigger = false; + if (bOtherKeysPressed) { + if (pKey->Active) { // kill all voices on this key + bShouldRelease = false; // no need to release, as we kill it here + RTList::Iterator itVoiceToBeKilled = pKey->pActiveVoices->first(); + RTList::Iterator end = pKey->pActiveVoices->end(); + for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { + if (itVoiceToBeKilled->Type != Voice::type_release_trigger) + itVoiceToBeKilled->Kill(itNoteOffEventOnKeyList); + } + } + } else pEngineChannel->PortamentoPos = -1.0f; } - // if neither a voice was spawned or postponed then remove note off event from key again + // if no solo mode (the usual case) or if solo mode and no other key pressed, then release voices on this key if needed + if (bShouldRelease) { + itNoteOffEventOnKeyList->Type = Event::type_release; // transform event type + + // spawn release triggered voice(s) if needed + if (pKey->ReleaseTrigger && pEngineChannel->pInstrument) { + // first, get total amount of required voices (dependant on amount of layers) + ::gig::Region* pRegion = pEngineChannel->pInstrument->GetRegion(itNoteOffEventOnKeyList->Param.Note.Key); + if (pRegion) { + int voicesRequired = pRegion->Layers; + + // MIDI note-on velocity is used instead of note-off velocity + itNoteOffEventOnKeyList->Param.Note.Velocity = pKey->Velocity; + + // now launch the required amount of voices + for (int i = 0; i < voicesRequired; i++) + LaunchVoice(pEngineChannel, itNoteOffEventOnKeyList, i, true, false, false); //FIXME: for the moment we don't perform voice stealing for release triggered samples + } + pKey->ReleaseTrigger = false; + } + } + + // if neither a voice was spawned or postponed on this key then remove note off event from key again if (!pKey->Active && !pKey->VoiceTheftsQueued) pKey->pEvents->free(itNoteOffEventOnKeyList); } /** - * Moves pitchbend event from the general (input) event list to the pitch - * event list. + * Moves pitchbend event from the general (input) event list to the engine + * channel's event list. It will actually processed later by the + * respective voice. * * @param pEngineChannel - engine channel on which this event occured on * @param itPitchbendEvent - absolute pitch value and time stamp of the event */ void Engine::ProcessPitchbend(EngineChannel* pEngineChannel, Pool::Iterator& itPitchbendEvent) { pEngineChannel->Pitch = itPitchbendEvent->Param.Pitch.Pitch; // store current pitch value - itPitchbendEvent.moveToEndOf(pEngineChannel->pSynthesisEvents[Event::destination_vco]); } /** @@ -709,41 +1206,181 @@ * @param VoiceStealing - if voice stealing should be performed * when there is no free voice * (optional, default = true) + * @param HandleKeyGroupConflicts - if voices should be killed due to a + * key group conflict * @returns pointer to new voice or NULL if there was no free voice or * if the voice wasn't triggered (for example when no region is * defined for the given key). */ - Pool::Iterator Engine::LaunchVoice(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent, int iLayer, bool ReleaseTriggerVoice, bool VoiceStealing) { - midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[itNoteOnEvent->Param.Note.Key]; + Pool::Iterator Engine::LaunchVoice(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent, int iLayer, bool ReleaseTriggerVoice, bool VoiceStealing, bool HandleKeyGroupConflicts) { + int MIDIKey = itNoteOnEvent->Param.Note.Key; + midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[MIDIKey]; + ::gig::Region* pRegion = pEngineChannel->pInstrument->GetRegion(MIDIKey); + + // if nothing defined for this key + if (!pRegion) return Pool::Iterator(); // nothing to do + + // only mark the first voice of a layered voice (group) to be in a + // key group, so the layered voices won't kill each other + int iKeyGroup = (iLayer == 0 && !ReleaseTriggerVoice) ? pRegion->KeyGroup : 0; + + // handle key group (a.k.a. exclusive group) conflicts + if (HandleKeyGroupConflicts) { + if (iKeyGroup) { // if this voice / key belongs to a key group + uint** ppKeyGroup = &pEngineChannel->ActiveKeyGroups[iKeyGroup]; + if (*ppKeyGroup) { // if there's already an active key in that key group + midi_key_info_t* pOtherKey = &pEngineChannel->pMIDIKeyInfo[**ppKeyGroup]; + // kill all voices on the (other) key + RTList::Iterator itVoiceToBeKilled = pOtherKey->pActiveVoices->first(); + RTList::Iterator end = pOtherKey->pActiveVoices->end(); + for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { + if (itVoiceToBeKilled->Type != Voice::type_release_trigger) { + itVoiceToBeKilled->Kill(itNoteOnEvent); + --VoiceSpawnsLeft; //FIXME: just a hack, we should better check in StealVoice() if the voice was killed due to key conflict + } + } + } + } + } + + Voice::type_t VoiceType = Voice::type_normal; + + // get current dimension values to select the right dimension region + //TODO: for stolen voices this dimension region selection block is processed twice, this should be changed + //FIXME: controller values for selecting the dimension region here are currently not sample accurate + uint DimValues[8] = { 0 }; + for (int i = pRegion->Dimensions - 1; i >= 0; i--) { + switch (pRegion->pDimensionDefinitions[i].dimension) { + case ::gig::dimension_samplechannel: + DimValues[i] = 0; //TODO: we currently ignore this dimension + break; + case ::gig::dimension_layer: + DimValues[i] = iLayer; + break; + case ::gig::dimension_velocity: + DimValues[i] = itNoteOnEvent->Param.Note.Velocity; + break; + case ::gig::dimension_channelaftertouch: + DimValues[i] = pEngineChannel->ControllerTable[128]; + break; + case ::gig::dimension_releasetrigger: + VoiceType = (ReleaseTriggerVoice) ? Voice::type_release_trigger : (!iLayer) ? Voice::type_release_trigger_required : Voice::type_normal; + DimValues[i] = (uint) ReleaseTriggerVoice; + break; + case ::gig::dimension_keyboard: + DimValues[i] = (uint) (pEngineChannel->CurrentKeyDimension * pRegion->pDimensionDefinitions[i].zones); + break; + case ::gig::dimension_roundrobin: + DimValues[i] = (uint) pEngineChannel->pMIDIKeyInfo[MIDIKey].RoundRobinIndex; // incremented for each note on + break; + case ::gig::dimension_random: + RandomSeed = RandomSeed * 1103515245 + 12345; // classic pseudo random number generator + DimValues[i] = (uint) RandomSeed >> (32 - pRegion->pDimensionDefinitions[i].bits); // highest bits are most random + break; + case ::gig::dimension_modwheel: + DimValues[i] = pEngineChannel->ControllerTable[1]; + break; + case ::gig::dimension_breath: + DimValues[i] = pEngineChannel->ControllerTable[2]; + break; + case ::gig::dimension_foot: + DimValues[i] = pEngineChannel->ControllerTable[4]; + break; + case ::gig::dimension_portamentotime: + DimValues[i] = pEngineChannel->ControllerTable[5]; + break; + case ::gig::dimension_effect1: + DimValues[i] = pEngineChannel->ControllerTable[12]; + break; + case ::gig::dimension_effect2: + DimValues[i] = pEngineChannel->ControllerTable[13]; + break; + case ::gig::dimension_genpurpose1: + DimValues[i] = pEngineChannel->ControllerTable[16]; + break; + case ::gig::dimension_genpurpose2: + DimValues[i] = pEngineChannel->ControllerTable[17]; + break; + case ::gig::dimension_genpurpose3: + DimValues[i] = pEngineChannel->ControllerTable[18]; + break; + case ::gig::dimension_genpurpose4: + DimValues[i] = pEngineChannel->ControllerTable[19]; + break; + case ::gig::dimension_sustainpedal: + DimValues[i] = pEngineChannel->ControllerTable[64]; + break; + case ::gig::dimension_portamento: + DimValues[i] = pEngineChannel->ControllerTable[65]; + break; + case ::gig::dimension_sostenutopedal: + DimValues[i] = pEngineChannel->ControllerTable[66]; + break; + case ::gig::dimension_softpedal: + DimValues[i] = pEngineChannel->ControllerTable[67]; + break; + case ::gig::dimension_genpurpose5: + DimValues[i] = pEngineChannel->ControllerTable[80]; + break; + case ::gig::dimension_genpurpose6: + DimValues[i] = pEngineChannel->ControllerTable[81]; + break; + case ::gig::dimension_genpurpose7: + DimValues[i] = pEngineChannel->ControllerTable[82]; + break; + case ::gig::dimension_genpurpose8: + DimValues[i] = pEngineChannel->ControllerTable[83]; + break; + case ::gig::dimension_effect1depth: + DimValues[i] = pEngineChannel->ControllerTable[91]; + break; + case ::gig::dimension_effect2depth: + DimValues[i] = pEngineChannel->ControllerTable[92]; + break; + case ::gig::dimension_effect3depth: + DimValues[i] = pEngineChannel->ControllerTable[93]; + break; + case ::gig::dimension_effect4depth: + DimValues[i] = pEngineChannel->ControllerTable[94]; + break; + case ::gig::dimension_effect5depth: + DimValues[i] = pEngineChannel->ControllerTable[95]; + break; + case ::gig::dimension_none: + std::cerr << "gig::Engine::LaunchVoice() Error: dimension=none\n" << std::flush; + break; + default: + std::cerr << "gig::Engine::LaunchVoice() Error: Unknown dimension\n" << std::flush; + } + } + + // return if this is a release triggered voice and there is no + // releasetrigger dimension (could happen if an instrument + // change has occured between note on and off) + if (ReleaseTriggerVoice && VoiceType != Voice::type_release_trigger) return Pool::Iterator(); + + ::gig::DimensionRegion* pDimRgn = pRegion->GetDimensionRegionByValue(DimValues); + + // no need to continue if sample is silent + if (!pDimRgn->pSample || !pDimRgn->pSample->SamplesTotal) return Pool::Iterator(); // allocate a new voice for the key Pool::Iterator itNewVoice = pKey->pActiveVoices->allocAppend(); if (itNewVoice) { // launch the new voice - if (itNewVoice->Trigger(pEngineChannel, itNoteOnEvent, pEngineChannel->Pitch, pEngineChannel->pInstrument, iLayer, ReleaseTriggerVoice, VoiceStealing) < 0) { + if (itNewVoice->Trigger(pEngineChannel, itNoteOnEvent, pEngineChannel->Pitch, pDimRgn, VoiceType, iKeyGroup) < 0) { dmsg(4,("Voice not triggered\n")); pKey->pActiveVoices->free(itNewVoice); } else { // on success - uint** ppKeyGroup = NULL; - if (itNewVoice->KeyGroup) { // if this voice / key belongs to a key group - ppKeyGroup = &pEngineChannel->ActiveKeyGroups[itNewVoice->KeyGroup]; - if (*ppKeyGroup) { // if there's already an active key in that key group - midi_key_info_t* pOtherKey = &pEngineChannel->pMIDIKeyInfo[**ppKeyGroup]; - // kill all voices on the (other) key - RTList::Iterator itVoiceToBeKilled = pOtherKey->pActiveVoices->first(); - RTList::Iterator end = pOtherKey->pActiveVoices->end(); - for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { - if (itVoiceToBeKilled->Type != Voice::type_release_trigger) itVoiceToBeKilled->Kill(itNoteOnEvent); - } - } - } + --VoiceSpawnsLeft; if (!pKey->Active) { // mark as active key pKey->Active = true; pKey->itSelf = pEngineChannel->pActiveKeys->allocAppend(); *pKey->itSelf = itNoteOnEvent->Param.Note.Key; } if (itNewVoice->KeyGroup) { + uint** ppKeyGroup = &pEngineChannel->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) @@ -780,8 +1417,8 @@ * @returns 0 on success, a value < 0 if no active voice could be picked for voice stealing */ int Engine::StealVoice(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) { - if (!VoiceTheftsLeft) { - dmsg(1,("Max. voice thefts per audio fragment reached (you may raise MAX_AUDIO_VOICES).\n")); + if (VoiceSpawnsLeft <= 0) { + dmsg(1,("Max. voice thefts per audio fragment reached (you may raise CONFIG_MAX_VOICES).\n")); return -1; } if (!pEventPool->poolIsEmpty()) { @@ -789,45 +1426,54 @@ RTList::Iterator itSelectedVoice; // Select one voice for voice stealing - switch (VOICE_STEAL_ALGORITHM) { + switch (CONFIG_VOICE_STEAL_ALGO) { // try to pick the oldest voice on the key where the new // voice should be spawned, if there is no voice on that - // key, or no voice left to kill there, then procceed with + // key, or no voice left to kill, then procceed with // 'oldestkey' algorithm case voice_steal_algo_oldestvoiceonkey: { - #if 0 // FIXME: broken midi_key_info_t* pSelectedKey = &pEngineChannel->pMIDIKeyInfo[itNoteOnEvent->Param.Note.Key]; - if (this->itLastStolenVoice) { - itSelectedVoice = this->itLastStolenVoice; - ++itSelectedVoice; - } - else { // no voice stolen in this audio fragment cycle yet - itSelectedVoice = pSelectedKey->pActiveVoices->first(); - } - if (itSelectedVoice) { - iuiSelectedKey = pSelectedKey->itSelf; - break; // selection succeeded - } - #endif + itSelectedVoice = pSelectedKey->pActiveVoices->first(); + // proceed iterating if voice was created in this fragment cycle + while (itSelectedVoice && !itSelectedVoice->IsStealable()) ++itSelectedVoice; + // if we haven't found a voice then proceed with algorithm 'oldestkey' + if (itSelectedVoice && itSelectedVoice->IsStealable()) break; } // no break - intentional ! // try to pick the oldest voice on the oldest active key + // from the same engine channel // (caution: must stay after 'oldestvoiceonkey' algorithm !) case voice_steal_algo_oldestkey: { + // if we already stole in this fragment, try to proceed on same key if (this->itLastStolenVoice) { itSelectedVoice = this->itLastStolenVoice; - ++itSelectedVoice; - if (itSelectedVoice) break; // selection succeeded - RTList::Iterator iuiSelectedKey = this->iuiLastStolenKey; - ++iuiSelectedKey; - if (iuiSelectedKey) { - this->iuiLastStolenKey = iuiSelectedKey; - midi_key_info_t* pSelectedKey = &pEngineChannel->pMIDIKeyInfo[*iuiSelectedKey]; - itSelectedVoice = pSelectedKey->pActiveVoices->first(); + do { + ++itSelectedVoice; + } while (itSelectedVoice && !itSelectedVoice->IsStealable()); // proceed iterating if voice was created in this fragment cycle + // found a "stealable" voice ? + if (itSelectedVoice && itSelectedVoice->IsStealable()) { + // remember which voice we stole, so we can simply proceed on next voice stealing + this->itLastStolenVoice = itSelectedVoice; break; // selection succeeded } } + // get (next) oldest key + RTList::Iterator iuiSelectedKey = (this->iuiLastStolenKey) ? ++this->iuiLastStolenKey : pEngineChannel->pActiveKeys->first(); + while (iuiSelectedKey) { + midi_key_info_t* pSelectedKey = &pEngineChannel->pMIDIKeyInfo[*iuiSelectedKey]; + itSelectedVoice = pSelectedKey->pActiveVoices->first(); + // proceed iterating if voice was created in this fragment cycle + while (itSelectedVoice && !itSelectedVoice->IsStealable()) ++itSelectedVoice; + // found a "stealable" voice ? + if (itSelectedVoice && itSelectedVoice->IsStealable()) { + // remember which voice on which key we stole, so we can simply proceed on next voice stealing + this->iuiLastStolenKey = iuiSelectedKey; + this->itLastStolenVoice = itSelectedVoice; + break; // selection succeeded + } + ++iuiSelectedKey; // get next oldest key + } break; } @@ -839,37 +1485,85 @@ } } - // steal oldest voice on the oldest key from this or any other engine channel - if (!itSelectedVoice) { - EngineChannel* pSelectedChannel = (pLastStolenChannel) ? pLastStolenChannel : pEngineChannel; - int iChannelIndex = pSelectedChannel->iEngineIndexSelf; - while (true) { - RTList::Iterator iuiSelectedKey = pSelectedChannel->pActiveKeys->first(); - if (iuiSelectedKey) { + // if we couldn't steal a voice from the same engine channel then + // steal oldest voice on the oldest key from any other engine channel + // (the smaller engine channel number, the higher priority) + if (!itSelectedVoice || !itSelectedVoice->IsStealable()) { + EngineChannel* pSelectedChannel; + int iChannelIndex; + // select engine channel + if (pLastStolenChannel) { + pSelectedChannel = pLastStolenChannel; + iChannelIndex = pSelectedChannel->iEngineIndexSelf; + } else { // pick the engine channel followed by this engine channel + iChannelIndex = (pEngineChannel->iEngineIndexSelf + 1) % engineChannels.size(); + pSelectedChannel = engineChannels[iChannelIndex]; + } + + // if we already stole in this fragment, try to proceed on same key + if (this->itLastStolenVoiceGlobally) { + itSelectedVoice = this->itLastStolenVoiceGlobally; + do { + ++itSelectedVoice; + } while (itSelectedVoice && !itSelectedVoice->IsStealable()); // proceed iterating if voice was created in this fragment cycle + } + + #if CONFIG_DEVMODE + EngineChannel* pBegin = pSelectedChannel; // to detect endless loop + #endif // CONFIG_DEVMODE + + // did we find a 'stealable' voice? + if (itSelectedVoice && itSelectedVoice->IsStealable()) { + // remember which voice we stole, so we can simply proceed on next voice stealing + this->itLastStolenVoiceGlobally = itSelectedVoice; + } else while (true) { // iterate through engine channels + // get (next) oldest key + RTList::Iterator iuiSelectedKey = (this->iuiLastStolenKeyGlobally) ? ++this->iuiLastStolenKeyGlobally : pSelectedChannel->pActiveKeys->first(); + this->iuiLastStolenKeyGlobally = RTList::Iterator(); // to prevent endless loop (see line above) + while (iuiSelectedKey) { midi_key_info_t* pSelectedKey = &pSelectedChannel->pMIDIKeyInfo[*iuiSelectedKey]; - itSelectedVoice = pSelectedKey->pActiveVoices->first(); - iuiLastStolenKey = iuiSelectedKey; - pLastStolenChannel = pSelectedChannel; - break; // selection succeeded + itSelectedVoice = pSelectedKey->pActiveVoices->first(); + // proceed iterating if voice was created in this fragment cycle + while (itSelectedVoice && !itSelectedVoice->IsStealable()) ++itSelectedVoice; + // found a "stealable" voice ? + if (itSelectedVoice && itSelectedVoice->IsStealable()) { + // remember which voice on which key on which engine channel we stole, so we can simply proceed on next voice stealing + this->iuiLastStolenKeyGlobally = iuiSelectedKey; + this->itLastStolenVoiceGlobally = itSelectedVoice; + this->pLastStolenChannel = pSelectedChannel; + goto stealable_voice_found; // selection succeeded + } + ++iuiSelectedKey; // get next key on current engine channel } + // get next engine channel iChannelIndex = (iChannelIndex + 1) % engineChannels.size(); - pSelectedChannel = engineChannels[iChannelIndex]; + pSelectedChannel = engineChannels[iChannelIndex]; + + #if CONFIG_DEVMODE + if (pSelectedChannel == pBegin) { + dmsg(1,("FATAL ERROR: voice stealing endless loop!\n")); + dmsg(1,("VoiceSpawnsLeft=%d.\n", VoiceSpawnsLeft)); + dmsg(1,("Exiting.\n")); + exit(-1); + } + #endif // CONFIG_DEVMODE } } - //FIXME: can be removed, just a sanity check for debugging + // jump point if a 'stealable' voice was found + stealable_voice_found: + + #if CONFIG_DEVMODE if (!itSelectedVoice->IsActive()) { dmsg(1,("gig::Engine: ERROR, tried to steal a voice which was not active !!!\n")); return -1; } + #endif // CONFIG_DEVMODE // now kill the selected voice itSelectedVoice->Kill(itNoteOnEvent); - // remember which voice we stole, so we can simply proceed for the next voice stealing - itLastStolenVoice = itSelectedVoice; - - --VoiceTheftsLeft; + --VoiceSpawnsLeft; return 0; // success } @@ -894,6 +1588,13 @@ uint keygroup = itVoice->KeyGroup; + // if the sample and dimension region belong to an + // instrument that is unloaded, tell the disk thread to + // release them + if (itVoice->Orphan) { + pDiskThread->OrderDeletionOfDimreg(itVoice->pDimRgn); + } + // free the voice object pVoicePool->free(itVoice); @@ -935,30 +1636,109 @@ void Engine::ProcessControlChange(EngineChannel* pEngineChannel, Pool::Iterator& itControlChangeEvent) { dmsg(4,("Engine::ContinuousController cc=%d v=%d\n", itControlChangeEvent->Param.CC.Controller, itControlChangeEvent->Param.CC.Value)); + // handle the "control triggered" MIDI rule: a control change + // event can trigger a new note on or note off event + if (pEngineChannel->pInstrument) { + + ::gig::MidiRule* rule; + for (int i = 0 ; (rule = pEngineChannel->pInstrument->GetMidiRule(i)) ; i++) { + + if (::gig::MidiRuleCtrlTrigger* ctrlTrigger = + dynamic_cast< ::gig::MidiRuleCtrlTrigger*>(rule)) { + if (itControlChangeEvent->Param.CC.Controller == + ctrlTrigger->ControllerNumber) { + + uint8_t oldCCValue = pEngineChannel->ControllerTable[ + itControlChangeEvent->Param.CC.Controller]; + uint8_t newCCValue = itControlChangeEvent->Param.CC.Value; + + for (int i = 0 ; i < ctrlTrigger->Triggers ; i++) { + ::gig::MidiRuleCtrlTrigger::trigger_t* pTrigger = + &ctrlTrigger->pTriggers[i]; + + // check if the controller has passed the + // trigger point in the right direction + if ((pTrigger->Descending && + oldCCValue > pTrigger->TriggerPoint && + newCCValue <= pTrigger->TriggerPoint) || + (!pTrigger->Descending && + oldCCValue < pTrigger->TriggerPoint && + newCCValue >= pTrigger->TriggerPoint)) { + + RTList::Iterator itNewEvent = pGlobalEvents->allocAppend(); + if (itNewEvent) { + *itNewEvent = *itControlChangeEvent; + itNewEvent->Param.Note.Key = pTrigger->Key; + + if (pTrigger->NoteOff || pTrigger->Velocity == 0) { + itNewEvent->Type = Event::type_note_off; + itNewEvent->Param.Note.Velocity = 100; + + ProcessNoteOff(pEngineChannel, itNewEvent); + } else { + itNewEvent->Type = Event::type_note_on; + //TODO: if Velocity is 255, the triggered velocity should + // depend on how fast the controller is moving + itNewEvent->Param.Note.Velocity = + pTrigger->Velocity == 255 ? 100 : + pTrigger->Velocity; + + ProcessNoteOn(pEngineChannel, itNewEvent); + } + } + else dmsg(1,("Event pool emtpy!\n")); + } + } + } + } + } + } + // update controller value in the engine channel's controller table pEngineChannel->ControllerTable[itControlChangeEvent->Param.CC.Controller] = itControlChangeEvent->Param.CC.Value; - // move event from the unsorted event list to the control change event list - Pool::Iterator itControlChangeEventOnCCList = itControlChangeEvent.moveToEndOf(pEngineChannel->pCCEvents); - - switch (itControlChangeEventOnCCList->Param.CC.Controller) { + // handle hard coded MIDI controllers + switch (itControlChangeEvent->Param.CC.Controller) { + case 5: { // portamento time + pEngineChannel->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 (pEngineChannel->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); + pEngineChannel->GlobalTranspose = transpose; + // workaround, so we won't have hanging notes + ReleaseAllVoices(pEngineChannel, itControlChangeEvent); + } + // to avoid other MIDI CC #6 messages to be misenterpreted as RPN controller data + pEngineChannel->ResetMidiRpnController(); + break; + } case 7: { // volume //TODO: not sample accurate yet - pEngineChannel->GlobalVolume = (float) itControlChangeEventOnCCList->Param.CC.Value / 127.0f; + pEngineChannel->MidiVolume = VolumeCurve[itControlChangeEvent->Param.CC.Value]; + pEngineChannel->bStatusChanged = true; // engine channel status has changed, so set notify flag break; } case 10: { // panpot //TODO: not sample accurate yet - const int pan = (int) itControlChangeEventOnCCList->Param.CC.Value - 64; - pEngineChannel->GlobalPanLeft = 1.0f - float(RTMath::Max(pan, 0)) / 63.0f; - pEngineChannel->GlobalPanRight = 1.0f - float(RTMath::Min(pan, 0)) / -64.0f; + pEngineChannel->GlobalPanLeft = PanCurve[128 - itControlChangeEvent->Param.CC.Value]; + pEngineChannel->GlobalPanRight = PanCurve[itControlChangeEvent->Param.CC.Value]; + pEngineChannel->iLastPanRequest = itControlChangeEvent->Param.CC.Value; break; } case 64: { // sustain - if (itControlChangeEventOnCCList->Param.CC.Value >= 64 && !pEngineChannel->SustainPedal) { - dmsg(4,("PEDAL DOWN\n")); + if (itControlChangeEvent->Param.CC.Value >= 64 && !pEngineChannel->SustainPedal) { + dmsg(4,("DAMPER (RIGHT) PEDAL DOWN\n")); pEngineChannel->SustainPedal = true; + #if !CONFIG_PROCESS_MUTED_CHANNELS + if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted + #endif + // cancel release process of voices if necessary RTList::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); for (; iuiKey; ++iuiKey) { @@ -966,25 +1746,29 @@ if (!pKey->KeyPressed) { RTList::Iterator itNewEvent = pKey->pEvents->allocAppend(); if (itNewEvent) { - *itNewEvent = *itControlChangeEventOnCCList; // copy event to the key's own event list + *itNewEvent = *itControlChangeEvent; // copy event to the key's own event list itNewEvent->Type = Event::type_cancel_release; // transform event type } else dmsg(1,("Event pool emtpy!\n")); } } } - if (itControlChangeEventOnCCList->Param.CC.Value < 64 && pEngineChannel->SustainPedal) { - dmsg(4,("PEDAL UP\n")); + if (itControlChangeEvent->Param.CC.Value < 64 && pEngineChannel->SustainPedal) { + dmsg(4,("DAMPER (RIGHT) PEDAL UP\n")); pEngineChannel->SustainPedal = false; + #if !CONFIG_PROCESS_MUTED_CHANNELS + if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted + #endif + // release voices if their respective key is not pressed RTList::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); for (; iuiKey; ++iuiKey) { midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; - if (!pKey->KeyPressed) { + if (!pKey->KeyPressed && ShouldReleaseVoice(pEngineChannel, *iuiKey)) { RTList::Iterator itNewEvent = pKey->pEvents->allocAppend(); if (itNewEvent) { - *itNewEvent = *itControlChangeEventOnCCList; // copy event to the key's own event list + *itNewEvent = *itControlChangeEvent; // copy event to the key's own event list itNewEvent->Type = Event::type_release; // transform event type } else dmsg(1,("Event pool emtpy!\n")); @@ -993,12 +1777,67 @@ } break; } + case 65: { // portamento on / off + const bool bPortamento = itControlChangeEvent->Param.CC.Value >= 64; + if (bPortamento != pEngineChannel->PortamentoMode) + KillAllVoices(pEngineChannel, itControlChangeEvent); + pEngineChannel->PortamentoMode = bPortamento; + break; + } + case 66: { // sostenuto + if (itControlChangeEvent->Param.CC.Value >= 64 && !pEngineChannel->SostenutoPedal) { + dmsg(4,("SOSTENUTO (CENTER) PEDAL DOWN\n")); + pEngineChannel->SostenutoPedal = true; + + #if !CONFIG_PROCESS_MUTED_CHANNELS + if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted + #endif + + SostenutoKeyCount = 0; + // Remeber the pressed keys + RTList::Iterator iuiKey = pEngineChannel->pActiveKeys->first(); + for (; iuiKey; ++iuiKey) { + midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[*iuiKey]; + if (pKey->KeyPressed && SostenutoKeyCount < 128) SostenutoKeys[SostenutoKeyCount++] = *iuiKey; + } + } + if (itControlChangeEvent->Param.CC.Value < 64 && pEngineChannel->SostenutoPedal) { + dmsg(4,("SOSTENUTO (CENTER) PEDAL UP\n")); + pEngineChannel->SostenutoPedal = false; + + #if !CONFIG_PROCESS_MUTED_CHANNELS + if (pEngineChannel->GetMute()) return; // skip if sampler channel is muted + #endif + + // release voices if the damper pedal is up and their respective key is not pressed + for (int i = 0; i < SostenutoKeyCount; i++) { + midi_key_info_t* pKey = &pEngineChannel->pMIDIKeyInfo[SostenutoKeys[i]]; + if (!pKey->KeyPressed && !pEngineChannel->SustainPedal) { + RTList::Iterator itNewEvent = pKey->pEvents->allocAppend(); + if (itNewEvent) { + *itNewEvent = *itControlChangeEvent; // copy event to the key's own event list + itNewEvent->Type = Event::type_release; // transform event type + } + else dmsg(1,("Event pool emtpy!\n")); + } + } + } + break; + } + case 100: { // RPN controller LSB + pEngineChannel->SetMidiRpnControllerLsb(itControlChangeEvent->Param.CC.Value); + break; + } + case 101: { // RPN controller MSB + pEngineChannel->SetMidiRpnControllerMsb(itControlChangeEvent->Param.CC.Value); + break; + } // Channel Mode Messages case 120: { // all sound off - KillAllVoices(pEngineChannel, itControlChangeEventOnCCList); + KillAllVoices(pEngineChannel, itControlChangeEvent); break; } case 121: { // reset all controllers @@ -1006,9 +1845,34 @@ break; } case 123: { // all notes off - ReleaseAllVoices(pEngineChannel, itControlChangeEventOnCCList); + #if CONFIG_PROCESS_ALL_NOTES_OFF + ReleaseAllVoices(pEngineChannel, itControlChangeEvent); + #endif // CONFIG_PROCESS_ALL_NOTES_OFF break; } + case 126: { // mono mode on + if (!pEngineChannel->SoloMode) + KillAllVoices(pEngineChannel, itControlChangeEvent); + pEngineChannel->SoloMode = true; + break; + } + case 127: { // poly mode on + if (pEngineChannel->SoloMode) + KillAllVoices(pEngineChannel, itControlChangeEvent); + pEngineChannel->SoloMode = false; + break; + } + } + + // handle FX send controllers + if (!pEngineChannel->fxSends.empty()) { + for (int iFxSend = 0; iFxSend < pEngineChannel->GetFxSendCount(); iFxSend++) { + FxSend* pFxSend = pEngineChannel->GetFxSend(iFxSend); + if (pFxSend->MidiController() == itControlChangeEvent->Param.CC.Controller) { + pFxSend->SetLevel(itControlChangeEvent->Param.CC.Value); + pFxSend->SetInfoChanged(true); + } + } } } @@ -1018,7 +1882,7 @@ * @param itSysexEvent - sysex data size and time stamp of the sysex event */ void Engine::ProcessSysex(Pool::Iterator& itSysexEvent) { - RingBuffer::NonVolatileReader reader = pSysexBuffer->get_non_volatile_reader(); + RingBuffer::NonVolatileReader reader = pSysexBuffer->get_non_volatile_reader(); uint8_t exclusive_status, id; if (!reader.pop(&exclusive_status)) goto free_sysex_data; @@ -1026,7 +1890,28 @@ if (exclusive_status != 0xF0) goto free_sysex_data; switch (id) { + case 0x7f: { // (Realtime) Universal Sysex (GM Standard) + uint8_t sysex_channel, sub_id1, sub_id2, val_msb, val_lsb;; + if (!reader.pop(&sysex_channel)) goto free_sysex_data; + if (!reader.pop(&sub_id1)) goto free_sysex_data; + if (!reader.pop(&sub_id2)) goto free_sysex_data; + if (!reader.pop(&val_lsb)) goto free_sysex_data; + if (!reader.pop(&val_msb)) goto free_sysex_data; + //TODO: for now we simply ignore the sysex channel, seldom used anyway + switch (sub_id1) { + case 0x04: // Device Control + switch (sub_id2) { + case 0x01: // Master Volume + GLOBAL_VOLUME = + double((uint(val_msb)<<7) | uint(val_lsb)) / 16383.0; + break; + } + break; + } + break; + } case 0x41: { // Roland + dmsg(3,("Roland Sysex\n")); uint8_t device_id, model_id, cmd_id; if (!reader.pop(&device_id)) goto free_sysex_data; if (!reader.pop(&model_id)) goto free_sysex_data; @@ -1036,22 +1921,53 @@ // command address uint8_t addr[3]; // 2 byte addr MSB, followed by 1 byte addr LSB) - const RingBuffer::NonVolatileReader checksum_reader = reader; // so we can calculate the check sum later + const RingBuffer::NonVolatileReader checksum_reader = reader; // so we can calculate the check sum later if (reader.read(&addr[0], 3) != 3) goto free_sysex_data; if (addr[0] == 0x40 && addr[1] == 0x00) { // System Parameters + dmsg(3,("\tSystem Parameter\n")); } else if (addr[0] == 0x40 && addr[1] == 0x01) { // Common Parameters + dmsg(3,("\tCommon Parameter\n")); } else if (addr[0] == 0x40 && (addr[1] & 0xf0) == 0x10) { // Part Parameters (1) - switch (addr[3]) { + dmsg(3,("\tPart Parameter\n")); + switch (addr[2]) { case 0x40: { // scale tuning + dmsg(3,("\t\tScale Tuning\n")); uint8_t scale_tunes[12]; // detuning of all 12 semitones of an octave if (reader.read(&scale_tunes[0], 12) != 12) goto free_sysex_data; uint8_t checksum; - if (!reader.pop(&checksum)) goto free_sysex_data; - if (GSCheckSum(checksum_reader, 12) != checksum) goto free_sysex_data; + if (!reader.pop(&checksum)) goto free_sysex_data; + #if CONFIG_ASSERT_GS_SYSEX_CHECKSUM + if (GSCheckSum(checksum_reader, 12)) goto free_sysex_data; + #endif // CONFIG_ASSERT_GS_SYSEX_CHECKSUM for (int i = 0; i < 12; i++) scale_tunes[i] -= 64; AdjustScale((int8_t*) scale_tunes); + dmsg(3,("\t\t\tNew scale applied.\n")); + break; + } + case 0x15: { // chromatic / drumkit mode + dmsg(3,("\t\tMIDI Instrument Map Switch\n")); + uint8_t part = addr[1] & 0x0f; + uint8_t map; + if (!reader.pop(&map)) goto free_sysex_data; + for (int i = 0; i < engineChannels.size(); ++i) { + EngineChannel* pEngineChannel = engineChannels[i]; + if (pEngineChannel->midiChannel == part || + pEngineChannel->midiChannel == midi_chan_all + ) { + try { + pEngineChannel->SetMidiInstrumentMap(map); + } catch (Exception e) { + dmsg(2,("\t\t\tCould not apply MIDI instrument map %d to part %d: %s\n", map, part, e.Message().c_str())); + goto free_sysex_data; + } catch (...) { + dmsg(2,("\t\t\tCould not apply MIDI instrument map %d to part %d (unknown exception)\n", map, part)); + goto free_sysex_data; + } + } + } + dmsg(3,("\t\t\tApplied MIDI instrument map %d to part %d.\n", map, part)); break; } } @@ -1076,8 +1992,8 @@ * question * @param DataSize - size of the GS message data (in bytes) */ - uint8_t Engine::GSCheckSum(const RingBuffer::NonVolatileReader AddrReader, uint DataSize) { - RingBuffer::NonVolatileReader reader = AddrReader; + uint8_t Engine::GSCheckSum(const RingBuffer::NonVolatileReader AddrReader, uint DataSize) { + RingBuffer::NonVolatileReader reader = AddrReader; uint bytes = 3 /*addr*/ + DataSize; uint8_t addr_and_data[bytes]; reader.read(&addr_and_data[0], bytes); @@ -1136,23 +2052,27 @@ RTList::Iterator itVoicesEnd = pKey->pActiveVoices->end(); for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key itVoice->Kill(itKillEvent); + --VoiceSpawnsLeft; //FIXME: just a temporary workaround, we should check the cause in StealVoice() instead } } } /** - * Initialize the parameter sequence for the modulation destination given by - * by 'dst' with the constant value given by val. + * Determines whether the specified voice should be released. + * + * @param pEngineChannel - The engine channel on which the voice should be checked + * @param Key - The key number + * @returns true if the specified should be released, false otherwise. */ - void Engine::ResetSynthesisParameters(Event::destination_t dst, float val) { - int maxsamples = pAudioOutputDevice->MaxSamplesPerCycle(); - float* m = &pSynthesisParameters[dst][0]; - for (int i = 0; i < maxsamples; i += 4) { - m[i] = val; - m[i+1] = val; - m[i+2] = val; - m[i+3] = val; + bool Engine::ShouldReleaseVoice(EngineChannel* pEngineChannel, int Key) { + if (pEngineChannel->SustainPedal) return false; + + if (pEngineChannel->SostenutoPedal) { + for (int i = 0; i < SostenutoKeyCount; i++) + if (Key == SostenutoKeys[i]) return false; } + + return true; } uint Engine::VoiceCount() { @@ -1184,16 +2104,62 @@ } String Engine::EngineName() { - return "GigEngine"; + return LS_GIG_ENGINE_NAME; } String Engine::Description() { - return "Gigasampler Engine"; + return "Gigasampler Format Engine"; } String Engine::Version() { - String s = "$Revision: 1.33 $"; + String s = "$Revision: 1.94 $"; return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword } + InstrumentManager* Engine::GetInstrumentManager() { + return &instruments; + } + + // static constant initializers + const Engine::FloatTable Engine::VolumeCurve(InitVolumeCurve()); + const Engine::FloatTable Engine::PanCurve(InitPanCurve()); + const Engine::FloatTable Engine::CrossfadeCurve(InitCrossfadeCurve()); + + float* Engine::InitVolumeCurve() { + // line-segment approximation + const float segments[] = { + 0, 0, 2, 0.0046, 16, 0.016, 31, 0.051, 45, 0.115, 54.5, 0.2, + 64.5, 0.39, 74, 0.74, 92, 1.03, 114, 1.94, 119.2, 2.2, 127, 2.2 + }; + return InitCurve(segments); + } + + float* Engine::InitPanCurve() { + // line-segment approximation + const float segments[] = { + 0, 0, 1, 0, + 2, 0.05, 31.5, 0.7, 51, 0.851, 74.5, 1.12, + 127, 1.41, 128, 1.41 + }; + return InitCurve(segments, 129); + } + + float* Engine::InitCrossfadeCurve() { + // line-segment approximation + const float segments[] = { + 0, 0, 1, 0.03, 10, 0.1, 51, 0.58, 127, 1 + }; + return InitCurve(segments); + } + + float* Engine::InitCurve(const float* segments, int size) { + float* y = new float[size]; + for (int x = 0 ; x < size ; x++) { + if (x > segments[2]) segments += 2; + y[x] = segments[1] + (x - segments[0]) * + (segments[3] - segments[1]) / (segments[2] - segments[0]); + } + return y; + } + }} // namespace LinuxSampler::gig