#include "timecode/time.h"
#include "timecode/bbt_time.h"
+#include "ardour/amp.h"
#include "ardour/async_midi_port.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "pbd/i18n.h"
+#ifdef PLATFORM_WINDOWS
+#define random() rand()
+#endif
+
using namespace ARDOUR;
using namespace std;
using namespace PBD;
#define ABLETON 0x2982
#define PUSH2 0x1967
-__attribute__((constructor)) static void
-register_enums ()
-{
- EnumWriter& enum_writer (EnumWriter::instance());
- vector<int> i;
- vector<string> s;
-
- MusicalMode::Type mode;
-
-#define REGISTER(e) enum_writer.register_distinct (typeid(e).name(), i, s); i.clear(); s.clear()
-#define REGISTER_CLASS_ENUM(t,e) i.push_back (t::e); s.push_back (#e)
-
- REGISTER_CLASS_ENUM (MusicalMode,Dorian);
- REGISTER_CLASS_ENUM (MusicalMode, IonianMajor);
- REGISTER_CLASS_ENUM (MusicalMode, Minor);
- REGISTER_CLASS_ENUM (MusicalMode, HarmonicMinor);
- REGISTER_CLASS_ENUM (MusicalMode, MelodicMinorAscending);
- REGISTER_CLASS_ENUM (MusicalMode, MelodicMinorDescending);
- REGISTER_CLASS_ENUM (MusicalMode, Phrygian);
- REGISTER_CLASS_ENUM (MusicalMode, Lydian);
- REGISTER_CLASS_ENUM (MusicalMode, Mixolydian);
- REGISTER_CLASS_ENUM (MusicalMode, Aeolian);
- REGISTER_CLASS_ENUM (MusicalMode, Locrian);
- REGISTER_CLASS_ENUM (MusicalMode, PentatonicMajor);
- REGISTER_CLASS_ENUM (MusicalMode, PentatonicMinor);
- REGISTER_CLASS_ENUM (MusicalMode, Chromatic);
- REGISTER_CLASS_ENUM (MusicalMode, BluesScale);
- REGISTER_CLASS_ENUM (MusicalMode, NeapolitanMinor);
- REGISTER_CLASS_ENUM (MusicalMode, NeapolitanMajor);
- REGISTER_CLASS_ENUM (MusicalMode, Oriental);
- REGISTER_CLASS_ENUM (MusicalMode, DoubleHarmonic);
- REGISTER_CLASS_ENUM (MusicalMode, Enigmatic);
- REGISTER_CLASS_ENUM (MusicalMode, Hirajoshi);
- REGISTER_CLASS_ENUM (MusicalMode, HungarianMinor);
- REGISTER_CLASS_ENUM (MusicalMode, HungarianMajor);
- REGISTER_CLASS_ENUM (MusicalMode, Kumoi);
- REGISTER_CLASS_ENUM (MusicalMode, Iwato);
- REGISTER_CLASS_ENUM (MusicalMode, Hindu);
- REGISTER_CLASS_ENUM (MusicalMode, Spanish8Tone);
- REGISTER_CLASS_ENUM (MusicalMode, Pelog);
- REGISTER_CLASS_ENUM (MusicalMode, HungarianGypsy);
- REGISTER_CLASS_ENUM (MusicalMode, Overtone);
- REGISTER_CLASS_ENUM (MusicalMode, LeadingWholeTone);
- REGISTER_CLASS_ENUM (MusicalMode, Arabian);
- REGISTER_CLASS_ENUM (MusicalMode, Balinese);
- REGISTER_CLASS_ENUM (MusicalMode, Gypsy);
- REGISTER_CLASS_ENUM (MusicalMode, Mohammedan);
- REGISTER_CLASS_ENUM (MusicalMode, Javanese);
- REGISTER_CLASS_ENUM (MusicalMode, Persian);
- REGISTER_CLASS_ENUM (MusicalMode, Algerian);
- REGISTER (mode);
-}
-
Push2::Push2 (ARDOUR::Session& s)
: ControlProtocol (s, string (X_("Ableton Push 2")))
, AbstractUI<Push2Request> (name())
, handle (0)
+ , in_use (false)
, _modifier_state (None)
, splash_start (0)
, _current_layout (0)
, _pressure_mode (AfterTouch)
, selection_color (LED::Green)
, contrast_color (LED::Green)
+ , in_range_select (false)
{
+ /* we're going to need this */
+
+ libusb_init (NULL);
build_maps ();
build_color_map ();
/* master cannot be removed, so no need to connect to going-away signal */
master = session->master_out ();
- if (open ()) {
- throw failed_constructor ();
- }
-
ControlProtocol::StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_selection_change, this, _1), this);
- /* catch current selection, if any */
- {
- StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected()));
- stripable_selection_change (sp);
- }
+ /* allocate graphics layouts, even though we're not using them yet */
+
+ _canvas = new Push2Canvas (*this, 960, 160);
+ mix_layout = new MixLayout (*this, *session, "globalmix");
+ scale_layout = new ScaleLayout (*this, *session, "scale");
+ track_mix_layout = new TrackMixLayout (*this, *session, "trackmix");
+ splash_layout = new SplashLayout (*this, *session, "splash");
+
+ run_event_loop ();
+
+ /* Ports exist for the life of this instance */
+
+ ports_acquire ();
/* catch arrival and departure of Push2 itself */
ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&Push2::port_registration_handler, this), this);
/* Catch port connections and disconnections */
ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&Push2::connection_handler, this, _1, _2, _3, _4, _5), this);
- /* ports might already be there */
+ /* Push 2 ports might already be there */
port_registration_handler ();
}
Push2::~Push2 ()
{
- stop ();
+ selection_connection.disconnect ();
+
+ stop_event_loop (); /* this will call stop_using_device () in Quit request handler */
+ device_release ();
+ ports_release ();
+
+ if (_current_layout) {
+ _canvas->root()->remove (_current_layout);
+ _current_layout = 0;
+ }
- delete track_mix_layout;
delete mix_layout;
+ mix_layout = 0;
delete scale_layout;
+ scale_layout = 0;
+ delete splash_layout;
+ splash_layout = 0;
}
void
-Push2::port_registration_handler ()
+Push2::run_event_loop ()
{
- if (!_async_in && !_async_out) {
- /* ports not registered yet */
- return;
- }
+ DEBUG_TRACE (DEBUG::Push2, "start event loop\n");
+ BaseUI::run ();
+}
- if (_async_in->connected() && _async_out->connected()) {
- /* don't waste cycles here */
- return;
- }
+void
+Push2::stop_event_loop ()
+{
+ DEBUG_TRACE (DEBUG::Push2, "stop event loop\n");
+ BaseUI::quit ();
+}
- string input_port_name = X_("Ableton Push 2 MIDI 1 in");
- string output_port_name = X_("Ableton Push 2 MIDI 1 out");
- vector<string> in;
- vector<string> out;
+int
+Push2::begin_using_device ()
+{
+ DEBUG_TRACE (DEBUG::Push2, "begin using device\n");
- 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);
+ /* set up periodic task used to push a frame buffer to the
+ * device (25fps). The device can handle 60fps, but we don't
+ * need that frame rate.
+ */
- if (!in.empty() && !out.empty()) {
- cerr << "Push2: both ports found\n";
- cerr << "\tconnecting to " << in.front() << " + " << out.front() << endl;
- if (!_async_in->connected()) {
- AudioEngine::instance()->connect (_async_in->name(), in.front());
- }
- if (!_async_out->connected()) {
- AudioEngine::instance()->connect (_async_out->name(), out.front());
- }
+ Glib::RefPtr<Glib::TimeoutSource> vblank_timeout = Glib::TimeoutSource::create (40); // milliseconds
+ vblank_connection = vblank_timeout->connect (sigc::mem_fun (*this, &Push2::vblank));
+ vblank_timeout->attach (main_loop()->get_context());
+
+ connect_session_signals ();
+
+ init_buttons (true);
+ init_touch_strip ();
+ set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
+ splash ();
+
+ /* catch current selection, if any so that we can wire up the pads if appropriate */
+ {
+ StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected()));
+ stripable_selection_change (sp);
}
+
+ request_pressure_mode ();
+
+ in_use = true;
+
+ return 0;
}
int
-Push2::open ()
+Push2::stop_using_device ()
{
- int err;
+ DEBUG_TRACE (DEBUG::Push2, "stop using device\n");
- if (handle) {
- /* already open */
+ if (!in_use) {
+ DEBUG_TRACE (DEBUG::Push2, "nothing to do, device not in use\n");
return 0;
}
- if ((handle = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) {
- return -1;
- }
+ init_buttons (false);
+ strip_buttons_off ();
- if ((err = libusb_claim_interface (handle, 0x00))) {
- return -1;
- }
+ vblank_connection.disconnect ();
+ session_connections.drop_connections ();
- try {
- _canvas = new Push2Canvas (*this, 960, 160);
- mix_layout = new MixLayout (*this, *session);
- scale_layout = new ScaleLayout (*this, *session);
- track_mix_layout = new TrackMixLayout (*this, *session);
- splash_layout = new SplashLayout (*this, *session);
- } catch (...) {
- error << _("Cannot construct Canvas for display") << endmsg;
- libusb_release_interface (handle, 0x00);
- libusb_close (handle);
- return -1;
- }
+ in_use = false;
+ return 0;
+}
+
+int
+Push2::ports_acquire ()
+{
+ DEBUG_TRACE (DEBUG::Push2, "acquiring ports\n");
/* setup ports */
_async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Push 2 out"), true);
if (_async_in == 0 || _async_out == 0) {
+ DEBUG_TRACE (DEBUG::Push2, "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.
+ * really insist on that (and use JACK)
*/
_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
connect_to_parser ();
- return 0;
-}
+ /* Connect input port to event loop */
-list<boost::shared_ptr<ARDOUR::Bundle> >
-Push2::bundles ()
-{
- list<boost::shared_ptr<ARDOUR::Bundle> > b;
+ AsyncMIDIPort* asp;
- if (_output_bundle) {
- b.push_back (_output_bundle);
- }
+ asp = dynamic_cast<AsyncMIDIPort*> (_input_port);
+ asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &Push2::midi_input_handler), _input_port));
+ asp->xthread().attach (main_loop()->get_context());
- return b;
+ return 0;
}
-int
-Push2::close ()
+void
+Push2::ports_release ()
{
- init_buttons (false);
- strip_buttons_off ();
+ DEBUG_TRACE (DEBUG::Push2, "releasing ports\n");
/* wait for button data to be flushed */
AsyncMIDIPort* asp;
_async_out.reset ((ARDOUR::Port*) 0);
_input_port = 0;
_output_port = 0;
+}
- periodic_connection.disconnect ();
- session_connections.drop_connections ();
+int
+Push2::device_acquire ()
+{
+ int err;
- if (_current_layout) {
- _canvas->root()->remove (_current_layout);
- _current_layout = 0;
+ DEBUG_TRACE (DEBUG::Push2, "acquiring device\n");
+
+ if (handle) {
+ DEBUG_TRACE (DEBUG::Push2, "open() called with handle already set\n");
+ /* already open */
+ return 0;
}
- delete mix_layout;
- mix_layout = 0;
- delete scale_layout;
- scale_layout = 0;
- delete splash_layout;
- splash_layout = 0;
+ if ((handle = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) {
+ DEBUG_TRACE (DEBUG::Push2, "failed to open USB handle\n");
+ return -1;
+ }
+
+ if ((err = libusb_claim_interface (handle, 0x00))) {
+ DEBUG_TRACE (DEBUG::Push2, "failed to claim USB device\n");
+ libusb_close (handle);
+ handle = 0;
+ return -1;
+ }
+ return 0;
+}
+
+void
+Push2::device_release ()
+{
+ DEBUG_TRACE (DEBUG::Push2, "releasing device\n");
if (handle) {
libusb_release_interface (handle, 0x00);
libusb_close (handle);
handle = 0;
}
+}
- return 0;
+list<boost::shared_ptr<ARDOUR::Bundle> >
+Push2::bundles ()
+{
+ list<boost::shared_ptr<ARDOUR::Bundle> > b;
+
+ if (_output_bundle) {
+ b.push_back (_output_bundle);
+ }
+
+ return b;
}
void
*/
ButtonID buttons[] = { Mute, Solo, Master, Up, Right, Left, Down, Note, Session, Mix, AddTrack, Delete, Undo,
- Metronome, Shift, Select, Play, RecordEnable, Automate, Repeat, Note, Session, DoubleLoop,
+ Metronome, Shift, Select, Play, RecordEnable, Automate, Repeat, Note, Session,
Quantize, Duplicate, Browse, PageRight, PageLeft, OctaveUp, OctaveDown, Layout, Scale
};
bool
Push2::probe ()
{
- libusb_device_handle *h;
- libusb_init (NULL);
-
- if ((h = libusb_open_device_with_vid_pid (NULL, ABLETON, PUSH2)) == 0) {
- DEBUG_TRACE (DEBUG::Push2, "no Push2 device found\n");
- return false;
- }
-
- libusb_close (h);
- DEBUG_TRACE (DEBUG::Push2, "Push2 device located\n");
return true;
}
} else if (req->type == Quit) {
- stop ();
+ stop_using_device ();
}
}
-int
-Push2::stop ()
-{
- BaseUI::quit ();
- close ();
- return 0;
-}
-
-
void
Push2::splash ()
{
}
}
+ if (_current_layout) {
+ _current_layout->update_meters ();
+ _current_layout->update_clocks ();
+ }
+
_canvas->vblank();
return true;
if (yn) {
- /* start event loop */
-
- BaseUI::run ();
-
- if (open ()) {
- DEBUG_TRACE (DEBUG::Push2, "device open failed\n");
- close ();
+ if (device_acquire ()) {
return -1;
}
- /* 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, &Push2::midi_input_handler), _input_port));
- asp->xthread().attach (main_loop()->get_context());
-
- connect_session_signals ();
-
- /* set up periodic task used to push a frame buffer to the
- * device (25fps). The device can handle 60fps, but we don't
- * need that frame rate.
- */
-
- Glib::RefPtr<Glib::TimeoutSource> vblank_timeout = Glib::TimeoutSource::create (40); // milliseconds
- vblank_connection = vblank_timeout->connect (sigc::mem_fun (*this, &Push2::vblank));
- vblank_timeout->attach (main_loop()->get_context());
-
-
- Glib::RefPtr<Glib::TimeoutSource> periodic_timeout = Glib::TimeoutSource::create (1000); // milliseconds
- periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &Push2::periodic));
- periodic_timeout->attach (main_loop()->get_context());
-
- init_buttons (true);
- init_touch_strip ();
- set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
- splash ();
+ if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
+ begin_using_device ();
+ } else {
+ /* begin_using_device () will get called once we're connected */
+ }
} else {
-
- stop ();
-
+ /* Control Protocol Manager never calls us with false, but
+ * insteads destroys us.
+ */
}
ControlProtocol::set_active (yn);
if (ioc & IO_IN) {
- // DEBUG_TRACE (DEBUG::Push2, string_compose ("something happend on %1\n", port->name()));
+ DEBUG_TRACE (DEBUG::Push2, string_compose ("something happend on %1\n", port->name()));
AsyncMIDIPort* asp = dynamic_cast<AsyncMIDIPort*>(port);
if (asp) {
asp->clear ();
}
- //DEBUG_TRACE (DEBUG::Push2, string_compose ("data available on %1\n", port->name()));
- framepos_t now = AudioEngine::instance()->sample_time();
- port->parse (now);
+ DEBUG_TRACE (DEBUG::Push2, string_compose ("data available on %1\n", port->name()));
+ if (in_use) {
+ framepos_t now = AudioEngine::instance()->sample_time();
+ port->parse (now);
+ }
}
return true;
}
-bool
-Push2::periodic ()
-{
- return true;
-}
-
void
Push2::connect_to_parser ()
{
void
Push2::other_vpot (int n, int delta)
{
+ boost::shared_ptr<Amp> click_gain;
switch (n) {
case 0:
+ /* tempo control */
break;
case 1:
+ /* metronome gain control */
+ click_gain = session->click_gain();
+ if (click_gain) {
+ boost::shared_ptr<AutomationControl> ac = click_gain->gain_control();
+ if (ac) {
+ ac->set_value (ac->interface_to_internal (
+ min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + (delta/256.0)))),
+ PBD::Controllable::UseGroup);
+ }
+ }
break;
case 2:
/* master gain control */
if (master) {
boost::shared_ptr<AutomationControl> ac = master->gain_control();
if (ac) {
- ac->set_value (ac->get_value() + ((2.0/64.0) * delta), PBD::Controllable::UseGroup);
+ ac->set_value (ac->interface_to_internal (
+ min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + (delta/256.0)))),
+ PBD::Controllable::UseGroup);
}
}
break;
return matched;
}
+void
+Push2::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;
+ }
+
+#ifdef __APPLE__
+ /* the origin of the numeric magic identifiers is known only to Ableton
+ and may change in time. This is part of how CoreMIDI works.
+ */
+ string input_port_name = X_("system:midi_capture_1319078870");
+ string output_port_name = X_("system:midi_playback_3409210341");
+#else
+ string input_port_name = X_("Ableton Push 2 MIDI 1 in");
+ string output_port_name = X_("Ableton Push 2 MIDI 1 out");
+#endif
+ vector<string> in;
+ vector<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()) {
+ cerr << "Push2: both ports found\n";
+ cerr << "\tconnecting to " << in.front() << " + " << out.front() << endl;
+ 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
Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
{
- DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler start\n");
+ DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler start\n");
if (!_input_port || !_output_port) {
return false;
}
connection_state &= ~OutputConnected;
}
} else {
- DEBUG_TRACE (DEBUG::FaderPort, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
+ DEBUG_TRACE (DEBUG::Push2, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
/* not our ports */
return false;
}
+ DEBUG_TRACE (DEBUG::Push2, 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,
*/
g_usleep (100000);
- DEBUG_TRACE (DEBUG::FaderPort, "device now connected for both input and output\n");
- connected ();
+ DEBUG_TRACE (DEBUG::Push2, "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::FaderPort, "Device disconnected (input or output or both) or not yet fully connected\n");
+ stop_using_device ();
}
ConnectionChange (); /* emit signal for our GUI */
return true; /* connection status changed */
}
-void
-Push2::connected ()
-{
- request_pressure_mode ();
-}
-
boost::shared_ptr<Port>
Push2::output_port()
{
int drum_note = 36;
+ fn_pad_map.clear ();
+
for (int row = 0; row < 8; ++row) {
for (int col = 0; col < 4; ++col) {
/* disconnect from pad port, if appropriate */
if (current_midi_track && pad_port) {
+
+ /* XXX this could possibly leave dangling MIDI notes.
+ *
+ * A general libardour fix is required. It isn't obvious
+ * how note resolution can be done unless disconnecting
+ * becomes "slow" (i.e. deferred for as long as it takes
+ * to resolve notes).
+ */
current_midi_track->input()->disconnect (current_midi_track->input()->nth(0), pad_port->name(), this);
}
void
Push2::set_current_layout (Push2Layout* layout)
{
- if (_current_layout) {
- _current_layout->hide ();
- _canvas->root()->remove (_current_layout);
- _previous_layout = _current_layout;
- }
+ if (layout && layout == _current_layout) {
+ _current_layout->show ();
+ } else {
- _current_layout = layout;
+ if (_current_layout) {
+ _current_layout->hide ();
+ _canvas->root()->remove (_current_layout);
+ _previous_layout = _current_layout;
+ }
- if (_current_layout) {
- _canvas->root()->add (_current_layout);
- _current_layout->show ();
- }
+ _current_layout = layout;
- _canvas->request_redraw ();
+ if (_current_layout) {
+ _canvas->root()->add (_current_layout);
+ _current_layout->show ();
+ }
+
+
+ _canvas->request_redraw ();
+ }
}
void