From 21de7016110081437ca0fbfdd6a14f41088da7b9 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 3 Jun 2009 01:37:29 +0000 Subject: Move internals to their own directory ala events. git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@2070 a436a847-0d15-0410-975c-d299462d15a1 --- src/engine/InternalController.cpp | 141 ------------- src/engine/InternalController.hpp | 62 ------ src/engine/InternalNote.cpp | 392 ------------------------------------ src/engine/InternalNote.hpp | 88 -------- src/engine/InternalPlugin.cpp | 8 +- src/engine/InternalTransport.cpp | 156 -------------- src/engine/InternalTransport.hpp | 45 ----- src/engine/InternalTrigger.cpp | 152 -------------- src/engine/InternalTrigger.hpp | 65 ------ src/engine/NodeFactory.cpp | 8 +- src/engine/PluginImpl.cpp | 8 +- src/engine/events/MidiLearn.cpp | 2 +- src/engine/events/MidiLearn.hpp | 2 +- src/engine/events/Note.cpp | 10 +- src/engine/internals/Controller.cpp | 141 +++++++++++++ src/engine/internals/Controller.hpp | 62 ++++++ src/engine/internals/Note.cpp | 392 ++++++++++++++++++++++++++++++++++++ src/engine/internals/Note.hpp | 88 ++++++++ src/engine/internals/Transport.cpp | 156 ++++++++++++++ src/engine/internals/Transport.hpp | 45 +++++ src/engine/internals/Trigger.cpp | 152 ++++++++++++++ src/engine/internals/Trigger.hpp | 65 ++++++ src/engine/wscript | 8 +- 23 files changed, 1124 insertions(+), 1124 deletions(-) delete mode 100644 src/engine/InternalController.cpp delete mode 100644 src/engine/InternalController.hpp delete mode 100644 src/engine/InternalNote.cpp delete mode 100644 src/engine/InternalNote.hpp delete mode 100644 src/engine/InternalTransport.cpp delete mode 100644 src/engine/InternalTransport.hpp delete mode 100644 src/engine/InternalTrigger.cpp delete mode 100644 src/engine/InternalTrigger.hpp create mode 100644 src/engine/internals/Controller.cpp create mode 100644 src/engine/internals/Controller.hpp create mode 100644 src/engine/internals/Note.cpp create mode 100644 src/engine/internals/Note.hpp create mode 100644 src/engine/internals/Transport.cpp create mode 100644 src/engine/internals/Transport.hpp create mode 100644 src/engine/internals/Trigger.cpp create mode 100644 src/engine/internals/Trigger.hpp diff --git a/src/engine/InternalController.cpp b/src/engine/InternalController.cpp deleted file mode 100644 index 1995e2cf..00000000 --- a/src/engine/InternalController.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* This file is part of Ingen. - * Copyright (C) 2007-2009 Dave Robillard - * - * 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 -#include "raul/midi_events.h" -#include "InternalController.hpp" -#include "PostProcessor.hpp" -#include "events/MidiLearn.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 { - -using namespace Shared; - -static InternalPlugin controller_plugin(NS_INTERNALS "Controller", "controller"); - -ControllerNode::ControllerNode(const string& path, - bool polyphonic, - PatchImpl* parent, - SampleRate srate, - size_t buffer_size) - : NodeBase(&controller_plugin, path, false, parent, srate, buffer_size) - , _learning(false) -{ - _ports = new Raul::Array(6); - - _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Raul::Atom(), _buffer_size); - _ports->at(0) = _midi_in_port; - - _param_port = new InputPort(this, "controller", 1, 1, DataType::CONTROL, 0.0f, 1); - _param_port->set_property("lv2:minimum", 0.0f); - _param_port->set_property("lv2:maximum", 127.0f); - _param_port->set_property("lv2:integer", true); - _ports->at(1) = _param_port; - - _log_port = new InputPort(this, "logarithmic", 2, 1, DataType::CONTROL, 0.0f, 1); - _log_port->set_property("lv2:toggled", true); - _ports->at(2) = _log_port; - - _min_port = new InputPort(this, "minimum", 3, 1, DataType::CONTROL, 0.0f, 1); - _ports->at(3) = _min_port; - - _max_port = new InputPort(this, "maximum", 4, 1, DataType::CONTROL, 1.0f, 1); - _ports->at(4) = _max_port; - - _audio_port = new OutputPort(this, "ar_output", 5, 1, DataType::AUDIO, 0.0f, _buffer_size); - _ports->at(5) = _audio_port; -} - - -void -ControllerNode::process(ProcessContext& context) -{ - NodeBase::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); - //assert(midi_in->this_nframes() == context.nframes()); - - 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(); - } - - NodeBase::post_process(context); -} - - -void -ControllerNode::control(ProcessContext& context, uint8_t control_num, uint8_t val, FrameTime time) -{ - assert(time - context.start() < _buffer_size); - - 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))->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))->value_at(0); - const Sample max_port_val = ((AudioBuffer*)_max_port->buffer(0))->value_at(0); - const Sample log_port_val = ((AudioBuffer*)_log_port->buffer(0))->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))->value_at(0)) - ((AudioBuffer*)_audio_port->buffer(0))->set_value(scaled_value, context.start(), time); -} - - -} // namespace Ingen - diff --git a/src/engine/InternalController.hpp b/src/engine/InternalController.hpp deleted file mode 100644 index e0c7de0f..00000000 --- a/src/engine/InternalController.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* This file is part of Ingen. - * Copyright (C) 2007-2009 Dave Robillard - * - * 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 MIDICONTROLNODE_H -#define MIDICONTROLNODE_H - -#include -#include "NodeBase.hpp" - -namespace Ingen { - -class InputPort; -class OutputPort; - - -/** 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 NodeBase -{ -public: - ControllerNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); - - void process(ProcessContext& context); - - void control(ProcessContext& context, uint8_t control_num, uint8_t val, FrameTime time); - - void learn() { _learning = true; } - -private: - bool _learning; - - InputPort* _midi_in_port; - InputPort* _param_port; - InputPort* _log_port; - InputPort* _min_port; - InputPort* _max_port; - OutputPort* _audio_port; -}; - - -} // namespace Ingen - -#endif // MIDICONTROLNODE_H diff --git a/src/engine/InternalNote.cpp b/src/engine/InternalNote.cpp deleted file mode 100644 index b2ab27b3..00000000 --- a/src/engine/InternalNote.cpp +++ /dev/null @@ -1,392 +0,0 @@ -/* This file is part of Ingen. - * Copyright (C) 2007-2009 Dave Robillard - * - * 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 "raul/Array.hpp" -#include "raul/Maid.hpp" -#include "raul/midi_events.h" -#include -#include -#include "AudioBuffer.hpp" -#include "AudioDriver.hpp" -#include "InputPort.hpp" -#include "InternalPlugin.hpp" -#include "EventBuffer.hpp" -#include "InternalNote.hpp" -#include "OutputPort.hpp" -#include "PatchImpl.hpp" -#include "ProcessContext.hpp" -#include "util.hpp" - -using namespace std; - -namespace Ingen { - -using namespace Shared; - -static InternalPlugin note_plugin(NS_INTERNALS "Note", "note"); - -NoteNode::NoteNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size) - : NodeBase(¬e_plugin, path, polyphonic, parent, srate, buffer_size) - , _voices(new Raul::Array(_polyphony)) - , _prepared_voices(NULL) - , _sustain(false) -{ - _ports = new Raul::Array(5); - - _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Raul::Atom(), _buffer_size); - _ports->at(0) = _midi_in_port; - - _freq_port = new OutputPort(this, "frequency", 1, _polyphony, DataType::AUDIO, 440.0f, _buffer_size); - _ports->at(1) = _freq_port; - - _vel_port = new OutputPort(this, "velocity", 2, _polyphony, DataType::AUDIO, 0.0f, _buffer_size); - _vel_port->set_property("lv2:minimum", 0.0f); - _vel_port->set_property("lv2:maximum", 1.0f); - _ports->at(2) = _vel_port; - - _gate_port = new OutputPort(this, "gate", 3, _polyphony, DataType::AUDIO, 0.0f, _buffer_size); - _gate_port->set_property("lv2:toggled", true); - _ports->at(3) = _gate_port; - - _trig_port = new OutputPort(this, "trigger", 4, _polyphony, DataType::AUDIO, 0.0f, _buffer_size); - _trig_port->set_property("lv2:toggled", true); - _ports->at(4) = _trig_port; -} - - -NoteNode::~NoteNode() -{ - delete _voices; -} - - -bool -NoteNode::prepare_poly(uint32_t poly) -{ - if (!_polyphonic) - return true; - - NodeBase::prepare_poly(poly); - - if (_prepared_voices && poly <= _prepared_voices->size()) - return true; - - _prepared_voices = new Raul::Array(poly, *_voices); - - return true; -} - - -bool -NoteNode::apply_poly(Raul::Maid& maid, uint32_t poly) -{ - if (!_polyphonic) - return true; - - NodeBase::apply_poly(maid, poly); - - if (_prepared_voices) { - assert(poly <= _prepared_voices->size()); - maid.push(_voices); - _voices = _prepared_voices; - _prepared_voices = NULL; - } - - _polyphony = poly; - assert(_voices->size() >= _polyphony); - - return true; -} - - -void -NoteNode::process(ProcessContext& context) -{ - EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0); - NodeBase::pre_process(context); - - uint32_t frames = 0; - uint32_t subframes = 0; - uint16_t type = 0; - uint16_t size = 0; - uint8_t* buf = NULL; - - //assert(midi_in->this_nframes() == context.nframes()); - - midi_in->rewind(); - - if (midi_in->event_count() > 0) - while (midi_in->get_event(&frames, &subframes, &type, &size, &buf)) { - - /*cout << "EVENT TYPE " << type << " @ " << frames << "." << subframes << ": "; - for (uint16_t i = 0; i < size; ++i) - cout << (int)((char)buf[i]) << " "; - cout << endl;*/ - - 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: - //cerr << "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]); - } - - if (midi_in->increment() == midi_in->this_nframes()) - break; - } - - NodeBase::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(time - context.start() < _buffer_size); - assert(note_num <= 127); - - Key* key = &_keys[note_num]; - Voice* voice = NULL; - uint32_t voice_num = 0; - - if (key->state != Key::OFF) { - //cerr << "[NoteNode] Double midi note received" << endl; - 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]); - - /*cerr << "[NoteNode] Note " << (int)note_num << " on @ " << time - << ". Voice " << voice_num << " / " << _polyphony << endl;*/ - - // 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; - //cerr << "[NoteNode] Stole voice " << voice_num << endl; - } - - // 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))->set_value(note_to_freq(note_num), context.start(), time); - ((AudioBuffer*)_vel_port->buffer(voice_num))->set_value(velocity/127.0, context.start(), time); - ((AudioBuffer*)_gate_port->buffer(voice_num))->set_value(1.0f, context.start(), time); - - // trigger (one sample) - ((AudioBuffer*)_trig_port->buffer(voice_num))->set_value(1.0f, context.start(), time); - ((AudioBuffer*)_trig_port->buffer(voice_num))->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()); - assert(time - context.start() < _buffer_size); - - Key* key = &_keys[note_num]; - - //cerr << "[NoteNode] Note " << (int)note_num << " off @ " << time << endl; - - 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) { - //cerr << "... free voice " << key->voice << endl; - free_voice(context, key->voice, time); - } else { - //cerr << "... hold voice " << key->voice << endl; - (*_voices)[key->voice].state = Voice::HOLDING; - } - - } else { -#ifndef NDEBUG - cerr << "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()); - assert(time - context.start() < _buffer_size); - - // 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))->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) - //cerr << "[NoteNode] Note off. Key " << (int)note_num << ", Voice " << voice << " Killed" << endl; - ((AudioBuffer*)_gate_port->buffer(voice))->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()); - assert(time - context.start() < _buffer_size); - - //cerr << "All notes off @ " << offset << endl; - - // FIXME: set all keys to Key::OFF? - - for (uint32_t i = 0; i < _polyphony; ++i) { - ((AudioBuffer*)_gate_port->buffer(i))->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; // Some LADSPA plugins don't like freq=0 -} - - -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()); - assert(time - context.start() < _buffer_size); - - _sustain = false; - - for (uint32_t i=0; i < _polyphony; ++i) - if ((*_voices)[i].state == Voice::HOLDING) - free_voice(context, i, time); -} - - -} // namespace Ingen - diff --git a/src/engine/InternalNote.hpp b/src/engine/InternalNote.hpp deleted file mode 100644 index b14ba604..00000000 --- a/src/engine/InternalNote.hpp +++ /dev/null @@ -1,88 +0,0 @@ -/* This file is part of Ingen. - * Copyright (C) 2007-2009 Dave Robillard - * - * 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 MIDINOTENODE_H -#define MIDINOTENODE_H - -#include -#include "types.hpp" -#include "NodeBase.hpp" - -namespace Ingen { - -class InputPort; -class OutputPort; - - -/** MIDI note input node. - * - * For pitched instruments like keyboard, etc. - * - * \ingroup engine - */ -class NoteNode : public NodeBase -{ -public: - NoteNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); - ~NoteNode(); - - bool prepare_poly(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); - -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) {} - 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* _voices; - Raul::Array* _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 Ingen - -#endif // MIDINOTENODE_H diff --git a/src/engine/InternalPlugin.cpp b/src/engine/InternalPlugin.cpp index 8ab9070b..77290c66 100644 --- a/src/engine/InternalPlugin.cpp +++ b/src/engine/InternalPlugin.cpp @@ -17,10 +17,10 @@ #include #include "InternalPlugin.hpp" -#include "InternalNote.hpp" -#include "InternalTrigger.hpp" -#include "InternalController.hpp" -#include "InternalTransport.hpp" +#include "internals/Note.hpp" +#include "internals/Trigger.hpp" +#include "internals/Controller.hpp" +#include "internals/Transport.hpp" #include "Engine.hpp" #include "AudioDriver.hpp" diff --git a/src/engine/InternalTransport.cpp b/src/engine/InternalTransport.cpp deleted file mode 100644 index 40f953a5..00000000 --- a/src/engine/InternalTransport.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/* This file is part of Ingen. - * Copyright (C) 2007-2009 Dave Robillard - * - * 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 "InternalTransport.hpp" -#include -#include "OutputPort.hpp" -#include "InternalPlugin.hpp" -#include "JackAudioDriver.hpp" -#include "PortImpl.hpp" -#include "util.hpp" -//#include "Engine.hpp" - -using namespace std; - -namespace Ingen { - -static InternalPlugin transport_plugin(NS_INTERNALS "Transport", "transport"); - -TransportNode::TransportNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size) - : NodeBase(&transport_plugin, path, false, parent, srate, buffer_size) -{ -#if 0 - _num_ports = 10; - _ports.alloc(_num_ports); - - OutputPort* spb_port = new OutputPort(this, "Seconds per Beat", 0, 1, - // new PortInfo("Seconds per Beat", CONTROL, OUTPUT, 0, 0, 1), 1); - _ports.at(0) = spb_port; - - OutputPort* bpb_port = new OutputPort(this, "Beats per Bar", 1, 1, - // new PortInfo("Beats per Bar", CONTROL, OUTPUT, 0, 0, 1), 1); - _ports.at(1) = bpb_port; - - OutputPort* bar_port = new OutputPort(this, "Bar", 3, 1, -// new PortInfo("Bar", CONTROL, OUTPUT, 0, 0, 1), buffer_size); - _ports.at(2) = bar_port; - - OutputPort* beat_port = new OutputPort(this, "Beat", 3, 1, - // new PortInfo("Beat", CONTROL, OUTPUT, 0, 0, 1), buffer_size); - _ports.at(3) = beat_port; - - OutputPort* frame_port = new OutputPort(this, "Frame", 3, 1, - // new PortInfo("Frame", CONTROL, OUTPUT, 0, 0, 1), buffer_size); - _ports.at(4) = frame_port; - - OutputPort* hour_port = new OutputPort(this, "Hour", 3, 1, - // new PortInfo("Hour", CONTROL, OUTPUT, 0, 0, 1), buffer_size); - _ports.at(5) = hour_port; - - OutputPort* minute_port = new OutputPort(this, "Minute", 3, 1, - // new PortInfo("Minute", CONTROL, OUTPUT, 0, 0, 1), buffer_size); - _ports.at(6) = minute_port; - - OutputPort* second_port = new OutputPort(this, "Second", 3, 1, - // new PortInfo("Second", CONTROL, OUTPUT, 0, 0, 1), buffer_size); - _ports.at(7) = second_port; - - OutputPort* trg_port = new OutputPort(this, "Beat Tick", 2, 1, - // new PortInfo("Beat Tick", AUDIO, OUTPUT, 0, 0, 1), buffer_size); - _ports.at(8) = trg_port; - - OutputPort* bar_trig_port = new OutputPort(this, "Bar Tick", 3, 1, - // new PortInfo("Bar Tick", AUDIO, OUTPUT, 0, 0, 1), buffer_size); - _ports.at(9) = bar_trig_port; -#endif -} - - -void -TransportNode::process(ProcessContext& context) -{ - NodeBase::pre_process(context); -#if 0 - - // FIXME: this will die horribly with any driver other than jack (in theory) - const jack_position_t* const position = ((JackAudioDriver*)Engine::instance().audio_driver())->position(); - jack_transport_state_t state = ((JackAudioDriver*)Engine::instance().audio_driver())->transport_state(); - double bpm = position->beats_per_minute; - float bpb = position->beats_per_bar; - float spb = 60.0 / bpm; - - //cerr << "bpm = " << bpm << endl; - //cerr << "spb = " << spb << endl; - - if (position->valid & JackPositionBBT) { - cerr << "bar: " << position->bar << endl; - cerr << "beat: " << position->beat << endl; - cerr << "tick: " << position->tick << endl; - } else { - cerr << "No BBT" << endl; - } - - if (position->valid & JackBBTFrameOffset) { - cerr << "bbt_offset: " << position->bbt_offset << endl; - } else { - cerr << "No BBT offset" << endl; - } - - if (position->valid & JackPositionTimecode) { - double time = position->frame_time; - cerr << "Seconds: " << time << " : " << endl; - /*time /= 60.0; - cerr << "Minutes: " << time << " : "; - time /= 60.0; - cerr << "Hours: " << time << " : ";*/ - } else { - cerr << "No timecode." << endl; - } - - - ((OutputPort*)_ports.at(0))->buffer(0)->set(spb, 0, 0); - ((OutputPort*)_ports.at(1))->buffer(0)->set(bpb, 0, 0); - - // fill the trigger buffers with zeros - ((OutputPort*)_ports.at(2))->buffer(0)->set(0.0f, 0, nframes - 1); - ((OutputPort*)_ports.at(3))->buffer(0)->set(0.0f, 0, nframes - 1); - - // if the transport is rolling, add triggers at the right frame positions - if ((position->valid & JackTransportBBT) && (state == JackTransportRolling)) { - double frames_per_beat = position->frame_rate * spb; - double first_beat = (1.0f - position->tick / position->ticks_per_beat) * frames_per_beat; - int first_beat_no = position->beat; - if (first_beat >= frames_per_beat) { - first_beat -= frames_per_beat; - --first_beat_no; - } - for ( ; first_beat < nframes; first_beat += frames_per_beat) { - ((OutputPort*)_ports.at(2))->buffer(0)->set(1.0f, size_t(first_beat)); - if (first_beat_no % int(bpb) == 0) { - ((OutputPort*)_ports.at(3))->buffer(0)->set(1.0f, size_t(first_beat)); - ++first_beat_no; - } - } - } - #endif - - NodeBase::post_process(context); -} - - -} // namespace Ingen - diff --git a/src/engine/InternalTransport.hpp b/src/engine/InternalTransport.hpp deleted file mode 100644 index 3947b235..00000000 --- a/src/engine/InternalTransport.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* This file is part of Ingen. - * Copyright (C) 2007-2009 Dave Robillard - * - * 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 TRANSPORTNODE_H -#define TRANSPORTNODE_H - -#include -#include -#include "NodeBase.hpp" - -namespace Ingen { - - -/** Transport Node, brings timing information into patches. - * - * This node uses the Jack transport API to get information about BPM, time - * signature, etc.. all sample accurate. Using this you can do - * tempo-synced effects or even synthesis, etc. - */ -class TransportNode : public NodeBase -{ -public: - TransportNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); - - virtual void process(ProcessContext& context); -}; - - -} // namespace Ingen - -#endif // TRANSPORTNODE_H diff --git a/src/engine/InternalTrigger.cpp b/src/engine/InternalTrigger.cpp deleted file mode 100644 index cd5684cf..00000000 --- a/src/engine/InternalTrigger.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* This file is part of Ingen. - * Copyright (C) 2007-2009 Dave Robillard - * - * 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 -#include "raul/midi_events.h" -#include "InternalTrigger.hpp" -#include "AudioBuffer.hpp" -#include "InputPort.hpp" -#include "OutputPort.hpp" -#include "InternalPlugin.hpp" -#include "ProcessContext.hpp" -#include "EventBuffer.hpp" -#include "util.hpp" - -using namespace std; - -namespace Ingen { - -using namespace Shared; - -static InternalPlugin trigger_plugin(NS_INTERNALS "Trigger", "trigger"); - -TriggerNode::TriggerNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size) - : NodeBase(&trigger_plugin, path, false, parent, srate, buffer_size) - , _learning(false) -{ - _ports = new Raul::Array(5); - - _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Raul::Atom(), _buffer_size); - _ports->at(0) = _midi_in_port; - - _note_port = new InputPort(this, "note", 1, 1, DataType::CONTROL, 60.0f, 1); - _note_port->set_property("lv2:minimum", 0.0f); - _note_port->set_property("lv2:maximum", 127.0f); - _note_port->set_property("lv2:integer", true); - _ports->at(1) = _note_port; - - _gate_port = new OutputPort(this, "gate", 2, 1, DataType::AUDIO, 0.0f, _buffer_size); - _gate_port->set_property("lv2:toggled", true); - _ports->at(2) = _gate_port; - - _trig_port = new OutputPort(this, "trigger", 3, 1, DataType::AUDIO, 0.0f, _buffer_size); - _trig_port->set_property("lv2:toggled", true); - _ports->at(3) = _trig_port; - - _vel_port = new OutputPort(this, "velocity", 4, 1, DataType::AUDIO, 0.0f, _buffer_size); - _vel_port->set_property("lv2:minimum", 0.0f); - _vel_port->set_property("lv2:maximum", 1.0f); - _ports->at(4) = _vel_port; -} - - -void -TriggerNode::process(ProcessContext& context) -{ - NodeBase::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); - //assert(midi_in->this_nframes() == context.nframes()); - - 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))->set_value(0.0f, context.start(), time); - default: - break; - } - } - - midi_in->increment(); - } - - NodeBase::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()); - assert(time - context.start() < _buffer_size); - - if (_learning) { - _note_port->set_value(note_num); - ((AudioBuffer*)_note_port->buffer(0))->set_value( - (float)note_num, context.start(), context.end()); - _note_port->broadcast_value(context, true); - _learning = false; - } - - /*cerr << "[TriggerNode] " << path() << " Note " << (int)note_num << " on @ " << time << endl;*/ - - Sample filter_note = ((AudioBuffer*)_note_port->buffer(0))->value_at(0); - if (filter_note >= 0.0 && filter_note < 127.0 && (note_num == (uint8_t)filter_note)) { - ((AudioBuffer*)_gate_port->buffer(0))->set_value(1.0f, context.start(), time); - ((AudioBuffer*)_trig_port->buffer(0))->set_value(1.0f, context.start(), time); - ((AudioBuffer*)_trig_port->buffer(0))->set_value(0.0f, context.start(), time + 1); - ((AudioBuffer*)_vel_port->buffer(0))->set_value(velocity / 127.0f, context.start(), time); - assert(((AudioBuffer*)_trig_port->buffer(0))->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()); - assert(time - context.start() < _buffer_size); - - if (note_num == lrintf(((AudioBuffer*)_note_port->buffer(0))->value_at(0))) - ((AudioBuffer*)_gate_port->buffer(0))->set_value(0.0f, context.start(), time); -} - - -} // namespace Ingen - diff --git a/src/engine/InternalTrigger.hpp b/src/engine/InternalTrigger.hpp deleted file mode 100644 index 973b9b41..00000000 --- a/src/engine/InternalTrigger.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/* This file is part of Ingen. - * Copyright (C) 2007-2009 Dave Robillard - * - * 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 MIDITRIGGERNODE_H -#define MIDITRIGGERNODE_H - -#include -#include "NodeBase.hpp" - -namespace Ingen { - -class InputPort; -class OutputPort; - - -/** 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 NodeBase -{ -public: - TriggerNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); - - 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; } - -private: - bool _learning; - - InputPort* _midi_in_port; - InputPort* _note_port; - OutputPort* _gate_port; - OutputPort* _trig_port; - OutputPort* _vel_port; -}; - - -} // namespace Ingen - -#endif // MIDITRIGGERNODE_H diff --git a/src/engine/NodeFactory.cpp b/src/engine/NodeFactory.cpp index ad4de3c2..f1df20b9 100644 --- a/src/engine/NodeFactory.cpp +++ b/src/engine/NodeFactory.cpp @@ -26,10 +26,10 @@ #include "module/World.hpp" #include "NodeFactory.hpp" #include "ThreadManager.hpp" -#include "InternalNote.hpp" -#include "InternalTrigger.hpp" -#include "InternalController.hpp" -#include "InternalTransport.hpp" +#include "internals/Note.hpp" +#include "internals/Trigger.hpp" +#include "internals/Controller.hpp" +#include "internals/Transport.hpp" #include "PatchImpl.hpp" #include "InternalPlugin.hpp" #ifdef HAVE_LADSPA_H diff --git a/src/engine/PluginImpl.cpp b/src/engine/PluginImpl.cpp index ad3c08b6..7a481c55 100644 --- a/src/engine/PluginImpl.cpp +++ b/src/engine/PluginImpl.cpp @@ -17,10 +17,10 @@ #include #include "PluginImpl.hpp" -#include "InternalNote.hpp" -#include "InternalTrigger.hpp" -#include "InternalController.hpp" -#include "InternalTransport.hpp" +#include "internals/Note.hpp" +#include "internals/Trigger.hpp" +#include "internals/Controller.hpp" +#include "internals/Transport.hpp" using namespace std; diff --git a/src/engine/events/MidiLearn.cpp b/src/engine/events/MidiLearn.cpp index c475833b..b5c25102 100644 --- a/src/engine/events/MidiLearn.cpp +++ b/src/engine/events/MidiLearn.cpp @@ -20,7 +20,7 @@ #include "Engine.hpp" #include "EngineStore.hpp" #include "NodeImpl.hpp" -#include "InternalController.hpp" +#include "internals/Controller.hpp" #include "ClientBroadcaster.hpp" #include "PluginImpl.hpp" diff --git a/src/engine/events/MidiLearn.hpp b/src/engine/events/MidiLearn.hpp index 061c9b7f..5835cf6a 100644 --- a/src/engine/events/MidiLearn.hpp +++ b/src/engine/events/MidiLearn.hpp @@ -19,7 +19,7 @@ #define MIDILEARNEVENT_H #include "QueuedEvent.hpp" -#include "InternalController.hpp" +#include "internals/Controller.hpp" #include "types.hpp" namespace Ingen { diff --git a/src/engine/events/Note.cpp b/src/engine/events/Note.cpp index 2bf71068..0871b0d3 100644 --- a/src/engine/events/Note.cpp +++ b/src/engine/events/Note.cpp @@ -15,16 +15,16 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "Note.hpp" -#include "Responder.hpp" +#include "internals/Note.hpp" +#include "internals/Trigger.hpp" #include "Engine.hpp" #include "EngineStore.hpp" +#include "InternalPlugin.hpp" #include "NodeImpl.hpp" -#include "InternalNote.hpp" -#include "InternalTrigger.hpp" +#include "Note.hpp" #include "PluginImpl.hpp" -#include "InternalPlugin.hpp" #include "ProcessContext.hpp" +#include "Responder.hpp" using namespace Raul; diff --git a/src/engine/internals/Controller.cpp b/src/engine/internals/Controller.cpp new file mode 100644 index 00000000..d615830a --- /dev/null +++ b/src/engine/internals/Controller.cpp @@ -0,0 +1,141 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2009 Dave Robillard + * + * 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 +#include "raul/midi_events.h" +#include "internals/Controller.hpp" +#include "PostProcessor.hpp" +#include "events/MidiLearn.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 { + +using namespace Shared; + +static InternalPlugin controller_plugin(NS_INTERNALS "Controller", "controller"); + +ControllerNode::ControllerNode(const string& path, + bool polyphonic, + PatchImpl* parent, + SampleRate srate, + size_t buffer_size) + : NodeBase(&controller_plugin, path, false, parent, srate, buffer_size) + , _learning(false) +{ + _ports = new Raul::Array(6); + + _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Raul::Atom(), _buffer_size); + _ports->at(0) = _midi_in_port; + + _param_port = new InputPort(this, "controller", 1, 1, DataType::CONTROL, 0.0f, 1); + _param_port->set_property("lv2:minimum", 0.0f); + _param_port->set_property("lv2:maximum", 127.0f); + _param_port->set_property("lv2:integer", true); + _ports->at(1) = _param_port; + + _log_port = new InputPort(this, "logarithmic", 2, 1, DataType::CONTROL, 0.0f, 1); + _log_port->set_property("lv2:toggled", true); + _ports->at(2) = _log_port; + + _min_port = new InputPort(this, "minimum", 3, 1, DataType::CONTROL, 0.0f, 1); + _ports->at(3) = _min_port; + + _max_port = new InputPort(this, "maximum", 4, 1, DataType::CONTROL, 1.0f, 1); + _ports->at(4) = _max_port; + + _audio_port = new OutputPort(this, "ar_output", 5, 1, DataType::AUDIO, 0.0f, _buffer_size); + _ports->at(5) = _audio_port; +} + + +void +ControllerNode::process(ProcessContext& context) +{ + NodeBase::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); + //assert(midi_in->this_nframes() == context.nframes()); + + 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(); + } + + NodeBase::post_process(context); +} + + +void +ControllerNode::control(ProcessContext& context, uint8_t control_num, uint8_t val, FrameTime time) +{ + assert(time - context.start() < _buffer_size); + + 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))->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))->value_at(0); + const Sample max_port_val = ((AudioBuffer*)_max_port->buffer(0))->value_at(0); + const Sample log_port_val = ((AudioBuffer*)_log_port->buffer(0))->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))->value_at(0)) + ((AudioBuffer*)_audio_port->buffer(0))->set_value(scaled_value, context.start(), time); +} + + +} // namespace Ingen + diff --git a/src/engine/internals/Controller.hpp b/src/engine/internals/Controller.hpp new file mode 100644 index 00000000..e0c7de0f --- /dev/null +++ b/src/engine/internals/Controller.hpp @@ -0,0 +1,62 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2009 Dave Robillard + * + * 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 MIDICONTROLNODE_H +#define MIDICONTROLNODE_H + +#include +#include "NodeBase.hpp" + +namespace Ingen { + +class InputPort; +class OutputPort; + + +/** 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 NodeBase +{ +public: + ControllerNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); + + void process(ProcessContext& context); + + void control(ProcessContext& context, uint8_t control_num, uint8_t val, FrameTime time); + + void learn() { _learning = true; } + +private: + bool _learning; + + InputPort* _midi_in_port; + InputPort* _param_port; + InputPort* _log_port; + InputPort* _min_port; + InputPort* _max_port; + OutputPort* _audio_port; +}; + + +} // namespace Ingen + +#endif // MIDICONTROLNODE_H diff --git a/src/engine/internals/Note.cpp b/src/engine/internals/Note.cpp new file mode 100644 index 00000000..e3ba42d3 --- /dev/null +++ b/src/engine/internals/Note.cpp @@ -0,0 +1,392 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2009 Dave Robillard + * + * 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 "raul/Array.hpp" +#include "raul/Maid.hpp" +#include "raul/midi_events.h" +#include +#include +#include "internals/Note.hpp" +#include "AudioBuffer.hpp" +#include "AudioDriver.hpp" +#include "EventBuffer.hpp" +#include "InputPort.hpp" +#include "InternalPlugin.hpp" +#include "OutputPort.hpp" +#include "PatchImpl.hpp" +#include "ProcessContext.hpp" +#include "util.hpp" + +using namespace std; + +namespace Ingen { + +using namespace Shared; + +static InternalPlugin note_plugin(NS_INTERNALS "Note", "note"); + +NoteNode::NoteNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size) + : NodeBase(¬e_plugin, path, polyphonic, parent, srate, buffer_size) + , _voices(new Raul::Array(_polyphony)) + , _prepared_voices(NULL) + , _sustain(false) +{ + _ports = new Raul::Array(5); + + _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Raul::Atom(), _buffer_size); + _ports->at(0) = _midi_in_port; + + _freq_port = new OutputPort(this, "frequency", 1, _polyphony, DataType::AUDIO, 440.0f, _buffer_size); + _ports->at(1) = _freq_port; + + _vel_port = new OutputPort(this, "velocity", 2, _polyphony, DataType::AUDIO, 0.0f, _buffer_size); + _vel_port->set_property("lv2:minimum", 0.0f); + _vel_port->set_property("lv2:maximum", 1.0f); + _ports->at(2) = _vel_port; + + _gate_port = new OutputPort(this, "gate", 3, _polyphony, DataType::AUDIO, 0.0f, _buffer_size); + _gate_port->set_property("lv2:toggled", true); + _ports->at(3) = _gate_port; + + _trig_port = new OutputPort(this, "trigger", 4, _polyphony, DataType::AUDIO, 0.0f, _buffer_size); + _trig_port->set_property("lv2:toggled", true); + _ports->at(4) = _trig_port; +} + + +NoteNode::~NoteNode() +{ + delete _voices; +} + + +bool +NoteNode::prepare_poly(uint32_t poly) +{ + if (!_polyphonic) + return true; + + NodeBase::prepare_poly(poly); + + if (_prepared_voices && poly <= _prepared_voices->size()) + return true; + + _prepared_voices = new Raul::Array(poly, *_voices); + + return true; +} + + +bool +NoteNode::apply_poly(Raul::Maid& maid, uint32_t poly) +{ + if (!_polyphonic) + return true; + + NodeBase::apply_poly(maid, poly); + + if (_prepared_voices) { + assert(poly <= _prepared_voices->size()); + maid.push(_voices); + _voices = _prepared_voices; + _prepared_voices = NULL; + } + + _polyphony = poly; + assert(_voices->size() >= _polyphony); + + return true; +} + + +void +NoteNode::process(ProcessContext& context) +{ + EventBuffer* const midi_in = (EventBuffer*)_midi_in_port->buffer(0); + NodeBase::pre_process(context); + + uint32_t frames = 0; + uint32_t subframes = 0; + uint16_t type = 0; + uint16_t size = 0; + uint8_t* buf = NULL; + + //assert(midi_in->this_nframes() == context.nframes()); + + midi_in->rewind(); + + if (midi_in->event_count() > 0) + while (midi_in->get_event(&frames, &subframes, &type, &size, &buf)) { + + /*cout << "EVENT TYPE " << type << " @ " << frames << "." << subframes << ": "; + for (uint16_t i = 0; i < size; ++i) + cout << (int)((char)buf[i]) << " "; + cout << endl;*/ + + 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: + //cerr << "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]); + } + + if (midi_in->increment() == midi_in->this_nframes()) + break; + } + + NodeBase::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(time - context.start() < _buffer_size); + assert(note_num <= 127); + + Key* key = &_keys[note_num]; + Voice* voice = NULL; + uint32_t voice_num = 0; + + if (key->state != Key::OFF) { + //cerr << "[NoteNode] Double midi note received" << endl; + 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]); + + /*cerr << "[NoteNode] Note " << (int)note_num << " on @ " << time + << ". Voice " << voice_num << " / " << _polyphony << endl;*/ + + // 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; + //cerr << "[NoteNode] Stole voice " << voice_num << endl; + } + + // 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))->set_value(note_to_freq(note_num), context.start(), time); + ((AudioBuffer*)_vel_port->buffer(voice_num))->set_value(velocity/127.0, context.start(), time); + ((AudioBuffer*)_gate_port->buffer(voice_num))->set_value(1.0f, context.start(), time); + + // trigger (one sample) + ((AudioBuffer*)_trig_port->buffer(voice_num))->set_value(1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(voice_num))->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()); + assert(time - context.start() < _buffer_size); + + Key* key = &_keys[note_num]; + + //cerr << "[NoteNode] Note " << (int)note_num << " off @ " << time << endl; + + 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) { + //cerr << "... free voice " << key->voice << endl; + free_voice(context, key->voice, time); + } else { + //cerr << "... hold voice " << key->voice << endl; + (*_voices)[key->voice].state = Voice::HOLDING; + } + + } else { +#ifndef NDEBUG + cerr << "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()); + assert(time - context.start() < _buffer_size); + + // 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))->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) + //cerr << "[NoteNode] Note off. Key " << (int)note_num << ", Voice " << voice << " Killed" << endl; + ((AudioBuffer*)_gate_port->buffer(voice))->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()); + assert(time - context.start() < _buffer_size); + + //cerr << "All notes off @ " << offset << endl; + + // FIXME: set all keys to Key::OFF? + + for (uint32_t i = 0; i < _polyphony; ++i) { + ((AudioBuffer*)_gate_port->buffer(i))->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; // Some LADSPA plugins don't like freq=0 +} + + +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()); + assert(time - context.start() < _buffer_size); + + _sustain = false; + + for (uint32_t i=0; i < _polyphony; ++i) + if ((*_voices)[i].state == Voice::HOLDING) + free_voice(context, i, time); +} + + +} // namespace Ingen + diff --git a/src/engine/internals/Note.hpp b/src/engine/internals/Note.hpp new file mode 100644 index 00000000..b14ba604 --- /dev/null +++ b/src/engine/internals/Note.hpp @@ -0,0 +1,88 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2009 Dave Robillard + * + * 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 MIDINOTENODE_H +#define MIDINOTENODE_H + +#include +#include "types.hpp" +#include "NodeBase.hpp" + +namespace Ingen { + +class InputPort; +class OutputPort; + + +/** MIDI note input node. + * + * For pitched instruments like keyboard, etc. + * + * \ingroup engine + */ +class NoteNode : public NodeBase +{ +public: + NoteNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); + ~NoteNode(); + + bool prepare_poly(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); + +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) {} + 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* _voices; + Raul::Array* _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 Ingen + +#endif // MIDINOTENODE_H diff --git a/src/engine/internals/Transport.cpp b/src/engine/internals/Transport.cpp new file mode 100644 index 00000000..3355f4d8 --- /dev/null +++ b/src/engine/internals/Transport.cpp @@ -0,0 +1,156 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2009 Dave Robillard + * + * 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 "internals/Transport.hpp" +#include +#include "OutputPort.hpp" +#include "InternalPlugin.hpp" +#include "JackAudioDriver.hpp" +#include "PortImpl.hpp" +#include "util.hpp" +//#include "Engine.hpp" + +using namespace std; + +namespace Ingen { + +static InternalPlugin transport_plugin(NS_INTERNALS "Transport", "transport"); + +TransportNode::TransportNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size) + : NodeBase(&transport_plugin, path, false, parent, srate, buffer_size) +{ +#if 0 + _num_ports = 10; + _ports.alloc(_num_ports); + + OutputPort* spb_port = new OutputPort(this, "Seconds per Beat", 0, 1, + // new PortInfo("Seconds per Beat", CONTROL, OUTPUT, 0, 0, 1), 1); + _ports.at(0) = spb_port; + + OutputPort* bpb_port = new OutputPort(this, "Beats per Bar", 1, 1, + // new PortInfo("Beats per Bar", CONTROL, OUTPUT, 0, 0, 1), 1); + _ports.at(1) = bpb_port; + + OutputPort* bar_port = new OutputPort(this, "Bar", 3, 1, +// new PortInfo("Bar", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + _ports.at(2) = bar_port; + + OutputPort* beat_port = new OutputPort(this, "Beat", 3, 1, + // new PortInfo("Beat", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + _ports.at(3) = beat_port; + + OutputPort* frame_port = new OutputPort(this, "Frame", 3, 1, + // new PortInfo("Frame", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + _ports.at(4) = frame_port; + + OutputPort* hour_port = new OutputPort(this, "Hour", 3, 1, + // new PortInfo("Hour", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + _ports.at(5) = hour_port; + + OutputPort* minute_port = new OutputPort(this, "Minute", 3, 1, + // new PortInfo("Minute", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + _ports.at(6) = minute_port; + + OutputPort* second_port = new OutputPort(this, "Second", 3, 1, + // new PortInfo("Second", CONTROL, OUTPUT, 0, 0, 1), buffer_size); + _ports.at(7) = second_port; + + OutputPort* trg_port = new OutputPort(this, "Beat Tick", 2, 1, + // new PortInfo("Beat Tick", AUDIO, OUTPUT, 0, 0, 1), buffer_size); + _ports.at(8) = trg_port; + + OutputPort* bar_trig_port = new OutputPort(this, "Bar Tick", 3, 1, + // new PortInfo("Bar Tick", AUDIO, OUTPUT, 0, 0, 1), buffer_size); + _ports.at(9) = bar_trig_port; +#endif +} + + +void +TransportNode::process(ProcessContext& context) +{ + NodeBase::pre_process(context); +#if 0 + + // FIXME: this will die horribly with any driver other than jack (in theory) + const jack_position_t* const position = ((JackAudioDriver*)Engine::instance().audio_driver())->position(); + jack_transport_state_t state = ((JackAudioDriver*)Engine::instance().audio_driver())->transport_state(); + double bpm = position->beats_per_minute; + float bpb = position->beats_per_bar; + float spb = 60.0 / bpm; + + //cerr << "bpm = " << bpm << endl; + //cerr << "spb = " << spb << endl; + + if (position->valid & JackPositionBBT) { + cerr << "bar: " << position->bar << endl; + cerr << "beat: " << position->beat << endl; + cerr << "tick: " << position->tick << endl; + } else { + cerr << "No BBT" << endl; + } + + if (position->valid & JackBBTFrameOffset) { + cerr << "bbt_offset: " << position->bbt_offset << endl; + } else { + cerr << "No BBT offset" << endl; + } + + if (position->valid & JackPositionTimecode) { + double time = position->frame_time; + cerr << "Seconds: " << time << " : " << endl; + /*time /= 60.0; + cerr << "Minutes: " << time << " : "; + time /= 60.0; + cerr << "Hours: " << time << " : ";*/ + } else { + cerr << "No timecode." << endl; + } + + + ((OutputPort*)_ports.at(0))->buffer(0)->set(spb, 0, 0); + ((OutputPort*)_ports.at(1))->buffer(0)->set(bpb, 0, 0); + + // fill the trigger buffers with zeros + ((OutputPort*)_ports.at(2))->buffer(0)->set(0.0f, 0, nframes - 1); + ((OutputPort*)_ports.at(3))->buffer(0)->set(0.0f, 0, nframes - 1); + + // if the transport is rolling, add triggers at the right frame positions + if ((position->valid & JackTransportBBT) && (state == JackTransportRolling)) { + double frames_per_beat = position->frame_rate * spb; + double first_beat = (1.0f - position->tick / position->ticks_per_beat) * frames_per_beat; + int first_beat_no = position->beat; + if (first_beat >= frames_per_beat) { + first_beat -= frames_per_beat; + --first_beat_no; + } + for ( ; first_beat < nframes; first_beat += frames_per_beat) { + ((OutputPort*)_ports.at(2))->buffer(0)->set(1.0f, size_t(first_beat)); + if (first_beat_no % int(bpb) == 0) { + ((OutputPort*)_ports.at(3))->buffer(0)->set(1.0f, size_t(first_beat)); + ++first_beat_no; + } + } + } + #endif + + NodeBase::post_process(context); +} + + +} // namespace Ingen + diff --git a/src/engine/internals/Transport.hpp b/src/engine/internals/Transport.hpp new file mode 100644 index 00000000..3947b235 --- /dev/null +++ b/src/engine/internals/Transport.hpp @@ -0,0 +1,45 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2009 Dave Robillard + * + * 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 TRANSPORTNODE_H +#define TRANSPORTNODE_H + +#include +#include +#include "NodeBase.hpp" + +namespace Ingen { + + +/** Transport Node, brings timing information into patches. + * + * This node uses the Jack transport API to get information about BPM, time + * signature, etc.. all sample accurate. Using this you can do + * tempo-synced effects or even synthesis, etc. + */ +class TransportNode : public NodeBase +{ +public: + TransportNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); + + virtual void process(ProcessContext& context); +}; + + +} // namespace Ingen + +#endif // TRANSPORTNODE_H diff --git a/src/engine/internals/Trigger.cpp b/src/engine/internals/Trigger.cpp new file mode 100644 index 00000000..cb7ae2fe --- /dev/null +++ b/src/engine/internals/Trigger.cpp @@ -0,0 +1,152 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2009 Dave Robillard + * + * 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 +#include "raul/midi_events.h" +#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" + +using namespace std; + +namespace Ingen { + +using namespace Shared; + +static InternalPlugin trigger_plugin(NS_INTERNALS "Trigger", "trigger"); + +TriggerNode::TriggerNode(const string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size) + : NodeBase(&trigger_plugin, path, false, parent, srate, buffer_size) + , _learning(false) +{ + _ports = new Raul::Array(5); + + _midi_in_port = new InputPort(this, "input", 0, 1, DataType::EVENT, Raul::Atom(), _buffer_size); + _ports->at(0) = _midi_in_port; + + _note_port = new InputPort(this, "note", 1, 1, DataType::CONTROL, 60.0f, 1); + _note_port->set_property("lv2:minimum", 0.0f); + _note_port->set_property("lv2:maximum", 127.0f); + _note_port->set_property("lv2:integer", true); + _ports->at(1) = _note_port; + + _gate_port = new OutputPort(this, "gate", 2, 1, DataType::AUDIO, 0.0f, _buffer_size); + _gate_port->set_property("lv2:toggled", true); + _ports->at(2) = _gate_port; + + _trig_port = new OutputPort(this, "trigger", 3, 1, DataType::AUDIO, 0.0f, _buffer_size); + _trig_port->set_property("lv2:toggled", true); + _ports->at(3) = _trig_port; + + _vel_port = new OutputPort(this, "velocity", 4, 1, DataType::AUDIO, 0.0f, _buffer_size); + _vel_port->set_property("lv2:minimum", 0.0f); + _vel_port->set_property("lv2:maximum", 1.0f); + _ports->at(4) = _vel_port; +} + + +void +TriggerNode::process(ProcessContext& context) +{ + NodeBase::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); + //assert(midi_in->this_nframes() == context.nframes()); + + 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))->set_value(0.0f, context.start(), time); + default: + break; + } + } + + midi_in->increment(); + } + + NodeBase::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()); + assert(time - context.start() < _buffer_size); + + if (_learning) { + _note_port->set_value(note_num); + ((AudioBuffer*)_note_port->buffer(0))->set_value( + (float)note_num, context.start(), context.end()); + _note_port->broadcast_value(context, true); + _learning = false; + } + + /*cerr << "[TriggerNode] " << path() << " Note " << (int)note_num << " on @ " << time << endl;*/ + + Sample filter_note = ((AudioBuffer*)_note_port->buffer(0))->value_at(0); + if (filter_note >= 0.0 && filter_note < 127.0 && (note_num == (uint8_t)filter_note)) { + ((AudioBuffer*)_gate_port->buffer(0))->set_value(1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(0))->set_value(1.0f, context.start(), time); + ((AudioBuffer*)_trig_port->buffer(0))->set_value(0.0f, context.start(), time + 1); + ((AudioBuffer*)_vel_port->buffer(0))->set_value(velocity / 127.0f, context.start(), time); + assert(((AudioBuffer*)_trig_port->buffer(0))->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()); + assert(time - context.start() < _buffer_size); + + if (note_num == lrintf(((AudioBuffer*)_note_port->buffer(0))->value_at(0))) + ((AudioBuffer*)_gate_port->buffer(0))->set_value(0.0f, context.start(), time); +} + + +} // namespace Ingen + diff --git a/src/engine/internals/Trigger.hpp b/src/engine/internals/Trigger.hpp new file mode 100644 index 00000000..973b9b41 --- /dev/null +++ b/src/engine/internals/Trigger.hpp @@ -0,0 +1,65 @@ +/* This file is part of Ingen. + * Copyright (C) 2007-2009 Dave Robillard + * + * 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 MIDITRIGGERNODE_H +#define MIDITRIGGERNODE_H + +#include +#include "NodeBase.hpp" + +namespace Ingen { + +class InputPort; +class OutputPort; + + +/** 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 NodeBase +{ +public: + TriggerNode(const std::string& path, bool polyphonic, PatchImpl* parent, SampleRate srate, size_t buffer_size); + + 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; } + +private: + bool _learning; + + InputPort* _midi_in_port; + InputPort* _note_port; + OutputPort* _gate_port; + OutputPort* _trig_port; + OutputPort* _vel_port; +}; + + +} // namespace Ingen + +#endif // MIDITRIGGERNODE_H diff --git a/src/engine/wscript b/src/engine/wscript index e869534a..b4d0dc7a 100644 --- a/src/engine/wscript +++ b/src/engine/wscript @@ -15,11 +15,7 @@ def build(bld): EventSink.cpp GraphObjectImpl.cpp InputPort.cpp - InternalController.cpp - InternalNote.cpp InternalPlugin.cpp - InternalTransport.cpp - InternalTrigger.cpp LV2EventBuffer.cpp MessageContext.cpp NodeBase.cpp @@ -35,6 +31,10 @@ def build(bld): events/SendPortActivity.cpp events/SendPortValue.cpp ingen_engine.cpp + internals/Controller.cpp + internals/Note.cpp + internals/Transport.cpp + internals/Trigger.cpp ''' obj = bld.new_task_gen('cxx', 'shlib') -- cgit v1.2.1