basic skeleton for new MIDISurface base class
This commit is contained in:
parent
16ce8b3331
commit
4fbf3028aa
113
libs/surfaces/midi_surface/midi_byte_array.cc
Normal file
113
libs/surfaces/midi_surface/midi_byte_array.cc
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Paul Davis <paul@linuxaudiosystems.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 more 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
#include "midi_byte_array.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cstdarg>
|
||||
#include <iomanip>
|
||||
#include <stdexcept>
|
||||
|
||||
MidiByteArray::MidiByteArray (size_t size, MIDI::byte array[])
|
||||
: std::vector<MIDI::byte>()
|
||||
{
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
push_back (array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
MidiByteArray::MidiByteArray (size_t count, MIDI::byte first, ...)
|
||||
: vector<MIDI::byte>()
|
||||
{
|
||||
push_back (first);
|
||||
va_list var_args;
|
||||
va_start (var_args, first);
|
||||
for (size_t i = 1; i < count; ++i)
|
||||
{
|
||||
MIDI::byte b = va_arg (var_args, int);
|
||||
push_back (b);
|
||||
}
|
||||
va_end (var_args);
|
||||
}
|
||||
|
||||
|
||||
void MidiByteArray::copy (size_t count, MIDI::byte * arr)
|
||||
{
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
push_back (arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
MidiByteArray & operator << (MidiByteArray & mba, const MIDI::byte & b)
|
||||
{
|
||||
mba.push_back (b);
|
||||
return mba;
|
||||
}
|
||||
|
||||
MidiByteArray & operator << (MidiByteArray & mba, const MidiByteArray & barr)
|
||||
{
|
||||
std::back_insert_iterator<MidiByteArray> bit (mba);
|
||||
copy (barr.begin(), barr.end(), bit);
|
||||
return mba;
|
||||
}
|
||||
|
||||
std::ostream & operator << (std::ostream & os, const MidiByteArray & mba)
|
||||
{
|
||||
os << "[";
|
||||
char fill = os.fill('0');
|
||||
for (MidiByteArray::const_iterator it = mba.begin(); it != mba.end(); ++it) {
|
||||
if (it != mba.begin()) os << " ";
|
||||
os << std::hex << std::setw(2) << (int)*it;
|
||||
}
|
||||
os.fill (fill);
|
||||
os << std::dec;
|
||||
os << "]";
|
||||
return os;
|
||||
}
|
||||
|
||||
MidiByteArray & operator << (MidiByteArray & mba, const std::string & st)
|
||||
{
|
||||
/* note that this assumes that "st" is ASCII encoded
|
||||
*/
|
||||
|
||||
mba.insert (mba.end(), st.begin(), st.end());
|
||||
return mba;
|
||||
}
|
||||
|
||||
bool
|
||||
MidiByteArray::compare_n (const MidiByteArray& other, MidiByteArray::size_type n) const
|
||||
{
|
||||
MidiByteArray::const_iterator us = begin();
|
||||
MidiByteArray::const_iterator them = other.begin();
|
||||
|
||||
while (n && us != end() && them != other.end()) {
|
||||
if ((*us) != (*them)) {
|
||||
return false;
|
||||
}
|
||||
--n;
|
||||
++us;
|
||||
++them;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
78
libs/surfaces/midi_surface/midi_byte_array.h
Normal file
78
libs/surfaces/midi_surface/midi_byte_array.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Paul Davis <paul@linuxaudiosystems.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 more 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
#ifndef midi_byte_array_h
|
||||
#define midi_byte_array_h
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/shared_array.hpp>
|
||||
|
||||
//#include <midi++/types.h>
|
||||
namespace MIDI {
|
||||
typedef unsigned char byte;
|
||||
}
|
||||
|
||||
/**
|
||||
To make building arrays of bytes easier. Thusly:
|
||||
|
||||
MidiByteArray mba;
|
||||
mba << 0xf0 << 0x00 << 0xf7;
|
||||
|
||||
MidiByteArray buf;
|
||||
buf << mba;
|
||||
|
||||
MidiByteArray direct( 3, 0xf0, 0x00, 0xf7 );
|
||||
|
||||
cout << mba << endl;
|
||||
cout << buf << endl;
|
||||
cout << direct << endl;
|
||||
|
||||
will all result in "f0 00 f7" being output to stdout
|
||||
*/
|
||||
class MidiByteArray : public std::vector<MIDI::byte>
|
||||
{
|
||||
public:
|
||||
MidiByteArray() : std::vector<MIDI::byte>() {}
|
||||
|
||||
MidiByteArray( size_t count, MIDI::byte array[] );
|
||||
|
||||
bool compare_n (const MidiByteArray& other, MidiByteArray::size_type len) const;
|
||||
|
||||
/**
|
||||
Accepts a preceding count, and then a list of bytes
|
||||
*/
|
||||
MidiByteArray( size_t count, MIDI::byte first, ... );
|
||||
|
||||
/// copy the given number of bytes from the given array
|
||||
void copy( size_t count, MIDI::byte arr[] );
|
||||
};
|
||||
|
||||
/// append the given byte to the end of the array
|
||||
MidiByteArray & operator << ( MidiByteArray & mba, const MIDI::byte & b );
|
||||
|
||||
/// append the given string to the end of the array
|
||||
MidiByteArray & operator << ( MidiByteArray & mba, const std::string & );
|
||||
|
||||
/// append the given array to the end of this array
|
||||
MidiByteArray & operator << ( MidiByteArray & mba, const MidiByteArray & barr );
|
||||
|
||||
/// output the bytes as hex to the given stream
|
||||
std::ostream & operator << ( std::ostream & os, const MidiByteArray & mba );
|
||||
|
||||
#endif
|
389
libs/surfaces/midi_surface/midi_surface.cc
Normal file
389
libs/surfaces/midi_surface/midi_surface.cc
Normal file
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 more 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "pbd/debug.h"
|
||||
#include "pbd/i18n.h"
|
||||
|
||||
#include "ardour/async_midi_port.h"
|
||||
#include "ardour/audioengine.h"
|
||||
#include "ardour/bundle.h"
|
||||
#include "ardour/debug.h"
|
||||
#include "ardour/midiport_manager.h"
|
||||
#include "ardour/midi_port.h"
|
||||
#include "ardour/session.h"
|
||||
|
||||
#include "midi_surface.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
using namespace Glib;
|
||||
using namespace PBD;
|
||||
|
||||
MIDISurface::MIDISurface (ARDOUR::Session& s, std::string const & namestr, bool use_pad_filter)
|
||||
: ControlProtocol (s, namestr)
|
||||
, AbstractUI<MidiSurfaceRequest> (namestr)
|
||||
, with_pad_filter (use_pad_filter)
|
||||
, _in_use (false)
|
||||
{
|
||||
|
||||
ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_connections, MISSING_INVALIDATOR, boost::bind (&MIDISurface::port_registration_handler, this), this);
|
||||
ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connections, MISSING_INVALIDATOR, boost::bind (&MIDISurface::connection_handler, this, _1, _2, _3, _4, _5), this);
|
||||
port_registration_handler ();
|
||||
}
|
||||
|
||||
int
|
||||
MIDISurface::ports_acquire ()
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, "acquiring ports\n");
|
||||
|
||||
/* setup ports */
|
||||
|
||||
_async_in = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Push 2 in"), true);
|
||||
_async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Push 2 out"), true);
|
||||
|
||||
if (_async_in == 0 || _async_out == 0) {
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, "cannot register ports\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* We do not add our ports to the input/output bundles because we don't
|
||||
* want users wiring them by hand. They could use JACK tools if they
|
||||
* really insist on that (and use JACK)
|
||||
*/
|
||||
|
||||
_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
|
||||
_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_out).get();
|
||||
|
||||
/* Create a shadow port where, depending on the state of the surface,
|
||||
* we will make pad note on/off events appear. The surface code will
|
||||
* automatically this port to the first selected MIDI track.
|
||||
*/
|
||||
|
||||
if (with_pad_filter) {
|
||||
boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in)->add_shadow_port (string_compose (_("%1 Pads"), X_("Push 2")), boost::bind (&MIDISurface::pad_filter, this, _1, _2));
|
||||
boost::shared_ptr<MidiPort> shadow_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in)->shadow_port();
|
||||
|
||||
if (shadow_port) {
|
||||
|
||||
_output_bundle.reset (new ARDOUR::Bundle (_("Push 2 Pads"), false));
|
||||
|
||||
_output_bundle->add_channel (
|
||||
shadow_port->name(),
|
||||
ARDOUR::DataType::MIDI,
|
||||
session->engine().make_port_name_non_relative (shadow_port->name())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
session->BundleAddedOrRemoved ();
|
||||
|
||||
connect_to_parser ();
|
||||
|
||||
/* Connect input port to event loop */
|
||||
|
||||
AsyncMIDIPort* asp;
|
||||
|
||||
asp = dynamic_cast<AsyncMIDIPort*> (_input_port);
|
||||
asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &MIDISurface::midi_input_handler), _input_port));
|
||||
asp->xthread().attach (main_loop()->get_context());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
MIDISurface::ports_release ()
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, "releasing ports\n");
|
||||
|
||||
/* wait for button data to be flushed */
|
||||
AsyncMIDIPort* asp;
|
||||
asp = dynamic_cast<AsyncMIDIPort*> (_output_port);
|
||||
asp->drain (10000, 500000);
|
||||
|
||||
{
|
||||
Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock());
|
||||
AudioEngine::instance()->unregister_port (_async_in);
|
||||
AudioEngine::instance()->unregister_port (_async_out);
|
||||
}
|
||||
|
||||
_async_in.reset ((ARDOUR::Port*) 0);
|
||||
_async_out.reset ((ARDOUR::Port*) 0);
|
||||
_input_port = 0;
|
||||
_output_port = 0;
|
||||
}
|
||||
|
||||
void
|
||||
MIDISurface::port_registration_handler ()
|
||||
{
|
||||
if (!_async_in || !_async_out) {
|
||||
/* ports not registered yet */
|
||||
return;
|
||||
}
|
||||
|
||||
if (_async_in->connected() && _async_out->connected()) {
|
||||
/* don't waste cycles here */
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> in;
|
||||
std::vector<std::string> out;
|
||||
|
||||
AudioEngine::instance()->get_ports (string_compose (".*%1", input_port_name()), DataType::MIDI, PortFlags (IsPhysical|IsOutput), in);
|
||||
AudioEngine::instance()->get_ports (string_compose (".*%1", output_port_name()), DataType::MIDI, PortFlags (IsPhysical|IsInput), out);
|
||||
|
||||
if (!in.empty() && !out.empty()) {
|
||||
if (!_async_in->connected()) {
|
||||
AudioEngine::instance()->connect (_async_in->name(), in.front());
|
||||
}
|
||||
if (!_async_out->connected()) {
|
||||
AudioEngine::instance()->connect (_async_out->name(), out.front());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MIDISurface::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, "FaderPort::connection_handler start\n");
|
||||
if (!_input_port || !_output_port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_in)->name());
|
||||
std::string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_out)->name());
|
||||
|
||||
if (ni == name1 || ni == name2) {
|
||||
if (yn) {
|
||||
_connection_state |= InputConnected;
|
||||
} else {
|
||||
_connection_state &= ~InputConnected;
|
||||
}
|
||||
} else if (no == name1 || no == name2) {
|
||||
if (yn) {
|
||||
_connection_state |= OutputConnected;
|
||||
} else {
|
||||
_connection_state &= ~OutputConnected;
|
||||
}
|
||||
} else {
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
|
||||
/* not our ports */
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, string_compose ("our ports changed connection state: %1 -> %2 connected ? %3\n",
|
||||
name1, name2, yn));
|
||||
|
||||
if ((_connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
|
||||
|
||||
/* XXX this is a horrible hack. Without a short sleep here,
|
||||
something prevents the device wakeup messages from being
|
||||
sent and/or the responses from being received.
|
||||
*/
|
||||
|
||||
g_usleep (100000);
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, "device now connected for both input and output\n");
|
||||
|
||||
/* may not have the device open if it was just plugged
|
||||
in. Really need USB device detection rather than MIDI port
|
||||
detection for this to work well.
|
||||
*/
|
||||
|
||||
device_acquire ();
|
||||
begin_using_device ();
|
||||
|
||||
} else {
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, "Device disconnected (input or output or both) or not yet fully connected\n");
|
||||
stop_using_device ();
|
||||
}
|
||||
|
||||
ConnectionChange (); /* emit signal for our GUI */
|
||||
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, "connection_handler end\n");
|
||||
|
||||
return true; /* connection status changed */
|
||||
}
|
||||
|
||||
boost::shared_ptr<Port>
|
||||
MIDISurface::output_port()
|
||||
{
|
||||
return _async_out;
|
||||
}
|
||||
|
||||
boost::shared_ptr<Port>
|
||||
MIDISurface::input_port()
|
||||
{
|
||||
return _async_in;
|
||||
}
|
||||
|
||||
void
|
||||
MIDISurface::write (const MidiByteArray& data)
|
||||
{
|
||||
/* immediate delivery */
|
||||
_output_port->write (&data[0], data.size(), 0);
|
||||
}
|
||||
|
||||
bool
|
||||
MIDISurface::midi_input_handler (IOCondition ioc, MIDI::Port* port)
|
||||
{
|
||||
if (ioc & ~IO_IN) {
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, "MIDI port closed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ioc & IO_IN) {
|
||||
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, string_compose ("something happened on %1\n", port->name()));
|
||||
|
||||
AsyncMIDIPort* asp = dynamic_cast<AsyncMIDIPort*>(port);
|
||||
if (asp) {
|
||||
asp->clear ();
|
||||
}
|
||||
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, string_compose ("data available on %1\n", port->name()));
|
||||
if (_in_use) {
|
||||
samplepos_t now = AudioEngine::instance()->sample_time();
|
||||
port->parse (now);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
MIDISurface::connect_to_parser ()
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, string_compose ("Connecting to signals on port %2\n", _input_port->name()));
|
||||
|
||||
MIDI::Parser* p = _input_port->parser();
|
||||
|
||||
/* Incoming sysex */
|
||||
p->sysex.connect_same_thread (*this, boost::bind (&MIDISurface::handle_midi_sysex, this, _1, _2, _3));
|
||||
/* V-Pot messages are Controller */
|
||||
p->controller.connect_same_thread (*this, boost::bind (&MIDISurface::handle_midi_controller_message, this, _1, _2));
|
||||
/* Button messages are NoteOn */
|
||||
p->note_on.connect_same_thread (*this, boost::bind (&MIDISurface::handle_midi_note_on_message, this, _1, _2));
|
||||
/* Button messages are NoteOn but libmidi++ sends note-on w/velocity = 0 as note-off so catch them too */
|
||||
p->note_off.connect_same_thread (*this, boost::bind (&MIDISurface::handle_midi_note_on_message, this, _1, _2));
|
||||
/* Fader messages are Pitchbend */
|
||||
p->channel_pitchbend[0].connect_same_thread (*this, boost::bind (&MIDISurface::handle_midi_pitchbend_message, this, _1, _2));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
MIDISurface::thread_init ()
|
||||
{
|
||||
pthread_set_name (event_loop_name().c_str());
|
||||
|
||||
PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
|
||||
ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
|
||||
|
||||
set_thread_priority ();
|
||||
}
|
||||
|
||||
void
|
||||
MIDISurface::connect_session_signals()
|
||||
{
|
||||
// receive routes added
|
||||
//session->RouteAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&MackieControlProtocol::notify_routes_added, this, _1), this);
|
||||
// receive VCAs added
|
||||
//session->vca_manager().VCAAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&MIDISurface::notify_vca_added, this, _1), this);
|
||||
|
||||
// receive record state toggled
|
||||
session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&MIDISurface::notify_record_state_changed, this), this);
|
||||
// receive transport state changed
|
||||
session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&MIDISurface::notify_transport_state_changed, this), this);
|
||||
session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&MIDISurface::notify_loop_state_changed, this), this);
|
||||
// receive punch-in and punch-out
|
||||
Config->ParameterChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&MIDISurface::notify_parameter_changed, this, _1), this);
|
||||
session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&MIDISurface::notify_parameter_changed, this, _1), this);
|
||||
// receive rude solo changed
|
||||
session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&MIDISurface::notify_solo_active_changed, this, _1), this);
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
MIDISurface::get_state() const
|
||||
{
|
||||
XMLNode& node (ControlProtocol::get_state());
|
||||
XMLNode* child;
|
||||
|
||||
child = new XMLNode (X_("Input"));
|
||||
child->add_child_nocopy (_async_in->get_state());
|
||||
node.add_child_nocopy (*child);
|
||||
child = new XMLNode (X_("Output"));
|
||||
child->add_child_nocopy (_async_out->get_state());
|
||||
node.add_child_nocopy (*child);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
int
|
||||
MIDISurface::set_state (const XMLNode & node, int version)
|
||||
{
|
||||
DEBUG_TRACE (DEBUG::MIDISurface, string_compose ("MIDISurface::set_state: active %1\n", active()));
|
||||
|
||||
if (ControlProtocol::set_state (node, version)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
XMLNode* child;
|
||||
|
||||
if ((child = node.child (X_("Input"))) != 0) {
|
||||
XMLNode* portnode = child->child (Port::state_node_name.c_str());
|
||||
if (portnode) {
|
||||
portnode->remove_property ("name");
|
||||
_async_in->set_state (*portnode, version);
|
||||
}
|
||||
}
|
||||
|
||||
if ((child = node.child (X_("Output"))) != 0) {
|
||||
XMLNode* portnode = child->child (Port::state_node_name.c_str());
|
||||
if (portnode) {
|
||||
portnode->remove_property ("name");
|
||||
_async_out->set_state (*portnode, version);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
MIDISurface::do_request (MidiSurfaceRequest * req)
|
||||
{
|
||||
if (req->type == CallSlot) {
|
||||
|
||||
call_slot (MISSING_INVALIDATOR, req->the_slot);
|
||||
|
||||
} else if (req->type == Quit) {
|
||||
|
||||
stop_using_device ();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
MIDISurface::begin_using_device ()
|
||||
{
|
||||
_in_use = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
MIDISurface::stop_using_device ()
|
||||
{
|
||||
_in_use = false;
|
||||
return 0;
|
||||
}
|
125
libs/surfaces/midi_surface/midi_surface.h
Normal file
125
libs/surfaces/midi_surface/midi_surface.h
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 more 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#define ABSTRACT_UI_EXPORTS
|
||||
#include "pbd/abstract_ui.h"
|
||||
#include "control_protocol/control_protocol.h"
|
||||
#include "midi++/types.h"
|
||||
|
||||
#include "midi_byte_array.h"
|
||||
|
||||
namespace MIDI {
|
||||
class Parser;
|
||||
class Port;
|
||||
}
|
||||
|
||||
namespace ARDOUR {
|
||||
class Bundle;
|
||||
class Port;
|
||||
class MidiBuffer;
|
||||
}
|
||||
|
||||
struct MidiSurfaceRequest : public BaseUI::BaseRequestObject {
|
||||
public:
|
||||
MidiSurfaceRequest () {}
|
||||
~MidiSurfaceRequest () {}
|
||||
};
|
||||
|
||||
class MIDISurface : public ARDOUR::ControlProtocol
|
||||
, public AbstractUI<MidiSurfaceRequest>
|
||||
{
|
||||
public:
|
||||
MIDISurface (ARDOUR::Session&, std::string const & name, bool use_pad_filter);
|
||||
~MIDISurface ();
|
||||
|
||||
boost::shared_ptr<ARDOUR::Port> input_port();
|
||||
boost::shared_ptr<ARDOUR::Port> output_port();
|
||||
|
||||
// Bundle to represent our input ports
|
||||
boost::shared_ptr<ARDOUR::Bundle> _input_bundle;
|
||||
// Bundle to represent our output ports
|
||||
boost::shared_ptr<ARDOUR::Bundle> _output_bundle;
|
||||
|
||||
ARDOUR::Session & get_session() { return *session; }
|
||||
|
||||
virtual std::string input_port_name () const = 0;
|
||||
virtual std::string output_port_name () const = 0;
|
||||
|
||||
void write (const MidiByteArray&);
|
||||
|
||||
XMLNode& get_state() const;
|
||||
int set_state (const XMLNode & node, int version);
|
||||
|
||||
std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles ();
|
||||
|
||||
PBD::Signal0<void> ConnectionChange;
|
||||
|
||||
CONTROL_PROTOCOL_THREADS_NEED_TEMPO_MAP_DECL();
|
||||
|
||||
private:
|
||||
bool with_pad_filter;
|
||||
bool _in_use;
|
||||
MIDI::Port* _input_port;
|
||||
MIDI::Port* _output_port;
|
||||
|
||||
boost::shared_ptr<ARDOUR::Port> _async_in;
|
||||
boost::shared_ptr<ARDOUR::Port> _async_out;
|
||||
|
||||
void do_request (MidiSurfaceRequest*);
|
||||
|
||||
virtual void connect_to_parser ();
|
||||
virtual void handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t) {}
|
||||
virtual void handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes*) {}
|
||||
virtual void handle_midi_note_on_message (MIDI::Parser&, MIDI::EventTwoBytes*) {}
|
||||
virtual void handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes*) {}
|
||||
virtual void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count) {}
|
||||
|
||||
virtual bool midi_input_handler (Glib::IOCondition ioc, MIDI::Port* port);
|
||||
|
||||
virtual void thread_init ();
|
||||
|
||||
PBD::ScopedConnectionList session_connections;
|
||||
|
||||
virtual void connect_session_signals ();
|
||||
virtual void notify_record_state_changed () {}
|
||||
virtual void notify_transport_state_changed () {}
|
||||
virtual void notify_loop_state_changed () {}
|
||||
virtual void notify_parameter_changed (std::string) {}
|
||||
virtual void notify_solo_active_changed (bool) {}
|
||||
|
||||
virtual void port_registration_handler ();
|
||||
virtual bool pad_filter (ARDOUR::MidiBuffer& in, ARDOUR::MidiBuffer& out) const { return false; }
|
||||
|
||||
enum ConnectionState {
|
||||
InputConnected = 0x1,
|
||||
OutputConnected = 0x2
|
||||
};
|
||||
|
||||
int _connection_state;
|
||||
|
||||
virtual bool connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn);
|
||||
PBD::ScopedConnectionList port_connections;
|
||||
|
||||
virtual int ports_acquire ();
|
||||
virtual void ports_release ();
|
||||
|
||||
virtual int begin_using_device ();
|
||||
virtual int stop_using_device ();
|
||||
virtual int device_acquire () = 0;
|
||||
virtual void device_release () = 0;
|
||||
};
|
33
libs/surfaces/midi_surface/wscript
Normal file
33
libs/surfaces/midi_surface/wscript
Normal file
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env python
|
||||
from waflib.extras import autowaf as autowaf
|
||||
import os
|
||||
|
||||
# Mandatory variables
|
||||
top = '.'
|
||||
out = 'build'
|
||||
|
||||
def options(opt):
|
||||
autowaf.set_options(opt)
|
||||
|
||||
def configure(conf):
|
||||
pass
|
||||
|
||||
def build(bld):
|
||||
obj = bld(features = 'cxx cxxshlib')
|
||||
obj.source = '''
|
||||
midi_surface.cc
|
||||
midi_byte_array.cc
|
||||
'''
|
||||
obj.export_includes = ['.']
|
||||
obj.defines = [ 'PACKAGE="ardour_midisurface"' ]
|
||||
obj.defines += [ 'ARDOURSURFACE_DLL_EXPORTS' ]
|
||||
obj.defines += [ 'VERSIONSTRING="' + bld.env['VERSION'] + '"' ]
|
||||
obj.includes = ['.', './midi_surface']
|
||||
obj.name = 'libardour_midisurface'
|
||||
obj.target = 'ardour_midisurface'
|
||||
obj.uselib = 'SIGCPP XML OSX'
|
||||
obj.use = 'libardour libardour_cp libpbd libevoral libmidipp libtemporal'
|
||||
obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces')
|
||||
|
||||
def shutdown():
|
||||
autowaf.shutdown()
|
|
@ -20,15 +20,16 @@ top = '.'
|
|||
out = 'build'
|
||||
|
||||
children = [
|
||||
'control_protocol',
|
||||
'faderport',
|
||||
'faderport8',
|
||||
'cc121',
|
||||
'generic_midi',
|
||||
'mackie',
|
||||
'us2400',
|
||||
'launch_control_xl',
|
||||
'osc',
|
||||
'control_protocol',
|
||||
'midi_surface',
|
||||
'faderport',
|
||||
'faderport8',
|
||||
'cc121',
|
||||
'generic_midi',
|
||||
'mackie',
|
||||
'us2400',
|
||||
'launch_control_xl',
|
||||
'osc',
|
||||
]
|
||||
|
||||
def options(opt):
|
||||
|
@ -82,6 +83,7 @@ def configure(conf):
|
|||
|
||||
def build(bld):
|
||||
bld.recurse('control_protocol')
|
||||
bld.recurse('midi_surface')
|
||||
bld.recurse('generic_midi')
|
||||
bld.recurse('faderport')
|
||||
bld.recurse('faderport8')
|
||||
|
|
Loading…
Reference in New Issue
Block a user