--- linuxsampler/trunk/src/engines/AbstractEngineChannel.cpp 2009/11/01 18:47:59 2025 +++ linuxsampler/trunk/src/engines/AbstractEngineChannel.cpp 2014/05/18 17:38:25 2559 @@ -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,7 +42,6 @@ pChannelRight = NULL; AudioDeviceChannelLeft = -1; AudioDeviceChannelRight = -1; - pMidiInputPort = NULL; midiChannel = midi_chan_all; ResetControllers(); PortamentoMode = false; @@ -49,8 +49,8 @@ } AbstractEngineChannel::~AbstractEngineChannel() { - - if (pEventQueue) delete pEventQueue; + delete pEventQueue; + DeleteGroupEventLists(); RemoveAllFxSends(); } @@ -112,8 +112,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 @@ -182,8 +180,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 +187,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 +228,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 +364,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 +402,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 +441,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 +479,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 +517,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 +543,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 +571,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 +599,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 +702,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 +720,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!"; @@ -661,4 +874,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