--- linuxsampler/trunk/src/engines/AbstractEngineChannel.cpp 2009/11/01 18:47:59 2025 +++ linuxsampler/trunk/src/engines/AbstractEngineChannel.cpp 2014/06/06 12:38:54 2598 @@ -3,8 +3,9 @@ * LinuxSampler - modular, streaming capable sampler * * * * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * - * Copyright (C) 2005-2009 Christian Schoenebeck * - * Copyright (C) 2009 Grigor Iliev * + * Copyright (C) 2005-2008 Christian Schoenebeck * + * Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev * + * Copyright (C) 2013-2014 Christian Schoenebeck and Andreas Persson * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -41,16 +42,18 @@ pChannelRight = NULL; AudioDeviceChannelLeft = -1; AudioDeviceChannelRight = -1; - pMidiInputPort = NULL; midiChannel = midi_chan_all; ResetControllers(); PortamentoMode = false; PortamentoTime = CONFIG_PORTAMENTO_TIME_DEFAULT; + pScriptEvents = NULL; } AbstractEngineChannel::~AbstractEngineChannel() { - - if (pEventQueue) delete pEventQueue; + unloadCurrentInstrumentScript(); + if (pScriptEvents) delete pScriptEvents; + delete pEventQueue; + DeleteGroupEventLists(); RemoveAllFxSends(); } @@ -112,8 +115,6 @@ Pitch = 0; GlobalVolume = 1.0f; MidiVolume = 1.0; - GlobalPanLeft = 1.0f; - GlobalPanRight = 1.0f; iLastPanRequest = 64; GlobalTranspose = 0; // set all MIDI controller values to zero @@ -144,6 +145,99 @@ } /** + * Loads the real-time instrument script given by @a text on this engine + * channel. A resource manager is used to allocate and share equivalent + * scripts on multiple engine channels. + * + * @param text - source code of script + */ + void AbstractEngineChannel::loadInstrumentScript(const String& text) { + dmsg(1,("Loading real-time instrument script ... ")); + + // hand back old script reference and VM execution contexts + // (if not done already) + unloadCurrentInstrumentScript(); + + // get new script reference + script.parserContext = pEngine->scripts.Borrow(text, this); + if (!script.parserContext->errors().empty()) { + std::vector errors = script.parserContext->errors(); + std::cerr << "[ScriptVM] Could not load instrument script, there were " + << errors.size() << " parser errors:\n"; + for (int i = 0; i < errors.size(); ++i) + errors[i].dump(); + return; // stop here if there were any parser errors + } + + script.handlerInit = script.parserContext->eventHandlerByName("init"); + script.handlerNote = script.parserContext->eventHandlerByName("note"); + script.handlerRelease = script.parserContext->eventHandlerByName("release"); + script.handlerController = script.parserContext->eventHandlerByName("controller"); + script.bHasValidScript = + script.handlerInit || script.handlerNote || script.handlerRelease || + script.handlerController; + + // amount of script handlers each script event has to execute + int handlerExecCount = 0; + if (script.handlerInit) handlerExecCount++; // "init" handler is always executed before the actual event handler + if (script.handlerNote || script.handlerRelease || script.handlerController) // only one of these are executed after "init" handler + handlerExecCount++; + + // create script event pool (if it doesn't exist already) + if (!pScriptEvents) + pScriptEvents = new Pool(CONFIG_MAX_EVENTS_PER_FRAGMENT); + + // create new VM execution contexts for new script + while (!pScriptEvents->poolIsEmpty()) { + RTList::Iterator it = pScriptEvents->allocAppend(); + it->execCtx = pEngine->pScriptVM->createExecContext( + script.parserContext + ); + it->handlers = new VMEventHandler*[handlerExecCount+1]; + } + pScriptEvents->clear(); + + dmsg(1,("Done\n")); + } + + /** + * Unloads the currently used real-time instrument script on this sampler + * channel. A resource manager is used to share equivalent scripts among + * multiple sampler channels, and to deallocate the parsed script once not + * used on any engine channel anymore. + */ + void AbstractEngineChannel::unloadCurrentInstrumentScript() { + if (script.parserContext) + dmsg(1,("Unloading current instrument script.")); + + // free allocated VM execution contexts + if (pScriptEvents) { + pScriptEvents->clear(); + while (!pScriptEvents->poolIsEmpty()) { + RTList::Iterator it = pScriptEvents->allocAppend(); + if (it->execCtx) { + // free VM execution context object + delete it->execCtx; + it->execCtx = NULL; + // free C array of handler pointers + delete [] it->handlers; + } + } + pScriptEvents->clear(); + } + // hand back VM representation of script + if (script.parserContext) { + pEngine->scripts.HandBack(script.parserContext, this); + script.parserContext = NULL; + script.handlerInit = NULL; + script.handlerNote = NULL; + script.handlerRelease = NULL; + script.handlerController = NULL; + } + script.bHasValidScript = false; + } + + /** * Implementation of virtual method from abstract EngineChannel interface. * This method will periodically be polled (e.g. by the LSCP server) to * check if some engine channel parameter has changed since the last @@ -182,8 +276,6 @@ int iMidiPan = int(f * 64.0f) + 64; if (iMidiPan > 127) iMidiPan = 127; else if (iMidiPan < 0) iMidiPan = 0; - GlobalPanLeft = AbstractEngine::PanCurve[128 - iMidiPan]; - GlobalPanRight = AbstractEngine::PanCurve[iMidiPan]; iLastPanRequest = iMidiPan; } @@ -191,6 +283,15 @@ return (pEngine) ? pEngine->pAudioOutputDevice : NULL; } + /** + * Gets thread safe access to the currently connected audio output + * device from other threads than the lscp thread. + */ + AudioOutputDevice* AbstractEngineChannel::GetAudioOutputDeviceSafe() { + LockGuard lock(EngineMutex); + return GetAudioOutputDevice(); + } + void AbstractEngineChannel::SetOutputChannel(uint EngineAudioChannel, uint AudioDeviceChannel) { if (!pEngine || !pEngine->pAudioOutputDevice) throw AudioOutputException("No audio output device connected yet."); @@ -223,28 +324,109 @@ } } + void AbstractEngineChannel::Connect(MidiInputPort* pMidiPort) { + if (!pMidiPort) return; + + Sync< ArrayList > connections = midiInputs.back(); + + // check if connection already exists + for (int i = 0; i < connections->size(); ++i) + if ((*connections)[i] == pMidiPort) + return; // to avoid endless recursion + + connections->add(pMidiPort); + + // inform MIDI port about this new connection + pMidiPort->Connect(this, MidiChannel()); + } + + void AbstractEngineChannel::Disconnect(MidiInputPort* pMidiPort) { + if (!pMidiPort) return; + + Sync< ArrayList > connections = midiInputs.back(); + + for (int i = 0; i < connections->size(); ++i) { + if ((*connections)[i] == pMidiPort) { + connections->remove(i); + // inform MIDI port about this disconnection + pMidiPort->Disconnect(this); + return; + } + } + } + + void AbstractEngineChannel::DisconnectAllMidiInputPorts() { + Sync< ArrayList > connections = midiInputs.back(); + ArrayList clonedList = *connections; + connections->clear(); + for (int i = 0; i < clonedList.size(); ++i) clonedList[i]->Disconnect(this); + } + + uint AbstractEngineChannel::GetMidiInputPortCount() { + Sync< ArrayList > connections = midiInputs.back(); + return connections->size(); + } + + MidiInputPort* AbstractEngineChannel::GetMidiInputPort(uint index) { + Sync< ArrayList > connections = midiInputs.back(); + return (index < connections->size()) ? (*connections)[index] : NULL; + } + + // deprecated (just for API backward compatibility) - may be removed in future void AbstractEngineChannel::Connect(MidiInputPort* pMidiPort, midi_chan_t MidiChannel) { - if (!pMidiPort || pMidiPort == this->pMidiInputPort) return; - DisconnectMidiInputPort(); - this->pMidiInputPort = pMidiPort; - this->midiChannel = MidiChannel; + if (!pMidiPort) return; + + Sync< ArrayList > connections = midiInputs.back(); + + // check against endless recursion + if (connections->size() == 1 && (*connections)[0] == pMidiPort && this->midiChannel == MidiChannel) + return; + + if (!isValidMidiChan(MidiChannel)) + throw MidiInputException("Invalid MIDI channel (" + ToString(int(MidiChannel)) + ")"); + + this->midiChannel = MidiChannel; + + // disconnect all currently connected MIDI ports + ArrayList clonedList = *connections; + connections->clear(); + for (int i = 0; i < clonedList.size(); ++i) + clonedList[i]->Disconnect(this); + + // connect the new port + connections->add(pMidiPort); pMidiPort->Connect(this, MidiChannel); } + // deprecated (just for API backward compatibility) - may be removed in future void AbstractEngineChannel::DisconnectMidiInputPort() { - MidiInputPort* pOldPort = this->pMidiInputPort; - this->pMidiInputPort = NULL; - if (pOldPort) pOldPort->Disconnect(this); + DisconnectAllMidiInputPorts(); } + // deprecated (just for API backward compatibility) - may be removed in future MidiInputPort* AbstractEngineChannel::GetMidiInputPort() { - return pMidiInputPort; + return GetMidiInputPort(0); } midi_chan_t AbstractEngineChannel::MidiChannel() { return midiChannel; } + void AbstractEngineChannel::SetMidiChannel(midi_chan_t MidiChannel) { + if (this->midiChannel == MidiChannel) return; + if (!isValidMidiChan(MidiChannel)) + throw MidiInputException("Invalid MIDI channel (" + ToString(int(MidiChannel)) + ")"); + + this->midiChannel = MidiChannel; + + Sync< ArrayList > connections = midiInputs.back(); + ArrayList clonedList = *connections; + + DisconnectAllMidiInputPorts(); + + for (int i = 0; i < clonedList.size(); ++i) Connect(clonedList[i]); + } + void AbstractEngineChannel::Connect(VirtualMidiDevice* pDevice) { // double buffer ... double work ... { @@ -278,12 +460,17 @@ * @param Key - MIDI key number of the triggered key * @param Velocity - MIDI velocity value of the triggered key */ - void AbstractEngineChannel::SendNoteOn(uint8_t Key, uint8_t Velocity) { + void AbstractEngineChannel::SendNoteOn(uint8_t Key, uint8_t Velocity, uint8_t MidiChannel) { if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + Event event = pEngine->pEventGenerator->CreateEvent(); event.Type = Event::type_note_on; event.Param.Note.Key = Key; event.Param.Note.Velocity = Velocity; + event.Param.Note.Channel = MidiChannel; event.pEngineChannel = this; if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); else dmsg(1,("EngineChannel: Input event queue full!")); @@ -311,15 +498,20 @@ * @param FragmentPos - sample point position in the current audio * fragment to which this event belongs to */ - void AbstractEngineChannel::SendNoteOn(uint8_t Key, uint8_t Velocity, int32_t FragmentPos) { + void AbstractEngineChannel::SendNoteOn(uint8_t Key, uint8_t Velocity, uint8_t MidiChannel, int32_t FragmentPos) { if (FragmentPos < 0) { dmsg(1,("EngineChannel::SendNoteOn(): negative FragmentPos! Seems MIDI driver is buggy!")); } else if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); event.Type = Event::type_note_on; event.Param.Note.Key = Key; event.Param.Note.Velocity = Velocity; + event.Param.Note.Channel = MidiChannel; event.pEngineChannel = this; if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); else dmsg(1,("EngineChannel: Input event queue full!")); @@ -345,12 +537,17 @@ * @param Key - MIDI key number of the released key * @param Velocity - MIDI release velocity value of the released key */ - void AbstractEngineChannel::SendNoteOff(uint8_t Key, uint8_t Velocity) { + void AbstractEngineChannel::SendNoteOff(uint8_t Key, uint8_t Velocity, uint8_t MidiChannel) { if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + Event event = pEngine->pEventGenerator->CreateEvent(); event.Type = Event::type_note_off; event.Param.Note.Key = Key; event.Param.Note.Velocity = Velocity; + event.Param.Note.Channel = MidiChannel; event.pEngineChannel = this; if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); else dmsg(1,("EngineChannel: Input event queue full!")); @@ -378,15 +575,20 @@ * @param FragmentPos - sample point position in the current audio * fragment to which this event belongs to */ - void AbstractEngineChannel::SendNoteOff(uint8_t Key, uint8_t Velocity, int32_t FragmentPos) { + void AbstractEngineChannel::SendNoteOff(uint8_t Key, uint8_t Velocity, uint8_t MidiChannel, int32_t FragmentPos) { if (FragmentPos < 0) { dmsg(1,("EngineChannel::SendNoteOff(): negative FragmentPos! Seems MIDI driver is buggy!")); } else if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); event.Type = Event::type_note_off; event.Param.Note.Key = Key; event.Param.Note.Velocity = Velocity; + event.Param.Note.Channel = MidiChannel; event.pEngineChannel = this; if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); else dmsg(1,("EngineChannel: Input event queue full!")); @@ -411,11 +613,16 @@ * * @param Pitch - MIDI pitch value (-8192 ... +8191) */ - void AbstractEngineChannel::SendPitchbend(int Pitch) { + void AbstractEngineChannel::SendPitchbend(int Pitch, uint8_t MidiChannel) { if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + Event event = pEngine->pEventGenerator->CreateEvent(); event.Type = Event::type_pitchbend; event.Param.Pitch.Pitch = Pitch; + event.Param.Pitch.Channel = MidiChannel; event.pEngineChannel = this; if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); else dmsg(1,("EngineChannel: Input event queue full!")); @@ -432,14 +639,19 @@ * @param FragmentPos - sample point position in the current audio * fragment to which this event belongs to */ - void AbstractEngineChannel::SendPitchbend(int Pitch, int32_t FragmentPos) { + void AbstractEngineChannel::SendPitchbend(int Pitch, uint8_t MidiChannel, int32_t FragmentPos) { if (FragmentPos < 0) { dmsg(1,("AbstractEngineChannel::SendPitchBend(): negative FragmentPos! Seems MIDI driver is buggy!")); } else if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); event.Type = Event::type_pitchbend; event.Param.Pitch.Pitch = Pitch; + event.Param.Pitch.Channel = MidiChannel; event.pEngineChannel = this; if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); else dmsg(1,("AbstractEngineChannel: Input event queue full!")); @@ -455,12 +667,17 @@ * @param Controller - MIDI controller number of the occured control change * @param Value - value of the control change */ - void AbstractEngineChannel::SendControlChange(uint8_t Controller, uint8_t Value) { + void AbstractEngineChannel::SendControlChange(uint8_t Controller, uint8_t Value, uint8_t MidiChannel) { if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + Event event = pEngine->pEventGenerator->CreateEvent(); event.Type = Event::type_control_change; event.Param.CC.Controller = Controller; event.Param.CC.Value = Value; + event.Param.CC.Channel = MidiChannel; event.pEngineChannel = this; if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); else dmsg(1,("AbstractEngineChannel: Input event queue full!")); @@ -478,21 +695,92 @@ * @param FragmentPos - sample point position in the current audio * fragment to which this event belongs to */ - void AbstractEngineChannel::SendControlChange(uint8_t Controller, uint8_t Value, int32_t FragmentPos) { + void AbstractEngineChannel::SendControlChange(uint8_t Controller, uint8_t Value, uint8_t MidiChannel, int32_t FragmentPos) { if (FragmentPos < 0) { dmsg(1,("AbstractEngineChannel::SendControlChange(): negative FragmentPos! Seems MIDI driver is buggy!")); } else if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); event.Type = Event::type_control_change; event.Param.CC.Controller = Controller; event.Param.CC.Value = Value; + event.Param.CC.Channel = MidiChannel; event.pEngineChannel = this; if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); else dmsg(1,("AbstractEngineChannel: Input event queue full!")); } } + void AbstractEngineChannel::SendChannelPressure(uint8_t Value, uint8_t MidiChannel) { + if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + + Event event = pEngine->pEventGenerator->CreateEvent(); + event.Type = Event::type_channel_pressure; + event.Param.ChannelPressure.Value = Value; + event.Param.ChannelPressure.Channel = MidiChannel; + event.pEngineChannel = this; + if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); + else dmsg(1,("AbstractEngineChannel: Input event queue full!")); + } + } + + void AbstractEngineChannel::SendChannelPressure(uint8_t Value, uint8_t MidiChannel, int32_t FragmentPos) { + if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + + Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); + event.Type = Event::type_channel_pressure; + event.Param.ChannelPressure.Value = Value; + event.Param.ChannelPressure.Channel = MidiChannel; + event.pEngineChannel = this; + if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); + else dmsg(1,("AbstractEngineChannel: Input event queue full!")); + } + } + + void AbstractEngineChannel::SendPolyphonicKeyPressure(uint8_t Key, uint8_t Value, uint8_t MidiChannel) { + if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + + Event event = pEngine->pEventGenerator->CreateEvent(); + event.Type = Event::type_note_pressure; + event.Param.NotePressure.Key = Key; + event.Param.NotePressure.Value = Value; + event.Param.NotePressure.Channel = MidiChannel; + event.pEngineChannel = this; + if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); + else dmsg(1,("AbstractEngineChannel: Input event queue full!")); + } + } + + void AbstractEngineChannel::SendPolyphonicKeyPressure(uint8_t Key, uint8_t Value, uint8_t MidiChannel, int32_t FragmentPos) { + if (pEngine) { + // protection in case there are more than 1 MIDI input threads sending MIDI events to this EngineChannel + LockGuard g; + if (hasMultipleMIDIInputs()) g = LockGuard(MidiInputMutex); + + Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); + event.Type = Event::type_note_pressure; + event.Param.NotePressure.Key = Key; + event.Param.NotePressure.Value = Value; + event.Param.NotePressure.Channel = MidiChannel; + event.pEngineChannel = this; + if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); + else dmsg(1,("AbstractEngineChannel: Input event queue full!")); + } + } + /** * Copy all events from the engine channel's input event queue buffer to * the internal event list. This will be done at the beginning of each @@ -510,6 +798,7 @@ // import events from pure software MIDI "devices" // (e.g. virtual keyboard in instrument editor) { + const uint8_t channel = MidiChannel() == midi_chan_all ? 0 : MidiChannel(); const int FragmentPos = 0; // randomly chosen, we don't care about jitter for virtual MIDI devices Event event = pEngine->pEventGenerator->CreateEvent(FragmentPos); VirtualMidiDevice::event_t devEvent; // the event format we get from the virtual MIDI device @@ -527,17 +816,37 @@ event.Type = Event::type_note_on; event.Param.Note.Key = devEvent.Arg1; event.Param.Note.Velocity = devEvent.Arg2; + event.Param.Note.Channel = channel; break; case VirtualMidiDevice::EVENT_TYPE_NOTEOFF: event.Type = Event::type_note_off; event.Param.Note.Key = devEvent.Arg1; event.Param.Note.Velocity = devEvent.Arg2; + event.Param.Note.Channel = channel; break; case VirtualMidiDevice::EVENT_TYPE_CC: - event.Type = Event::type_control_change; - event.Param.CC.Controller = devEvent.Arg1; - event.Param.CC.Value = devEvent.Arg2; + switch (devEvent.Arg1) { + case 0: // bank select MSB ... + SetMidiBankMsb(devEvent.Arg2); + continue; // don't push this event into FIFO + case 32: // bank select LSB ... + SetMidiBankLsb(devEvent.Arg2); + continue; // don't push this event into FIFO + default: // regular MIDI CC ... + event.Type = Event::type_control_change; + event.Param.CC.Controller = devEvent.Arg1; + event.Param.CC.Value = devEvent.Arg2; + event.Param.CC.Channel = channel; + } break; + case VirtualMidiDevice::EVENT_TYPE_PITCHBEND: + event.Type = Event::type_pitchbend; + event.Param.Pitch.Pitch = int(devEvent.Arg2 << 7 | devEvent.Arg1) - 8192; + event.Param.Pitch.Channel = channel; + break; + case VirtualMidiDevice::EVENT_TYPE_PROGRAM: + SendProgramChange(devEvent.Arg1); + continue; // don't push this event into FIFO default: std::cerr << "AbstractEngineChannel::ImportEvents() ERROR: unknown event type (" << devEvent.Type << "). This is a bug!"; @@ -579,6 +888,31 @@ eventQueueReader.free(); // free all copied events from input queue } + /** + * Called by real-time instrument script functions to schedule a new event + * somewhere in future. + * + * @returns unique event ID of scheduled new event + */ + int AbstractEngineChannel::ScheduleEvent(const Event* pEvent, int delay) { //TODO: delay not implemented yet + // since delay is not implemented yet, we simply add the new event + // to the event list of the current audio fragmet cycle for now + RTList::Iterator itEvent = pEvents->allocAppend(); + if (itEvent) *itEvent = *pEvent; // copy event + return pEvents->getID(itEvent); + } + + /** + * Called by real-time instrument script functions to ignore the event + * reflected by given event ID. The event will be freed immediately to its + * pool and cannot be dereferenced by its old ID anymore. Even if its + * allocated back from the Pool later on, it will have a different ID. + */ + void AbstractEngineChannel::IgnoreEvent(int id) { + RTList::Iterator it = pEvents->fromID(id); + if (it) pEvents->free(it); + } + FxSend* AbstractEngineChannel::AddFxSend(uint8_t MidiCtrl, String Name) throw (Exception) { if (pEngine) pEngine->DisableAndLock(); FxSend* pFxSend = new FxSend(this, MidiCtrl, Name); @@ -661,4 +995,62 @@ if (pEngine) pEngine->Enable(); } + /** + * Add a group number to the set of key groups. Should be called + * when an instrument is loaded to make sure there are event lists + * for all key groups. + */ + void AbstractEngineChannel::AddGroup(uint group) { + if (group) { + std::pair p = + ActiveKeyGroups.insert(ActiveKeyGroupMap::value_type(group, 0)); + if (p.second) { + // If the engine channel is pending deletion (see bug + // #113), pEngine will be null, so we can't use + // pEngine->pEventPool here. Instead we're using a + // specialized RTList that allows specifying the pool + // later. + (*p.first).second = new LazyList; + } + } + } + + /** + * Handle key group (a.k.a. exclusive group) conflicts. + */ + void AbstractEngineChannel::HandleKeyGroupConflicts(uint KeyGroup, Pool::Iterator& itNoteOnEvent) { + dmsg(4,("HandelKeyGroupConflicts KeyGroup=%d\n", KeyGroup)); + if (KeyGroup) { + // send a release event to all active voices in the group + RTList::Iterator itEvent = ActiveKeyGroups[KeyGroup]->allocAppend(pEngine->pEventPool); + *itEvent = *itNoteOnEvent; + } + } + + /** + * Empty the lists of group events. Should be called from the + * audio thread, after all voices have been rendered. + */ + void AbstractEngineChannel::ClearGroupEventLists() { + for (ActiveKeyGroupMap::iterator iter = ActiveKeyGroups.begin(); + iter != ActiveKeyGroups.end(); iter++) { + if (iter->second) { + iter->second->clear(); + } else { + dmsg(1,("EngineChannel: group event list was NULL")); + } + } + } + + /** + * Remove all lists with group events. + */ + void AbstractEngineChannel::DeleteGroupEventLists() { + for (ActiveKeyGroupMap::iterator iter = ActiveKeyGroups.begin(); + iter != ActiveKeyGroups.end(); iter++) { + delete iter->second; + } + ActiveKeyGroups.clear(); + } + } // namespace LinuxSampler