diff options
Diffstat (limited to 'src/server/internals')
-rw-r--r-- | src/server/internals/BlockDelay.cpp | 89 | ||||
-rw-r--r-- | src/server/internals/BlockDelay.hpp | 62 | ||||
-rw-r--r-- | src/server/internals/Controller.cpp | 174 | ||||
-rw-r--r-- | src/server/internals/Controller.hpp | 71 | ||||
-rw-r--r-- | src/server/internals/Note.cpp | 420 | ||||
-rw-r--r-- | src/server/internals/Note.hpp | 109 | ||||
-rw-r--r-- | src/server/internals/Time.cpp | 78 | ||||
-rw-r--r-- | src/server/internals/Time.hpp | 59 | ||||
-rw-r--r-- | src/server/internals/Trigger.cpp | 187 | ||||
-rw-r--r-- | src/server/internals/Trigger.hpp | 75 |
10 files changed, 1324 insertions, 0 deletions
diff --git a/src/server/internals/BlockDelay.cpp b/src/server/internals/BlockDelay.cpp new file mode 100644 index 00000000..5c7ad147 --- /dev/null +++ b/src/server/internals/BlockDelay.cpp @@ -0,0 +1,89 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <climits> + +#include <cmath> + +#include "ingen/URIs.hpp" +#include "raul/Array.hpp" +#include "raul/Maid.hpp" + +#include "Buffer.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "RunContext.hpp" +#include "internals/BlockDelay.hpp" + +namespace ingen { +namespace server { +namespace internals { + +InternalPlugin* BlockDelayNode::internal_plugin(URIs& uris) { + return new InternalPlugin( + uris, URI(NS_INTERNALS "BlockDelay"), Raul::Symbol("blockDelay")); +} + +BlockDelayNode::BlockDelayNode(InternalPlugin* plugin, + BufferFactory& bufs, + const Raul::Symbol& symbol, + bool polyphonic, + GraphImpl* parent, + SampleRate srate) + : InternalBlock(plugin, symbol, polyphonic, parent, srate) +{ + const ingen::URIs& uris = bufs.uris(); + _ports = bufs.maid().make_managed<Ports>(2); + + _in_port = new InputPort(bufs, this, Raul::Symbol("in"), 0, 1, + PortType::AUDIO, 0, bufs.forge().make(0.0f)); + _in_port->set_property(uris.lv2_name, bufs.forge().alloc("In")); + _ports->at(0) = _in_port; + + _out_port = new OutputPort(bufs, this, Raul::Symbol("out"), 0, 1, + PortType::AUDIO, 0, bufs.forge().make(0.0f)); + _out_port->set_property(uris.lv2_name, bufs.forge().alloc("Out")); + _ports->at(1) = _out_port; +} + +BlockDelayNode::~BlockDelayNode() +{ + _buffer.reset(); +} + +void +BlockDelayNode::activate(BufferFactory& bufs) +{ + _buffer = bufs.create( + bufs.uris().atom_Sound, 0, bufs.audio_buffer_size()); + + BlockImpl::activate(bufs); +} + +void +BlockDelayNode::run(RunContext& context) +{ + // Copy buffer from last cycle to output + _out_port->buffer(0)->copy(context, _buffer.get()); + + // Copy input from this cycle to buffer + _buffer->copy(context, _in_port->buffer(0).get()); +} + +} // namespace internals +} // namespace server +} // namespace ingen diff --git a/src/server/internals/BlockDelay.hpp b/src/server/internals/BlockDelay.hpp new file mode 100644 index 00000000..0e8fadce --- /dev/null +++ b/src/server/internals/BlockDelay.hpp @@ -0,0 +1,62 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_INTERNALS_BLOCKDELAY_HPP +#define INGEN_INTERNALS_BLOCKDELAY_HPP + +#include "BufferRef.hpp" +#include "InternalBlock.hpp" +#include "types.hpp" + +namespace ingen { +namespace server { + +class InputPort; +class OutputPort; +class InternalPlugin; +class BufferFactory; + +namespace internals { + +class BlockDelayNode : public InternalBlock +{ +public: + BlockDelayNode(InternalPlugin* plugin, + BufferFactory& bufs, + const Raul::Symbol& symbol, + bool polyphonic, + GraphImpl* parent, + SampleRate srate); + + ~BlockDelayNode(); + + void activate(BufferFactory& bufs) override; + + void run(RunContext& context) override; + + static InternalPlugin* internal_plugin(URIs& uris); + +private: + InputPort* _in_port; + OutputPort* _out_port; + BufferRef _buffer; +}; + +} // namespace server +} // namespace ingen +} // namespace internals + +#endif // INGEN_INTERNALS_BLOCKDELAY_HPP diff --git a/src/server/internals/Controller.cpp b/src/server/internals/Controller.cpp new file mode 100644 index 00000000..4b5735d7 --- /dev/null +++ b/src/server/internals/Controller.cpp @@ -0,0 +1,174 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cmath> + +#include "ingen/URIs.hpp" +#include "internals/Controller.hpp" +#include "lv2/atom/util.h" +#include "lv2/midi/midi.h" + +#include "Buffer.hpp" +#include "Engine.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "PostProcessor.hpp" +#include "RunContext.hpp" +#include "util.hpp" + +namespace ingen { +namespace server { +namespace internals { + +InternalPlugin* ControllerNode::internal_plugin(URIs& uris) { + return new InternalPlugin( + uris, URI(NS_INTERNALS "Controller"), Raul::Symbol("controller")); +} + +ControllerNode::ControllerNode(InternalPlugin* plugin, + BufferFactory& bufs, + const Raul::Symbol& symbol, + bool polyphonic, + GraphImpl* parent, + SampleRate srate) + : InternalBlock(plugin, symbol, false, parent, srate) + , _learning(false) +{ + const ingen::URIs& uris = bufs.uris(); + _ports = bufs.maid().make_managed<Ports>(7); + + const Atom zero = bufs.forge().make(0.0f); + const Atom one = bufs.forge().make(1.0f); + const Atom atom_Float = bufs.forge().make_urid(URI(LV2_ATOM__Float)); + + _midi_in_port = new InputPort(bufs, this, Raul::Symbol("input"), 0, 1, + PortType::ATOM, uris.atom_Sequence, Atom()); + _midi_in_port->set_property(uris.lv2_name, bufs.forge().alloc("Input")); + _midi_in_port->set_property(uris.atom_supports, + bufs.forge().make_urid(uris.midi_MidiEvent)); + _ports->at(0) = _midi_in_port; + + _midi_out_port = new OutputPort(bufs, this, Raul::Symbol("event"), 1, 1, + PortType::ATOM, uris.atom_Sequence, Atom()); + _midi_out_port->set_property(uris.lv2_name, bufs.forge().alloc("Event")); + _midi_out_port->set_property(uris.atom_supports, + bufs.forge().make_urid(uris.midi_MidiEvent)); + _ports->at(1) = _midi_out_port; + + _param_port = new InputPort(bufs, this, Raul::Symbol("controller"), 2, 1, + PortType::ATOM, uris.atom_Sequence, zero); + _param_port->set_property(uris.atom_supports, atom_Float); + _param_port->set_property(uris.lv2_minimum, zero); + _param_port->set_property(uris.lv2_maximum, bufs.forge().make(127.0f)); + _param_port->set_property(uris.lv2_portProperty, uris.lv2_integer); + _param_port->set_property(uris.lv2_name, bufs.forge().alloc("Controller")); + _ports->at(2) = _param_port; + + _log_port = new InputPort(bufs, this, Raul::Symbol("logarithmic"), 3, 1, + PortType::ATOM, uris.atom_Sequence, zero); + _log_port->set_property(uris.atom_supports, atom_Float); + _log_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _log_port->set_property(uris.lv2_name, bufs.forge().alloc("Logarithmic")); + _ports->at(3) = _log_port; + + _min_port = new InputPort(bufs, this, Raul::Symbol("minimum"), 4, 1, + PortType::ATOM, uris.atom_Sequence, zero); + _min_port->set_property(uris.atom_supports, atom_Float); + _min_port->set_property(uris.lv2_name, bufs.forge().alloc("Minimum")); + _ports->at(4) = _min_port; + + _max_port = new InputPort(bufs, this, Raul::Symbol("maximum"), 5, 1, + PortType::ATOM, uris.atom_Sequence, one); + _max_port->set_property(uris.atom_supports, atom_Float); + _max_port->set_property(uris.lv2_name, bufs.forge().alloc("Maximum")); + _ports->at(5) = _max_port; + + _audio_port = new OutputPort(bufs, this, Raul::Symbol("output"), 6, 1, + PortType::ATOM, uris.atom_Sequence, zero); + _audio_port->set_property(uris.atom_supports, atom_Float); + _audio_port->set_property(uris.lv2_name, bufs.forge().alloc("Output")); + _ports->at(6) = _audio_port; +} + +void +ControllerNode::run(RunContext& context) +{ + const BufferRef midi_in = _midi_in_port->buffer(0); + LV2_Atom_Sequence* seq = midi_in->get<LV2_Atom_Sequence>(); + const BufferRef midi_out = _midi_out_port->buffer(0); + LV2_ATOM_SEQUENCE_FOREACH(seq, ev) { + const uint8_t* buf = (const uint8_t*)LV2_ATOM_BODY(&ev->body); + if (ev->body.type == _midi_in_port->bufs().uris().midi_MidiEvent && + ev->body.size >= 3 && + lv2_midi_message_type(buf) == LV2_MIDI_MSG_CONTROLLER) { + if (control(context, buf[1], buf[2], ev->time.frames + context.start())) { + midi_out->append_event(ev->time.frames, &ev->body); + } + } + } +} + +bool +ControllerNode::control(RunContext& context, uint8_t control_num, uint8_t val, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + const uint32_t offset = time - context.start(); + + const Sample nval = (val / 127.0f); // normalized [0, 1] + + if (_learning) { + _param_port->set_control_value(context, time, control_num); + _param_port->force_monitor_update(); + _learning = false; + } else { + _param_port->update_values(offset, 0); + } + + if (control_num != _param_port->buffer(0)->value_at(offset)) { + return false; + } + + for (const auto& port : { _min_port, _max_port, _log_port }) { + port->update_values(offset, 0); + } + + const Sample min_port_val = _min_port->buffer(0)->value_at(offset); + const Sample max_port_val = _max_port->buffer(0)->value_at(offset); + const Sample log_port_val = _log_port->buffer(0)->value_at(offset); + + Sample scaled_value; + if (log_port_val > 0.0f) { + // haaaaack, stupid negatives and logarithms + Sample log_offset = 0; + if (min_port_val < 0) { + log_offset = fabs(min_port_val); + } + const Sample min = log(min_port_val + 1 + log_offset); + const Sample max = log(max_port_val + 1 + log_offset); + scaled_value = expf(nval * (max - min) + min) - 1 - log_offset; + } else { + scaled_value = ((nval) * (max_port_val - min_port_val)) + min_port_val; + } + + _audio_port->set_control_value(context, time, scaled_value); + + return true; +} + +} // namespace internals +} // namespace server +} // namespace ingen diff --git a/src/server/internals/Controller.hpp b/src/server/internals/Controller.hpp new file mode 100644 index 00000000..d138e690 --- /dev/null +++ b/src/server/internals/Controller.hpp @@ -0,0 +1,71 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_INTERNALS_CONTROLLER_HPP +#define INGEN_INTERNALS_CONTROLLER_HPP + +#include "InternalBlock.hpp" + +namespace ingen { +namespace server { + +class InputPort; +class OutputPort; +class InternalPlugin; + +namespace internals { + +/** MIDI control input block. + * + * Creating one of these nodes is how a user makes "MIDI Bindings". Note that + * this node will always be monophonic, the poly parameter is ignored. + * + * \ingroup engine + */ +class ControllerNode : public InternalBlock +{ +public: + ControllerNode(InternalPlugin* plugin, + BufferFactory& bufs, + const Raul::Symbol& symbol, + bool polyphonic, + GraphImpl* parent, + SampleRate srate); + + void run(RunContext& context) override; + + bool control(RunContext& context, uint8_t control_num, uint8_t val, FrameTime time); + + void learn() override { _learning = true; } + + static InternalPlugin* internal_plugin(URIs& uris); + +private: + InputPort* _midi_in_port; + OutputPort* _midi_out_port; + InputPort* _param_port; + InputPort* _log_port; + InputPort* _min_port; + InputPort* _max_port; + OutputPort* _audio_port; + bool _learning; +}; + +} // namespace server +} // namespace ingen +} // namespace internals + +#endif // INGEN_INTERNALS_CONTROLLER_HPP diff --git a/src/server/internals/Note.cpp b/src/server/internals/Note.cpp new file mode 100644 index 00000000..9d39f345 --- /dev/null +++ b/src/server/internals/Note.cpp @@ -0,0 +1,420 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cmath> + +#include "ingen/URIs.hpp" +#include "lv2/atom/util.h" +#include "lv2/midi/midi.h" +#include "raul/Array.hpp" +#include "raul/Maid.hpp" + +#include "Buffer.hpp" +#include "GraphImpl.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "RunContext.hpp" +#include "ingen_config.h" +#include "internals/Note.hpp" +#include "util.hpp" + +// #define NOTE_DEBUG 1 + +namespace ingen { +namespace server { +namespace internals { + +InternalPlugin* NoteNode::internal_plugin(URIs& uris) { + return new InternalPlugin( + uris, URI(NS_INTERNALS "Note"), Raul::Symbol("note")); +} + +NoteNode::NoteNode(InternalPlugin* plugin, + BufferFactory& bufs, + const Raul::Symbol& symbol, + bool polyphonic, + GraphImpl* parent, + SampleRate srate) + : InternalBlock(plugin, symbol, polyphonic, parent, srate) + , _voices(bufs.maid().make_managed<Voices>(_polyphony)) + , _sustain(false) +{ + const ingen::URIs& uris = bufs.uris(); + _ports = bufs.maid().make_managed<Ports>(8); + + const Atom zero = bufs.forge().make(0.0f); + const Atom one = bufs.forge().make(1.0f); + + _midi_in_port = new InputPort(bufs, this, Raul::Symbol("input"), 0, 1, + PortType::ATOM, uris.atom_Sequence, Atom()); + _midi_in_port->set_property(uris.lv2_name, bufs.forge().alloc("Input")); + _midi_in_port->set_property(uris.atom_supports, + bufs.forge().make_urid(uris.midi_MidiEvent)); + _ports->at(0) = _midi_in_port; + + _freq_port = new OutputPort(bufs, this, Raul::Symbol("frequency"), 1, _polyphony, + PortType::ATOM, uris.atom_Sequence, + bufs.forge().make(440.0f)); + _freq_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _freq_port->set_property(uris.lv2_name, bufs.forge().alloc("Frequency")); + _freq_port->set_property(uris.lv2_minimum, bufs.forge().make(16.0f)); + _freq_port->set_property(uris.lv2_maximum, bufs.forge().make(25088.0f)); + _ports->at(1) = _freq_port; + + _num_port = new OutputPort(bufs, this, Raul::Symbol("number"), 1, _polyphony, + PortType::ATOM, uris.atom_Sequence, zero); + _num_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _num_port->set_property(uris.lv2_minimum, zero); + _num_port->set_property(uris.lv2_maximum, bufs.forge().make(127.0f)); + _num_port->set_property(uris.lv2_portProperty, uris.lv2_integer); + _num_port->set_property(uris.lv2_name, bufs.forge().alloc("Number")); + _ports->at(2) = _num_port; + + _vel_port = new OutputPort(bufs, this, Raul::Symbol("velocity"), 2, _polyphony, + PortType::ATOM, uris.atom_Sequence, zero); + _vel_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _vel_port->set_property(uris.lv2_minimum, zero); + _vel_port->set_property(uris.lv2_maximum, one); + _vel_port->set_property(uris.lv2_name, bufs.forge().alloc("Velocity")); + _ports->at(3) = _vel_port; + + _gate_port = new OutputPort(bufs, this, Raul::Symbol("gate"), 3, _polyphony, + PortType::ATOM, uris.atom_Sequence, zero); + _gate_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _gate_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _gate_port->set_property(uris.lv2_name, bufs.forge().alloc("Gate")); + _ports->at(4) = _gate_port; + + _trig_port = new OutputPort(bufs, this, Raul::Symbol("trigger"), 4, _polyphony, + PortType::ATOM, uris.atom_Sequence, zero); + _trig_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _trig_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _trig_port->set_property(uris.lv2_name, bufs.forge().alloc("Trigger")); + _ports->at(5) = _trig_port; + + _bend_port = new OutputPort(bufs, this, Raul::Symbol("bend"), 5, _polyphony, + PortType::ATOM, uris.atom_Sequence, zero); + _bend_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _bend_port->set_property(uris.lv2_name, bufs.forge().alloc("Bender")); + _bend_port->set_property(uris.lv2_default, zero); + _bend_port->set_property(uris.lv2_minimum, bufs.forge().make(-1.0f)); + _bend_port->set_property(uris.lv2_maximum, one); + _ports->at(6) = _bend_port; + + _pressure_port = new OutputPort(bufs, this, Raul::Symbol("pressure"), 6, _polyphony, + PortType::ATOM, uris.atom_Sequence, zero); + _pressure_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _pressure_port->set_property(uris.lv2_name, bufs.forge().alloc("Pressure")); + _pressure_port->set_property(uris.lv2_default, zero); + _pressure_port->set_property(uris.lv2_minimum, zero); + _pressure_port->set_property(uris.lv2_maximum, one); + _ports->at(7) = _pressure_port; +} + +bool +NoteNode::prepare_poly(BufferFactory& bufs, uint32_t poly) +{ + if (!_polyphonic) { + return true; + } + + BlockImpl::prepare_poly(bufs, poly); + + if (_prepared_voices && poly <= _prepared_voices->size()) { + return true; + } + + _prepared_voices = bufs.maid().make_managed<Voices>( + poly, *_voices, Voice()); + + return true; +} + +bool +NoteNode::apply_poly(RunContext& context, uint32_t poly) +{ + if (!BlockImpl::apply_poly(context, poly)) { + return false; + } + + if (_prepared_voices) { + assert(_polyphony <= _prepared_voices->size()); + _voices = std::move(_prepared_voices); + } + assert(_polyphony <= _voices->size()); + + return true; +} + +void +NoteNode::run(RunContext& context) +{ + Buffer* const midi_in = _midi_in_port->buffer(0).get(); + LV2_Atom_Sequence* seq = midi_in->get<LV2_Atom_Sequence>(); + LV2_ATOM_SEQUENCE_FOREACH(seq, ev) { + const uint8_t* buf = (const uint8_t*)LV2_ATOM_BODY_CONST(&ev->body); + const FrameTime time = context.start() + (FrameTime)ev->time.frames; + if (ev->body.type == _midi_in_port->bufs().uris().midi_MidiEvent && + ev->body.size >= 3) { + switch (lv2_midi_message_type(buf)) { + case LV2_MIDI_MSG_NOTE_ON: + if (buf[2] == 0) { + note_off(context, buf[1], time); + } else { + note_on(context, buf[1], buf[2], time); + } + break; + case LV2_MIDI_MSG_NOTE_OFF: + note_off(context, buf[1], time); + break; + case LV2_MIDI_MSG_CONTROLLER: + switch (buf[1]) { + case LV2_MIDI_CTL_ALL_NOTES_OFF: + case LV2_MIDI_CTL_ALL_SOUNDS_OFF: + all_notes_off(context, time); + break; + case LV2_MIDI_CTL_SUSTAIN: + if (buf[2] > 63) { + sustain_on(context, time); + } else { + sustain_off(context, time); + } + break; + } + break; + case LV2_MIDI_MSG_BENDER: + bend(context, time, (((((uint16_t)buf[2] << 7) | buf[1]) - 8192.0f) + / 8192.0f)); + break; + case LV2_MIDI_MSG_CHANNEL_PRESSURE: + channel_pressure(context, time, buf[1] / 127.0f); + break; + case LV2_MIDI_MSG_NOTE_PRESSURE: + note_pressure(context, time, buf[1], buf[2] / 127.0f); + break; + default: + break; + } + } + } +} + +static inline float +note_to_freq(uint8_t num) +{ + static const float A4 = 440.0f; + return A4 * powf(2.0f, (float)(num - 57.0f) / 12.0f); +} + +void +NoteNode::note_on(RunContext& context, uint8_t note_num, uint8_t velocity, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + assert(note_num <= 127); + + Key* key = &_keys[note_num]; + Voice* voice = nullptr; + uint32_t voice_num = 0; + + if (key->state != Key::State::OFF) { + return; + } + + // Look for free voices + for (uint32_t i=0; i < _polyphony; ++i) { + if ((*_voices)[i].state == Voice::State::FREE) { + voice = &(*_voices)[i]; + voice_num = i; + break; + } + } + + // If we didn't find a free one, steal the oldest + if (voice == nullptr) { + voice_num = 0; + voice = &(*_voices)[0]; + FrameTime oldest_time = (*_voices)[0].time; + for (uint32_t i=1; i < _polyphony; ++i) { + if ((*_voices)[i].time < oldest_time) { + voice = &(*_voices)[i]; + voice_num = i; + oldest_time = voice->time; + } + } + } + assert(voice != nullptr); + assert(voice == &(*_voices)[voice_num]); + + // Update stolen key, if applicable + if (voice->state == Voice::State::ACTIVE) { + assert(_keys[voice->note].state == Key::State::ON_ASSIGNED); + assert(_keys[voice->note].voice == voice_num); + _keys[voice->note].state = Key::State::ON_UNASSIGNED; + } + + // Store key information for later reallocation on note off + key->state = Key::State::ON_ASSIGNED; + key->voice = voice_num; + key->time = time; + + // Check if we just triggered this voice at the same time + // (Double note-on at the same sample on the same voice) + const bool double_trigger = (voice->state == Voice::State::ACTIVE && + voice->time == time); + + // Trigger voice + voice->state = Voice::State::ACTIVE; + voice->note = note_num; + voice->time = time; + + assert(_keys[voice->note].state == Key::State::ON_ASSIGNED); + assert(_keys[voice->note].voice == voice_num); + + _freq_port->set_voice_value(context, voice_num, time, note_to_freq(note_num)); + _num_port->set_voice_value(context, voice_num, time, (float)note_num); + _vel_port->set_voice_value(context, voice_num, time, velocity / 127.0f); + _gate_port->set_voice_value(context, voice_num, time, 1.0f); + if (!double_trigger) { + _trig_port->set_voice_value(context, voice_num, time, 1.0f); + _trig_port->set_voice_value(context, voice_num, time + 1, 0.0f); + } + + assert(key->state == Key::State::ON_ASSIGNED); + assert(voice->state == Voice::State::ACTIVE); + assert(key->voice == voice_num); + assert((*_voices)[key->voice].note == note_num); +} + +void +NoteNode::note_off(RunContext& context, uint8_t note_num, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + Key* key = &_keys[note_num]; + + if (key->state == Key::State::ON_ASSIGNED) { + // Assigned key, turn off voice and key + if ((*_voices)[key->voice].state == Voice::State::ACTIVE) { + assert((*_voices)[key->voice].note == note_num); + if ( ! _sustain) { + free_voice(context, key->voice, time); + } else { + (*_voices)[key->voice].state = Voice::State::HOLDING; + } + } + } + + key->state = Key::State::OFF; +} + +void +NoteNode::free_voice(RunContext& context, uint32_t voice, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + // Find a key to reassign to the freed voice (the newest, if there is one) + Key* replace_key = nullptr; + uint8_t replace_key_num = 0; + + for (uint8_t i = 0; i <= 127; ++i) { + if (_keys[i].state == Key::State::ON_UNASSIGNED) { + if (replace_key == nullptr || _keys[i].time > replace_key->time) { + replace_key = &_keys[i]; + replace_key_num = i; + } + } + } + + if (replace_key != nullptr) { // Found a key to assign to freed voice + assert(&_keys[replace_key_num] == replace_key); + assert(replace_key->state == Key::State::ON_UNASSIGNED); + + // Change the freq but leave the gate high and don't retrigger + _freq_port->set_voice_value(context, voice, time, note_to_freq(replace_key_num)); + _num_port->set_voice_value(context, voice, time, replace_key_num); + + replace_key->state = Key::State::ON_ASSIGNED; + replace_key->voice = voice; + _keys[(*_voices)[voice].note].state = Key::State::ON_UNASSIGNED; + (*_voices)[voice].note = replace_key_num; + (*_voices)[voice].state = Voice::State::ACTIVE; + } else { + // No new note for voice, deactivate (set gate low) + _gate_port->set_voice_value(context, voice, time, 0.0f); + (*_voices)[voice].state = Voice::State::FREE; + } +} + +void +NoteNode::all_notes_off(RunContext& context, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + // FIXME: set all keys to Key::OFF? + + for (uint32_t i = 0; i < _polyphony; ++i) { + _gate_port->set_voice_value(context, i, time, 0.0f); + (*_voices)[i].state = Voice::State::FREE; + } +} + +void +NoteNode::sustain_on(RunContext& context, FrameTime time) +{ + _sustain = true; +} + +void +NoteNode::sustain_off(RunContext& context, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + _sustain = false; + + for (uint32_t i=0; i < _polyphony; ++i) { + if ((*_voices)[i].state == Voice::State::HOLDING) { + free_voice(context, i, time); + } + } +} + +void +NoteNode::bend(RunContext& context, FrameTime time, float amount) +{ + _bend_port->set_control_value(context, time, amount); +} + +void +NoteNode::note_pressure(RunContext& context, FrameTime time, uint8_t note_num, float amount) +{ + for (uint32_t i=0; i < _polyphony; ++i) { + if ((*_voices)[i].state != Voice::State::FREE && (*_voices)[i].note == note_num) { + _pressure_port->set_voice_value(context, i, time, amount); + return; + } + } +} + +void +NoteNode::channel_pressure(RunContext& context, FrameTime time, float amount) +{ + _pressure_port->set_control_value(context, time, amount); +} + +} // namespace internals +} // namespace server +} // namespace ingen diff --git a/src/server/internals/Note.hpp b/src/server/internals/Note.hpp new file mode 100644 index 00000000..fb935179 --- /dev/null +++ b/src/server/internals/Note.hpp @@ -0,0 +1,109 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_INTERNALS_NOTE_HPP +#define INGEN_INTERNALS_NOTE_HPP + +#include "InternalBlock.hpp" +#include "types.hpp" + +namespace ingen { +namespace server { + +class InputPort; +class OutputPort; +class InternalPlugin; + +namespace internals { + +/** MIDI note input block. + * + * For pitched instruments like keyboard, etc. + * + * \ingroup engine + */ +class NoteNode : public InternalBlock +{ +public: + NoteNode(InternalPlugin* plugin, + BufferFactory& bufs, + const Raul::Symbol& symbol, + bool polyphonic, + GraphImpl* parent, + SampleRate srate); + + bool prepare_poly(BufferFactory& bufs, uint32_t poly) override; + bool apply_poly(RunContext& context, uint32_t poly) override; + + void run(RunContext& context) override; + + void note_on(RunContext& context, uint8_t note_num, uint8_t velocity, FrameTime time); + void note_off(RunContext& context, uint8_t note_num, FrameTime time); + void all_notes_off(RunContext& context, FrameTime time); + + void sustain_on(RunContext& context, FrameTime time); + void sustain_off(RunContext& context, FrameTime time); + + void bend(RunContext& context, FrameTime time, float amount); + void note_pressure(RunContext& context, FrameTime time, uint8_t note_num, float amount); + void channel_pressure(RunContext& context, FrameTime time, float amount); + + static InternalPlugin* internal_plugin(URIs& uris); + +private: + /** Key, one for each key on the keyboard */ + struct Key { + enum class State { OFF, ON_ASSIGNED, ON_UNASSIGNED }; + Key() : state(State::OFF), voice(0), time(0) {} + State state; + uint32_t voice; + SampleCount time; + }; + + /** Voice, one of these always exists for each voice */ + struct Voice { + enum class State { FREE, ACTIVE, HOLDING }; + Voice() : state(State::FREE), note(0), time(0) {} + State state; + uint8_t note; + SampleCount time; + }; + + typedef Raul::Array<Voice> Voices; + + void free_voice(RunContext& context, uint32_t voice, FrameTime time); + + MPtr<Voices> _voices; + MPtr<Voices> _prepared_voices; + + Key _keys[128]; + bool _sustain; ///< Whether or not hold pedal is depressed + + InputPort* _midi_in_port; + OutputPort* _freq_port; + OutputPort* _num_port; + OutputPort* _vel_port; + OutputPort* _gate_port; + OutputPort* _trig_port; + OutputPort* _bend_port; + OutputPort* _pressure_port; +}; + +} // namespace server +} // namespace ingen +} // namespace internals + +#endif // INGEN_INTERNALS_NOTE_HPP diff --git a/src/server/internals/Time.cpp b/src/server/internals/Time.cpp new file mode 100644 index 00000000..c35aa02b --- /dev/null +++ b/src/server/internals/Time.cpp @@ -0,0 +1,78 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "ingen/URIs.hpp" +#include "lv2/atom/util.h" +#include "lv2/midi/midi.h" + +#include "Buffer.hpp" +#include "Driver.hpp" +#include "Engine.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "RunContext.hpp" +#include "internals/Time.hpp" +#include "util.hpp" + +namespace ingen { +namespace server { +namespace internals { + +InternalPlugin* TimeNode::internal_plugin(URIs& uris) { + return new InternalPlugin( + uris, URI(NS_INTERNALS "Time"), Raul::Symbol("time")); +} + +TimeNode::TimeNode(InternalPlugin* plugin, + BufferFactory& bufs, + const Raul::Symbol& symbol, + bool polyphonic, + GraphImpl* parent, + SampleRate srate) + : InternalBlock(plugin, symbol, false, parent, srate) +{ + const ingen::URIs& uris = bufs.uris(); + _ports = bufs.maid().make_managed<Ports>(1); + + _notify_port = new OutputPort( + bufs, this, Raul::Symbol("notify"), 0, 1, + PortType::ATOM, uris.atom_Sequence, Atom(), 1024); + _notify_port->set_property(uris.lv2_name, bufs.forge().alloc("Notify")); + _notify_port->set_property(uris.atom_supports, + bufs.forge().make_urid(uris.time_Position)); + _ports->at(0) = _notify_port; +} + +void +TimeNode::run(RunContext& context) +{ + BufferRef buf = _notify_port->buffer(0); + LV2_Atom_Sequence* seq = buf->get<LV2_Atom_Sequence>(); + + // Initialise output to the empty sequence + seq->atom.type = _notify_port->bufs().uris().atom_Sequence; + seq->atom.size = sizeof(LV2_Atom_Sequence_Body); + seq->body.unit = 0; + seq->body.pad = 0; + + // Ask the driver to append any time events for this cycle + context.engine().driver()->append_time_events( + context, *_notify_port->buffer(0)); +} + +} // namespace internals +} // namespace server +} // namespace ingen diff --git a/src/server/internals/Time.hpp b/src/server/internals/Time.hpp new file mode 100644 index 00000000..958cd239 --- /dev/null +++ b/src/server/internals/Time.hpp @@ -0,0 +1,59 @@ +/* + This file is part of Ingen. + Copyright 2007-2015 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_INTERNALS_TIME_HPP +#define INGEN_INTERNALS_TIME_HPP + +#include "InternalBlock.hpp" + +namespace ingen { +namespace server { + +class InputPort; +class OutputPort; +class InternalPlugin; + +namespace internals { + +/** Time information block. + * + * This sends messages whenever the transport speed or tempo changes. + * + * \ingroup engine + */ +class TimeNode : public InternalBlock +{ +public: + TimeNode(InternalPlugin* plugin, + BufferFactory& bufs, + const Raul::Symbol& symbol, + bool polyphonic, + GraphImpl* parent, + SampleRate srate); + + void run(RunContext& context) override; + + static InternalPlugin* internal_plugin(URIs& uris); + +private: + OutputPort* _notify_port; +}; + +} // namespace server +} // namespace ingen +} // namespace internals + +#endif // INGEN_INTERNALS_TIME_HPP diff --git a/src/server/internals/Trigger.cpp b/src/server/internals/Trigger.cpp new file mode 100644 index 00000000..793e508d --- /dev/null +++ b/src/server/internals/Trigger.cpp @@ -0,0 +1,187 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cmath> + +#include "ingen/URIs.hpp" +#include "lv2/atom/util.h" +#include "lv2/midi/midi.h" + +#include "Buffer.hpp" +#include "Engine.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "RunContext.hpp" +#include "ingen_config.h" +#include "internals/Trigger.hpp" +#include "util.hpp" + +namespace ingen { +namespace server { +namespace internals { + +InternalPlugin* TriggerNode::internal_plugin(URIs& uris) { + return new InternalPlugin( + uris, URI(NS_INTERNALS "Trigger"), Raul::Symbol("trigger")); +} + +TriggerNode::TriggerNode(InternalPlugin* plugin, + BufferFactory& bufs, + const Raul::Symbol& symbol, + bool polyphonic, + GraphImpl* parent, + SampleRate srate) + : InternalBlock(plugin, symbol, false, parent, srate) + , _learning(false) +{ + const ingen::URIs& uris = bufs.uris(); + _ports = bufs.maid().make_managed<Ports>(6); + + const Atom zero = bufs.forge().make(0.0f); + + _midi_in_port = new InputPort(bufs, this, Raul::Symbol("input"), 0, 1, + PortType::ATOM, uris.atom_Sequence, Atom()); + _midi_in_port->set_property(uris.lv2_name, bufs.forge().alloc("Input")); + _midi_in_port->set_property(uris.atom_supports, + bufs.forge().make_urid(uris.midi_MidiEvent)); + _ports->at(0) = _midi_in_port; + + _midi_out_port = new OutputPort(bufs, this, Raul::Symbol("event"), 1, 1, + PortType::ATOM, uris.atom_Sequence, Atom()); + _midi_out_port->set_property(uris.lv2_name, bufs.forge().alloc("Event")); + _midi_out_port->set_property(uris.atom_supports, + bufs.forge().make_urid(uris.midi_MidiEvent)); + _ports->at(1) = _midi_out_port; + + _note_port = new InputPort(bufs, this, Raul::Symbol("note"), 2, 1, + PortType::ATOM, uris.atom_Sequence, + bufs.forge().make(60.0f)); + _note_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _note_port->set_property(uris.lv2_minimum, zero); + _note_port->set_property(uris.lv2_maximum, bufs.forge().make(127.0f)); + _note_port->set_property(uris.lv2_portProperty, uris.lv2_integer); + _note_port->set_property(uris.lv2_name, bufs.forge().alloc("Note")); + _ports->at(2) = _note_port; + + _gate_port = new OutputPort(bufs, this, Raul::Symbol("gate"), 3, 1, + PortType::ATOM, uris.atom_Sequence, zero); + _gate_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _gate_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _gate_port->set_property(uris.lv2_name, bufs.forge().alloc("Gate")); + _ports->at(3) = _gate_port; + + _trig_port = new OutputPort(bufs, this, Raul::Symbol("trigger"), 4, 1, + PortType::ATOM, uris.atom_Sequence, zero); + _trig_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _trig_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _trig_port->set_property(uris.lv2_name, bufs.forge().alloc("Trigger")); + _ports->at(4) = _trig_port; + + _vel_port = new OutputPort(bufs, this, Raul::Symbol("velocity"), 5, 1, + PortType::ATOM, uris.atom_Sequence, zero); + _vel_port->set_property(uris.atom_supports, bufs.uris().atom_Float); + _vel_port->set_property(uris.lv2_minimum, zero); + _vel_port->set_property(uris.lv2_maximum, bufs.forge().make(1.0f)); + _vel_port->set_property(uris.lv2_name, bufs.forge().alloc("Velocity")); + _ports->at(5) = _vel_port; +} + +void +TriggerNode::run(RunContext& context) +{ + const BufferRef midi_in = _midi_in_port->buffer(0); + LV2_Atom_Sequence* const seq = midi_in->get<LV2_Atom_Sequence>(); + const BufferRef midi_out = _midi_out_port->buffer(0); + + // Initialise output to the empty sequence + midi_out->prepare_write(context); + + LV2_ATOM_SEQUENCE_FOREACH(seq, ev) { + const int64_t t = ev->time.frames; + const uint8_t* buf = (const uint8_t*)LV2_ATOM_BODY(&ev->body); + bool emit = false; + if (ev->body.type == _midi_in_port->bufs().uris().midi_MidiEvent && + ev->body.size >= 3) { + const FrameTime time = context.start() + t; + switch (lv2_midi_message_type(buf)) { + case LV2_MIDI_MSG_NOTE_ON: + if (buf[2] == 0) { + emit = note_off(context, buf[1], time); + } else { + emit = note_on(context, buf[1], buf[2], time); + } + break; + case LV2_MIDI_MSG_NOTE_OFF: + emit = note_off(context, buf[1], time); + break; + case LV2_MIDI_MSG_CONTROLLER: + switch (buf[1]) { + case LV2_MIDI_CTL_ALL_NOTES_OFF: + case LV2_MIDI_CTL_ALL_SOUNDS_OFF: + _gate_port->set_control_value(context, time, 0.0f); + emit = true; + } + default: + break; + } + } + + if (emit) { + midi_out->append_event(t, &ev->body); + } + } +} + +bool +TriggerNode::note_on(RunContext& context, uint8_t note_num, uint8_t velocity, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + const uint32_t offset = time - context.start(); + + if (_learning) { + _note_port->set_control_value(context, time, (float)note_num); + _note_port->force_monitor_update(); + _learning = false; + } + + if (note_num == lrintf(_note_port->buffer(0)->value_at(offset))) { + _gate_port->set_control_value(context, time, 1.0f); + _trig_port->set_control_value(context, time, 1.0f); + _trig_port->set_control_value(context, time + 1, 0.0f); + _vel_port->set_control_value(context, time, velocity / 127.0f); + return true; + } + return false; +} + +bool +TriggerNode::note_off(RunContext& context, uint8_t note_num, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + const uint32_t offset = time - context.start(); + + if (note_num == lrintf(_note_port->buffer(0)->value_at(offset))) { + _gate_port->set_control_value(context, time, 0.0f); + return true; + } + + return false; +} + +} // namespace internals +} // namespace server +} // namespace ingen diff --git a/src/server/internals/Trigger.hpp b/src/server/internals/Trigger.hpp new file mode 100644 index 00000000..98d50f2c --- /dev/null +++ b/src/server/internals/Trigger.hpp @@ -0,0 +1,75 @@ +/* + This file is part of Ingen. + Copyright 2007-2016 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_INTERNALS_TRIGGER_HPP +#define INGEN_INTERNALS_TRIGGER_HPP + +#include "InternalBlock.hpp" + +namespace ingen { +namespace server { + +class InputPort; +class OutputPort; +class InternalPlugin; + +namespace internals { + +/** MIDI trigger input block. + * + * Just has a gate, for drums etc. A control port is used to select + * which note number is responded to. + * + * Note that this block is always monophonic, the poly parameter is ignored. + * (Should that change?) + * + * \ingroup engine + */ +class TriggerNode : public InternalBlock +{ +public: + TriggerNode(InternalPlugin* plugin, + BufferFactory& bufs, + const Raul::Symbol& symbol, + bool polyphonic, + GraphImpl* parent, + SampleRate srate); + + void run(RunContext& context) override; + + bool note_on(RunContext& context, uint8_t note_num, uint8_t velocity, FrameTime time); + bool note_off(RunContext& context, uint8_t note_num, FrameTime time); + + void learn() override { _learning = true; } + + static InternalPlugin* internal_plugin(URIs& uris); + +private: + bool _learning; + + InputPort* _midi_in_port; + OutputPort* _midi_out_port; + InputPort* _note_port; + OutputPort* _gate_port; + OutputPort* _trig_port; + OutputPort* _vel_port; +}; + +} // namespace server +} // namespace ingen +} // namespace internals + +#endif // INGEN_INTERNALS_TRIGGER_HPP |