diff options
author | David Robillard <d@drobilla.net> | 2011-04-20 16:26:40 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2011-04-20 16:26:40 +0000 |
commit | 138a87e915ad3aff184730415105f94c874174bf (patch) | |
tree | 0d942bdddfdbcc3d969b4fce5592e770ab851b86 /src/server/internals | |
parent | 1f1758f4dda0ddaf01c0b1d3a756f9db8ddc979d (diff) | |
download | ingen-138a87e915ad3aff184730415105f94c874174bf.tar.gz ingen-138a87e915ad3aff184730415105f94c874174bf.tar.bz2 ingen-138a87e915ad3aff184730415105f94c874174bf.zip |
Rename Ingen::Engine to Ingen::Server (hopefully avoid odd name clases and fix #675).
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@3184 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/server/internals')
-rw-r--r-- | src/server/internals/Controller.cpp | 147 | ||||
-rw-r--r-- | src/server/internals/Controller.hpp | 74 | ||||
-rw-r--r-- | src/server/internals/Delay.cpp | 208 | ||||
-rw-r--r-- | src/server/internals/Delay.hpp | 78 | ||||
-rw-r--r-- | src/server/internals/Note.cpp | 416 | ||||
-rw-r--r-- | src/server/internals/Note.hpp | 101 | ||||
-rw-r--r-- | src/server/internals/Trigger.cpp | 168 | ||||
-rw-r--r-- | src/server/internals/Trigger.hpp | 77 |
8 files changed, 1269 insertions, 0 deletions
diff --git a/src/server/internals/Controller.cpp b/src/server/internals/Controller.cpp new file mode 100644 index 00000000..3d477de6 --- /dev/null +++ b/src/server/internals/Controller.cpp @@ -0,0 +1,147 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) 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 General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <math.h> +#include "raul/midi_events.h" +#include "shared/LV2URIMap.hpp" +#include "internals/Controller.hpp" +#include "PostProcessor.hpp" +#include "events/SendPortValue.hpp" +#include "InputPort.hpp" +#include "OutputPort.hpp" +#include "InternalPlugin.hpp" +#include "AudioBuffer.hpp" +#include "ProcessContext.hpp" +#include "EventBuffer.hpp" +#include "util.hpp" + +using namespace std; + +namespace Ingen { +namespace Server { +namespace Internals { + +InternalPlugin* ControllerNode::internal_plugin(Shared::LV2URIMap& uris) { + return new InternalPlugin(uris, NS_INTERNALS "Controller", "controller"); +} + +ControllerNode::ControllerNode(InternalPlugin* plugin, + BufferFactory& bufs, + const string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate) + : NodeImpl(plugin, path, false, parent, srate) + , _learning(false) +{ + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + _ports = new Raul::Array<PortImpl*>(6); + + _midi_in_port = new InputPort(bufs, this, "input", 0, 1, PortType::EVENTS, Raul::Atom()); + _midi_in_port->set_property(uris.lv2_name, "Input"); + _ports->at(0) = _midi_in_port; + + _param_port = new InputPort(bufs, this, "controller", 1, 1, PortType::CONTROL, 0.0f); + _param_port->set_property(uris.lv2_minimum, 0.0f); + _param_port->set_property(uris.lv2_maximum, 127.0f); + _param_port->set_property(uris.lv2_integer, true); + _param_port->set_property(uris.lv2_name, "Controller"); + _ports->at(1) = _param_port; + + _log_port = new InputPort(bufs, this, "logarithmic", 2, 1, PortType::CONTROL, 0.0f); + _log_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _log_port->set_property(uris.lv2_name, "Logarithmic"); + _ports->at(2) = _log_port; + + _min_port = new InputPort(bufs, this, "minimum", 3, 1, PortType::CONTROL, 0.0f); + _min_port->set_property(uris.lv2_name, "Minimum"); + _ports->at(3) = _min_port; + + _max_port = new InputPort(bufs, this, "maximum", 4, 1, PortType::CONTROL, 1.0f); + _max_port->set_property(uris.lv2_name, "Maximum"); + _ports->at(4) = _max_port; + + _audio_port = new OutputPort(bufs, this, "ar_output", 5, 1, PortType::AUDIO, 0.0f); + _audio_port->set_property(uris.lv2_name, "Output"); + _ports->at(5) = _audio_port; +} + +void +ControllerNode::process(ProcessContext& context) +{ + NodeImpl::pre_process(context); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* buf = NULL; + + EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0).get(); + + midi_in->rewind(); + + while (midi_in->get_event(&frames, &subframes, &type, &size, &buf)) { + // FIXME: type + if (size >= 3 && (buf[0] & 0xF0) == MIDI_CMD_CONTROL) + control(context, buf[1], buf[2], frames + context.start()); + + midi_in->increment(); + } + + NodeImpl::post_process(context); +} + +void +ControllerNode::control(ProcessContext& context, uint8_t control_num, uint8_t val, FrameTime time) +{ + Sample scaled_value; + + const Sample nval = (val / 127.0f); // normalized [0, 1] + + if (_learning) { + _param_port->set_value(control_num); + ((AudioBuffer*)_param_port->buffer(0).get())->set_value( + (float)control_num, context.start(), context.end()); + _param_port->broadcast_value(context, true); + _learning = false; + } + + const Sample min_port_val = ((AudioBuffer*)_min_port->buffer(0).get())->value_at(0); + const Sample max_port_val = ((AudioBuffer*)_max_port->buffer(0).get())->value_at(0); + const Sample log_port_val = ((AudioBuffer*)_log_port->buffer(0).get())->value_at(0); + + 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; + } + + if (control_num == ((AudioBuffer*)_param_port->buffer(0).get())->value_at(0)) + ((AudioBuffer*)_audio_port->buffer(0).get())->set_value(scaled_value, context.start(), time); +} + +} // 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..30ef663f --- /dev/null +++ b/src/server/internals/Controller.hpp @@ -0,0 +1,74 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) 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 General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef INGEN_INTERNALS_CONTROLLER_HPP +#define INGEN_INTERNALS_CONTROLLER_HPP + +#include <string> +#include "NodeImpl.hpp" + +namespace Ingen { +namespace Server { + +class InputPort; +class OutputPort; +class InternalPlugin; + +namespace Internals { + +/** MIDI control input node. + * + * 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 NodeImpl +{ +public: + ControllerNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate); + + void process(ProcessContext& context); + + void control(ProcessContext& context, uint8_t control_num, uint8_t val, FrameTime time); + + void learn() { _learning = true; } + + static InternalPlugin* internal_plugin(Shared::LV2URIMap& uris); + +private: + bool _learning; + + InputPort* _midi_in_port; + InputPort* _param_port; + InputPort* _log_port; + InputPort* _min_port; + InputPort* _max_port; + OutputPort* _audio_port; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Internals + +#endif // INGEN_INTERNALS_CONTROLLER_HPP diff --git a/src/server/internals/Delay.cpp b/src/server/internals/Delay.cpp new file mode 100644 index 00000000..5c2758da --- /dev/null +++ b/src/server/internals/Delay.cpp @@ -0,0 +1,208 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) 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 General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cmath> +#include <limits.h> +#include "raul/log.hpp" +#include "raul/Array.hpp" +#include "raul/Maid.hpp" +#include "raul/midi_events.h" +#include "shared/LV2URIMap.hpp" +#include "internals/Delay.hpp" +#include "AudioBuffer.hpp" +#include "Driver.hpp" +#include "EventBuffer.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "ProcessContext.hpp" +#include "ingen-config.h" +#include "util.hpp" + +#define LOG(s) s << "[DelayNode] " + +#define CALC_DELAY(delaytime) \ + (f_clamp (delaytime * (float)sample_rate, 1.0f, (float)(buffer_mask + 1))) + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Internals { + +static const float MAX_DELAY_SECONDS = 8.0f; + +InternalPlugin* DelayNode::internal_plugin(Shared::LV2URIMap& uris) { + return new InternalPlugin(uris, NS_INTERNALS "Delay", "delay"); +} + +DelayNode::DelayNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate) + : NodeImpl(plugin, path, polyphonic, parent, srate) + , _buffer(0) + , _buffer_length(0) + , _buffer_mask(0) + , _write_phase(0) +{ + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + _ports = new Raul::Array<PortImpl*>(3); + + const float default_delay = 1.0f; + _last_delay_time = default_delay; + _delay_samples = default_delay; + + _delay_port = new InputPort(bufs, this, "delay", 1, _polyphony, PortType::CONTROL, default_delay); + _delay_port->set_property(uris.lv2_name, "Delay"); + _delay_port->set_property(uris.lv2_default, default_delay); + _delay_port->set_property(uris.lv2_minimum, (float)(1.0/(double)srate)); + _delay_port->set_property(uris.lv2_maximum, MAX_DELAY_SECONDS); + _ports->at(0) = _delay_port; + + _in_port = new InputPort(bufs, this, "in", 0, 1, PortType::AUDIO, 0.0f); + _in_port->set_property(uris.lv2_name, "Input"); + _ports->at(1) = _in_port; + + _out_port = new OutputPort(bufs, this, "out", 0, 1, PortType::AUDIO, 0.0f); + _out_port->set_property(uris.lv2_name, "Output"); + _ports->at(2) = _out_port; + + //_buffer = bufs.get(PortType::AUDIO, bufs.audio_buffer_size(buffer_length_frames), true); + +} + +DelayNode::~DelayNode() +{ + //_buffer.reset(); + free(_buffer); +} + +void +DelayNode::activate(BufferFactory& bufs) +{ + NodeImpl::activate(bufs); + const SampleCount min_size = MAX_DELAY_SECONDS * _srate; + + // Smallest power of two larger than min_size + SampleCount size = 1; + while (size < min_size) + size <<= 1; + + _buffer = (float*)calloc(size, sizeof(float)); + _buffer_mask = size - 1; + _buffer_length = size; + //_buffer->clear(); + _write_phase = 0; +} + +static inline float f_clamp(float x, float a, float b) +{ + const float x1 = fabs(x - a); + const float x2 = fabs(x - b); + + x = x1 + a + b; + x -= x2; + x *= 0.5; + + return x; +} + +static inline float cube_interp(const float fr, const float inm1, const float + in, const float inp1, const float inp2) +{ + return in + 0.5f * fr * (inp1 - inm1 + + fr * (4.0f * inp1 + 2.0f * inm1 - 5.0f * in - inp2 + + fr * (3.0f * (in - inp1) - inm1 + inp2))); +} + +void +DelayNode::process(ProcessContext& context) +{ + AudioBuffer* const delay_buf = (AudioBuffer*)_delay_port->buffer(0).get(); + AudioBuffer* const in_buf = (AudioBuffer*)_in_port->buffer(0).get(); + AudioBuffer* const out_buf = (AudioBuffer*)_out_port->buffer(0).get(); + + NodeImpl::pre_process(context); + + DelayNode* plugin_data = this; + + const float* const in = in_buf->data(); + float* const out = out_buf->data(); + const float delay_time = delay_buf->data()[0]; + const uint32_t buffer_mask = plugin_data->_buffer_mask; + const unsigned int sample_rate = plugin_data->_srate; + float delay_samples = plugin_data->_delay_samples; + long write_phase = plugin_data->_write_phase; + const uint32_t sample_count = context.nframes(); + + if (write_phase == 0) { + _last_delay_time = delay_time; + _delay_samples = delay_samples = CALC_DELAY(delay_time); + } + + if (delay_time == _last_delay_time) { + const long idelay_samples = (long)delay_samples; + const float frac = delay_samples - idelay_samples; + + for (uint32_t i = 0; i < sample_count; i++) { + long read_phase = write_phase - (long)delay_samples; + const float read = cube_interp(frac, + buffer_at(read_phase - 1), + buffer_at(read_phase), + buffer_at(read_phase + 1), + buffer_at(read_phase + 2)); + buffer_at(write_phase++) = in[i]; + out[i] = read; + } + } else { + const float next_delay_samples = CALC_DELAY(delay_time); + const float delay_samples_slope = (next_delay_samples - delay_samples) / sample_count; + + for (uint32_t i = 0; i < sample_count; i++) { + delay_samples += delay_samples_slope; + write_phase++; + const long read_phase = write_phase - (long)delay_samples; + const long idelay_samples = (long)delay_samples; + const float frac = delay_samples - idelay_samples; + const float read = cube_interp(frac, + buffer_at(read_phase - 1), + buffer_at(read_phase), + buffer_at(read_phase + 1), + buffer_at(read_phase + 2)); + buffer_at(write_phase) = in[i]; + out[i] = read; + } + + _last_delay_time = delay_time; + _delay_samples = delay_samples; + } + + _write_phase = write_phase; + + NodeImpl::post_process(context); +} + +} // namespace Internals +} // namespace Server +} // namespace Ingen + diff --git a/src/server/internals/Delay.hpp b/src/server/internals/Delay.hpp new file mode 100644 index 00000000..7456d7cc --- /dev/null +++ b/src/server/internals/Delay.hpp @@ -0,0 +1,78 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) 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 General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef INGEN_INTERNALS_DELAY_HPP +#define INGEN_INTERNALS_DELAY_HPP + +#include <string> +#include <math.h> +#include "types.hpp" +#include "NodeImpl.hpp" + +namespace Ingen { +namespace Server { + +class InputPort; +class OutputPort; +class InternalPlugin; +class BufferFactory; + +namespace Internals { + +class DelayNode : public NodeImpl +{ +public: + DelayNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate); + + ~DelayNode(); + + void activate(BufferFactory& bufs); + + void process(ProcessContext& context); + + static InternalPlugin* internal_plugin(Shared::LV2URIMap& uris); + + float delay_samples() const { return _delay_samples; } + +private: + inline float& buffer_at(long phase) const { return _buffer[phase & _buffer_mask]; } + + InputPort* _delay_port; + InputPort* _in_port; + OutputPort* _out_port; + + typedef long Phase; + + float* _buffer; + uint32_t _buffer_length; + uint32_t _buffer_mask; + Phase _write_phase; + float _last_delay_time; + float _delay_samples; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Internals + +#endif // INGEN_INTERNALS_DELAY_HPP diff --git a/src/server/internals/Note.cpp b/src/server/internals/Note.cpp new file mode 100644 index 00000000..f28dacc1 --- /dev/null +++ b/src/server/internals/Note.cpp @@ -0,0 +1,416 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) 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 General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cmath> +#include "raul/log.hpp" +#include "raul/Array.hpp" +#include "raul/Maid.hpp" +#include "raul/midi_events.h" +#include "shared/LV2URIMap.hpp" +#include "internals/Note.hpp" +#include "AudioBuffer.hpp" +#include "Driver.hpp" +#include "EventBuffer.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "ProcessContext.hpp" +#include "util.hpp" +#include "ingen-config.h" + +#define LOG(s) s << "[NoteNode] " + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Internals { + +InternalPlugin* NoteNode::internal_plugin(Shared::LV2URIMap& uris) { + return new InternalPlugin(uris, NS_INTERNALS "Note", "note"); +} + +NoteNode::NoteNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate) + : NodeImpl(plugin, path, polyphonic, parent, srate) + , _voices(new Raul::Array<Voice>(_polyphony)) + , _prepared_voices(NULL) + , _sustain(false) +{ + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + _ports = new Raul::Array<PortImpl*>(5); + + _midi_in_port = new InputPort(bufs, this, "input", 0, 1, PortType::EVENTS, Raul::Atom()); + _midi_in_port->set_property(uris.lv2_name, "Input"); + _ports->at(0) = _midi_in_port; + + _freq_port = new OutputPort(bufs, this, "frequency", 1, _polyphony, PortType::AUDIO, 440.0f); + _freq_port->set_property(uris.lv2_name, "Frequency"); + _ports->at(1) = _freq_port; + + _vel_port = new OutputPort(bufs, this, "velocity", 2, _polyphony, PortType::AUDIO, 0.0f); + _vel_port->set_property(uris.lv2_minimum, 0.0f); + _vel_port->set_property(uris.lv2_maximum, 1.0f); + _vel_port->set_property(uris.lv2_name, "Velocity"); + _ports->at(2) = _vel_port; + + _gate_port = new OutputPort(bufs, this, "gate", 3, _polyphony, PortType::AUDIO, 0.0f); + _gate_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _gate_port->set_property(uris.lv2_name, "Gate"); + _ports->at(3) = _gate_port; + + _trig_port = new OutputPort(bufs, this, "trigger", 4, _polyphony, PortType::AUDIO, 0.0f); + _trig_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _trig_port->set_property(uris.lv2_name, "Trigger"); + _ports->at(4) = _trig_port; +} + +NoteNode::~NoteNode() +{ + delete _voices; +} + +bool +NoteNode::prepare_poly(BufferFactory& bufs, uint32_t poly) +{ + if (!_polyphonic) + return true; + + NodeImpl::prepare_poly(bufs, poly); + + if (_prepared_voices && poly <= _prepared_voices->size()) + return true; + + _prepared_voices = new Raul::Array<Voice>(poly, *_voices, Voice()); + + return true; +} + +bool +NoteNode::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + if (!NodeImpl::apply_poly(maid, poly)) + return false; + + if (_prepared_voices) { + assert(_polyphony <= _prepared_voices->size()); + maid.push(_voices); + _voices = _prepared_voices; + _prepared_voices = NULL; + } + assert(_polyphony <= _voices->size()); + + return true; +} + +void +NoteNode::process(ProcessContext& context) +{ + EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0).get(); + NodeImpl::pre_process(context); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* buf = NULL; + + midi_in->rewind(); + + if (midi_in->event_count() > 0) + for (midi_in->rewind(); midi_in->get_event(&frames, &subframes, &type, &size, &buf); + midi_in->increment()) { + +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "EVENT TYPE " << type << " @ " << frames << "." << subframes << ": "; + for (uint16_t i = 0; i < size; ++i) + debug << (int)((char)buf[i]) << " "; + debug << endl; +#endif + + if (frames < context.offset()) + continue; + if (frames > context.nframes()) + break; + + const FrameTime time = context.start() + (FrameTime)frames; + + if (size >= 3) { + switch (buf[0] & 0xF0) { + case MIDI_CMD_NOTE_ON: + if (buf[2] == 0) + note_off(context, buf[1], time); + else + note_on(context, buf[1], buf[2], time); + break; + case MIDI_CMD_NOTE_OFF: + note_off(context, buf[1], time); + break; + case MIDI_CMD_CONTROL: + switch (buf[1]) { + case MIDI_CTL_ALL_NOTES_OFF: + case MIDI_CTL_ALL_SOUNDS_OFF: + all_notes_off(context, time); + break; + case MIDI_CTL_SUSTAIN: + if (buf[2] > 63) + sustain_on(context, time); + else + sustain_off(context, time); + break; + case MIDI_CMD_BENDER: + // ? + break; + default: + //warn << "Ignored controller " << buf[1] << endl; + break; + } + break; + default: + //fprintf(stderr, "Unknown (size %d) MIDI event %X\n", size, buf[0]); + break; + } + } else { + //fprintf(stderr, "Unknown (size %d) MIDI event %X\n", size, buf[0]); + } + } + + NodeImpl::post_process(context); +} + +void +NoteNode::note_on(ProcessContext& 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 = NULL; + uint32_t voice_num = 0; + + if (key->state != Key::OFF) { +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "Double midi note received" << endl; +#endif + return; + } + + // Look for free voices + for (uint32_t i=0; i < _polyphony; ++i) { + if ((*_voices)[i].state == Voice::Voice::FREE) { + voice = &(*_voices)[i]; + voice_num = i; + break; + } + } + + // If we didn't find a free one, steal the oldest + if (voice == NULL) { + 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 != NULL); + assert(voice == &(*_voices)[voice_num]); + +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "Note " << (int)note_num << " on @ " << time + << ". Voice " << voice_num << " / " << _polyphony << endl; +#endif + + // Update stolen key, if applicable + if (voice->state == Voice::Voice::ACTIVE) { + assert(_keys[voice->note].state == Key::ON_ASSIGNED); + assert(_keys[voice->note].voice == voice_num); + _keys[voice->note].state = Key::Key::ON_UNASSIGNED; +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "Stole voice " << voice_num << endl; +#endif + } + + // Store key information for later reallocation on note off + key->state = Key::Key::ON_ASSIGNED; + key->voice = voice_num; + key->time = time; + + // Trigger voice + voice->state = Voice::Voice::ACTIVE; + voice->note = note_num; + voice->time = time; + + assert(_keys[voice->note].state == Key::Key::ON_ASSIGNED); + assert(_keys[voice->note].voice == voice_num); + + ((AudioBuffer*)_freq_port->buffer(voice_num).get())->set_value( + note_to_freq(note_num), context.start(), time); + ((AudioBuffer*)_vel_port->buffer(voice_num).get())->set_value( + velocity/127.0, context.start(), time); + ((AudioBuffer*)_gate_port->buffer(voice_num).get())->set_value( + 1.0f, context.start(), time); + + // trigger (one sample) + ((AudioBuffer*)_trig_port->buffer(voice_num).get())->set_value( + 1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(voice_num).get())->set_value( + 0.0f, context.start(), time + 1); + + assert(key->state == Key::Key::ON_ASSIGNED); + assert(voice->state == Voice::Voice::ACTIVE); + assert(key->voice == voice_num); + assert((*_voices)[key->voice].note == note_num); +} + +void +NoteNode::note_off(ProcessContext& context, uint8_t note_num, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + Key* key = &_keys[note_num]; + +#ifdef RAUL_LOG_DEBUG + debug << "Note " << (int)note_num << " off @ " << time << endl; +#endif + + if (key->state == Key::ON_ASSIGNED) { + // Assigned key, turn off voice and key + if ((*_voices)[key->voice].state == Voice::ACTIVE) { + assert((*_voices)[key->voice].note == note_num); + + if ( ! _sustain) { +#ifdef RAUL_LOG_DEBUG + debug << "Free voice " << key->voice << endl; +#endif + free_voice(context, key->voice, time); + } else { +#ifdef RAUL_LOG_DEBUG + debug << "Hold voice " << key->voice << endl; +#endif + (*_voices)[key->voice].state = Voice::HOLDING; + } + + } else { +#ifdef RAUL_LOG_DEBUG + debug << "WARNING: Assigned key, but voice not active" << endl; +#endif + } + } + + key->state = Key::OFF; +} + +void +NoteNode::free_voice(ProcessContext& 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 = NULL; + uint8_t replace_key_num = 0; + + for (uint8_t i = 0; i <= 127; ++i) { + if (_keys[i].state == Key::ON_UNASSIGNED) { + if (replace_key == NULL || _keys[i].time > replace_key->time) { + replace_key = &_keys[i]; + replace_key_num = i; + } + } + } + + if (replace_key != NULL) { // Found a key to assign to freed voice + assert(&_keys[replace_key_num] == replace_key); + assert(replace_key->state == Key::ON_UNASSIGNED); + + // Change the freq but leave the gate high and don't retrigger + ((AudioBuffer*)_freq_port->buffer(voice).get())->set_value(note_to_freq(replace_key_num), context.start(), time); + + replace_key->state = Key::ON_ASSIGNED; + replace_key->voice = voice; + _keys[(*_voices)[voice].note].state = Key::ON_UNASSIGNED; + (*_voices)[voice].note = replace_key_num; + (*_voices)[voice].state = Voice::ACTIVE; + } else { + // No new note for voice, deactivate (set gate low) +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "Note off: key " << (*_voices)[voice].note << " voice " << voice << endl; +#endif + ((AudioBuffer*)_gate_port->buffer(voice).get())->set_value(0.0f, context.start(), time); + (*_voices)[voice].state = Voice::FREE; + } +} + +void +NoteNode::all_notes_off(ProcessContext& context, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + +#ifdef RAUL_LOG_DEBUG + LOG(debug) << "All notes off @ " << time << endl; +#endif + + // FIXME: set all keys to Key::OFF? + + for (uint32_t i = 0; i < _polyphony; ++i) { + ((AudioBuffer*)_gate_port->buffer(i).get())->set_value(0.0f, context.start(), time); + (*_voices)[i].state = Voice::FREE; + } +} + +float +NoteNode::note_to_freq(int num) +{ + static const float A4 = 440.0f; + if (num >= 0 && num <= 119) + return A4 * powf(2.0f, (float)(num - 57.0f) / 12.0f); + return 1.0f; // Frequency of zero causes numerical problems... +} + +void +NoteNode::sustain_on(ProcessContext& context, FrameTime time) +{ + _sustain = true; +} + +void +NoteNode::sustain_off(ProcessContext& 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::HOLDING) + free_voice(context, i, time); +} + +} // 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..d20baf13 --- /dev/null +++ b/src/server/internals/Note.hpp @@ -0,0 +1,101 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) 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 General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef INGEN_INTERNALS_NOTE_HPP +#define INGEN_INTERNALS_NOTE_HPP + +#include <string> +#include "types.hpp" +#include "NodeImpl.hpp" + +namespace Ingen { +namespace Server { + +class InputPort; +class OutputPort; +class InternalPlugin; + +namespace Internals { + +/** MIDI note input node. + * + * For pitched instruments like keyboard, etc. + * + * \ingroup engine + */ +class NoteNode : public NodeImpl +{ +public: + NoteNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate); + + ~NoteNode(); + + bool prepare_poly(BufferFactory& bufs, uint32_t poly); + bool apply_poly(Raul::Maid& maid, uint32_t poly); + + void process(ProcessContext& context); + + void note_on(ProcessContext& context, uint8_t note_num, uint8_t velocity, FrameTime time); + void note_off(ProcessContext& context, uint8_t note_num, FrameTime time); + void all_notes_off(ProcessContext& context, FrameTime time); + + void sustain_on(ProcessContext& context, FrameTime time); + void sustain_off(ProcessContext& context, FrameTime time); + + static InternalPlugin* internal_plugin(Shared::LV2URIMap& uris); + +private: + /** Key, one for each key on the keyboard */ + struct Key { + enum State { OFF, ON_ASSIGNED, ON_UNASSIGNED }; + Key() : 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 State { FREE, ACTIVE, HOLDING }; + Voice() : state(FREE), note(0), time(0) {} + State state; uint8_t note; SampleCount time; + }; + + float note_to_freq(int num); + void free_voice(ProcessContext& context, uint32_t voice, FrameTime time); + + Raul::Array<Voice>* _voices; + Raul::Array<Voice>* _prepared_voices; + Key _keys[128]; + bool _sustain; ///< Whether or not hold pedal is depressed + + InputPort* _midi_in_port; + OutputPort* _freq_port; + OutputPort* _vel_port; + OutputPort* _gate_port; + OutputPort* _trig_port; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Internals + +#endif // INGEN_INTERNALS_NOTE_HPP diff --git a/src/server/internals/Trigger.cpp b/src/server/internals/Trigger.cpp new file mode 100644 index 00000000..d062fa13 --- /dev/null +++ b/src/server/internals/Trigger.cpp @@ -0,0 +1,168 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) 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 General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <cmath> +#include "raul/log.hpp" +#include "raul/midi_events.h" +#include "shared/LV2URIMap.hpp" +#include "internals/Trigger.hpp" +#include "AudioBuffer.hpp" +#include "EventBuffer.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "ProcessContext.hpp" +#include "util.hpp" +#include "ingen-config.h" + +#define LOG(s) s << "[TriggerNode] " + +using namespace std; +using namespace Raul; + +namespace Ingen { +namespace Server { +namespace Internals { + +InternalPlugin* TriggerNode::internal_plugin(Shared::LV2URIMap& uris) { + return new InternalPlugin(uris, NS_INTERNALS "Trigger", "trigger"); +} + +TriggerNode::TriggerNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate) + : NodeImpl(plugin, path, false, parent, srate) + , _learning(false) +{ + const Ingen::Shared::LV2URIMap& uris = bufs.uris(); + _ports = new Raul::Array<PortImpl*>(5); + + _midi_in_port = new InputPort(bufs, this, "input", 0, 1, PortType::EVENTS, Raul::Atom()); + _midi_in_port->set_property(uris.lv2_name, "Input"); + _ports->at(0) = _midi_in_port; + + _note_port = new InputPort(bufs, this, "note", 1, 1, PortType::CONTROL, 60.0f); + _note_port->set_property(uris.lv2_minimum, 0.0f); + _note_port->set_property(uris.lv2_maximum, 127.0f); + _note_port->set_property(uris.lv2_integer, true); + _note_port->set_property(uris.lv2_name, "Note"); + _ports->at(1) = _note_port; + + _gate_port = new OutputPort(bufs, this, "gate", 2, 1, PortType::AUDIO, 0.0f); + _gate_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _gate_port->set_property(uris.lv2_name, "Gate"); + _ports->at(2) = _gate_port; + + _trig_port = new OutputPort(bufs, this, "trigger", 3, 1, PortType::AUDIO, 0.0f); + _trig_port->set_property(uris.lv2_portProperty, uris.lv2_toggled); + _trig_port->set_property(uris.lv2_name, "Trigger"); + _ports->at(3) = _trig_port; + + _vel_port = new OutputPort(bufs, this, "velocity", 4, 1, PortType::AUDIO, 0.0f); + _vel_port->set_property(uris.lv2_minimum, 0.0f); + _vel_port->set_property(uris.lv2_maximum, 1.0f); + _vel_port->set_property(uris.lv2_name, "Velocity"); + _ports->at(4) = _vel_port; +} + +void +TriggerNode::process(ProcessContext& context) +{ + NodeImpl::pre_process(context); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* buf = NULL; + + EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0).get(); + + midi_in->rewind(); + + while (midi_in->get_event(&frames, &subframes, &type, &size, &buf)) { + const FrameTime time = context.start() + (FrameTime)frames; + + if (size >= 3) { + switch (buf[0] & 0xF0) { + case MIDI_CMD_NOTE_ON: + if (buf[2] == 0) + note_off(context, buf[1], time); + else + note_on(context, buf[1], buf[2], time); + break; + case MIDI_CMD_NOTE_OFF: + note_off(context, buf[1], time); + break; + case MIDI_CMD_CONTROL: + if (buf[1] == MIDI_CTL_ALL_NOTES_OFF + || buf[1] == MIDI_CTL_ALL_SOUNDS_OFF) + ((AudioBuffer*)_gate_port->buffer(0).get())->set_value(0.0f, context.start(), time); + default: + break; + } + } + + midi_in->increment(); + } + + NodeImpl::post_process(context); +} + +void +TriggerNode::note_on(ProcessContext& context, uint8_t note_num, uint8_t velocity, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + if (_learning) { + _note_port->set_value(note_num); + ((AudioBuffer*)_note_port->buffer(0).get())->set_value( + (float)note_num, context.start(), context.end()); + _note_port->broadcast_value(context, true); + _learning = false; + } + +#ifdef RAUL_LOG_DEBUG + LOG(debug) << path() << " note " << (int)note_num << " on @ " << time << endl; +#endif + + Sample filter_note = ((AudioBuffer*)_note_port->buffer(0).get())->value_at(0); + if (filter_note >= 0.0 && filter_note < 127.0 && (note_num == (uint8_t)filter_note)) { + ((AudioBuffer*)_gate_port->buffer(0).get())->set_value(1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(0).get())->set_value(1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(0).get())->set_value(0.0f, context.start(), time + 1); + ((AudioBuffer*)_vel_port->buffer(0).get())->set_value(velocity / 127.0f, context.start(), time); + assert(((AudioBuffer*)_trig_port->buffer(0).get())->data()[time - context.start()] == 1.0f); + } +} + +void +TriggerNode::note_off(ProcessContext& context, uint8_t note_num, FrameTime time) +{ + assert(time >= context.start() && time <= context.end()); + + if (note_num == lrintf(((AudioBuffer*)_note_port->buffer(0).get())->value_at(0))) + ((AudioBuffer*)_gate_port->buffer(0).get())->set_value(0.0f, context.start(), time); +} + +} // 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..92b85345 --- /dev/null +++ b/src/server/internals/Trigger.hpp @@ -0,0 +1,77 @@ +/* This file is part of Ingen. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ingen is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) 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 General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef INGEN_INTERNALS_TRIGGER_HPP +#define INGEN_INTERNALS_TRIGGER_HPP + +#include <string> +#include "NodeImpl.hpp" + +namespace Ingen { +namespace Server { + +class InputPort; +class OutputPort; +class InternalPlugin; + +namespace Internals { + +/** MIDI trigger input node. + * + * Just has a gate, for drums etc. A control port is used to select + * which note number is responded to. + * + * Note that this node is always monophonic, the poly parameter is ignored. + * (Should that change?) + * + * \ingroup engine + */ +class TriggerNode : public NodeImpl +{ +public: + TriggerNode( + InternalPlugin* plugin, + BufferFactory& bufs, + const std::string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate); + + void process(ProcessContext& context); + + void note_on(ProcessContext& context, uint8_t note_num, uint8_t velocity, FrameTime time); + void note_off(ProcessContext& context, uint8_t note_num, FrameTime time); + + void learn() { _learning = true; } + + static InternalPlugin* internal_plugin(Shared::LV2URIMap& uris); + +private: + bool _learning; + + InputPort* _midi_in_port; + InputPort* _note_port; + OutputPort* _gate_port; + OutputPort* _trig_port; + OutputPort* _vel_port; +}; + +} // namespace Server +} // namespace Ingen +} // namespace Internals + +#endif // INGEN_INTERNALS_TRIGGER_HPP |