diff options
Diffstat (limited to 'libs/surfaces/mackie')
48 files changed, 9597 insertions, 0 deletions
diff --git a/libs/surfaces/mackie/SConscript b/libs/surfaces/mackie/SConscript new file mode 100644 index 0000000000..7bd1357cb3 --- /dev/null +++ b/libs/surfaces/mackie/SConscript @@ -0,0 +1,74 @@ +# -*- python -*- + +import os +import os.path +import glob + +Import('env final_prefix install_prefix final_config_prefix libraries i18n') + +mackie = env.Copy() + +# +# this defines the version number of libardour_mackie +# + +domain = 'ardour_mackie' + +mackie.Append(DOMAIN = domain, MAJOR = 1, MINOR = 0, MICRO = 0) +mackie.Append(CXXFLAGS = "-DPACKAGE=\\\"" + domain + "\\\"") +mackie.Append(CXXFLAGS="-DLIBSIGC_DISABLE_DEPRECATED") +mackie.Append(PACKAGE = domain) +mackie.Append(POTFILE = domain + '.pot') + +mackie_files=Split(""" +interface.cc +midi_byte_array.cc +controls.cc +route_signal.cc +mackie_midi_builder.cc +mackie_button_handler.cc +mackie_control_protocol_poll.cc +surface_port.cc +mackie_port.cc +types.cc +surface.cc +mackie_control_protocol.cc +bcf_surface.cc +mackie_surface.cc +""") + +mackie.Append(CCFLAGS="-D_REENTRANT -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE") +mackie.Append(CXXFLAGS="-DDATA_DIR=\\\""+final_prefix+"/share\\\"") +mackie.Append(CXXFLAGS="-DCONFIG_DIR=\\\""+final_config_prefix+"\\\"") +mackie.Append(CXXFLAGS="-DLOCALEDIR=\\\""+final_prefix+"/share/locale\\\"") + +mackie.Merge ([ + libraries['ardour'], + libraries['ardour_cp'], + libraries['sigc2'], + libraries['pbd'], + libraries['midi++2'], + libraries['xml'], + libraries['glib2'], + libraries['glibmm2'], + libraries['sndfile-ardour'] + ]) + +libardour_mackie = mackie.SharedLibrary('ardour_mackie', mackie_files) + +test_files = Split(""" +midi_byte_array.cc +test.cc +""") +mackie_test = Program('mackie_test', test_files ) + +if mackie['SURFACES']: + Default(libardour_mackie) + if env['NLS']: + i18n (mackie, mackie_files, env) + env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ardour2','surfaces'), libardour_mackie)) + +env.Alias('tarball', env.Distribute (env['DISTTREE'], + [ 'SConscript' ] + + mackie_files + + glob.glob('po/*.po') + glob.glob('*.h'))) diff --git a/libs/surfaces/mackie/TODO b/libs/surfaces/mackie/TODO new file mode 100644 index 0000000000..a9cb1b9878 --- /dev/null +++ b/libs/surfaces/mackie/TODO @@ -0,0 +1,45 @@ +* how long can UI signal callbacks take to execute? What happens if they block? + where ENSURE_CORRECT_THREAD is a macro that is modelled on ENSURE_GUI_THREAD + if the handler is not called in the "correct thread", it will use a pseudo-RT-safe-enough technique to get the correct thread to recall "handler" later on, and return. + +* jog with transport rolling doesn't work properly. My use of ScrollTimeline also doesn't work. +* make loop button sensitive to current transport state +* make sure rew button can go past the previous if pressed twice, relatively quickly. +* finish button mapping. Only shifted buttons left for bcf. +* concurrency for bank switching? And make sure "old" events aren't sent to "new" faders +* TODOs in code +* removal of a route results in a strip that isn't dead, but doesn't have any effect on the session +* use i18n. see string_compose +* docs in manual, including button assignment diagram + +Later +----- +* remove commented couts +* Queueing of writes? +* Generic surface code to common location +* bulk remote id changes cause too many surface updates. use Config->remote_model. +* which bank switching - overlap or dead faders? Option? +* signals for buttons? +* MackieControlProtocol in namespace Mackie? +* power-cycling of surface. fd_midiport doesn't close. +* mix busses and/or a "bus-only" bank/mode +* what about surfaces like Mackie C4 and BCR2000? + +Need UI integration +------------------- +* Some indication on the UI of currently bank-switched-in routes? + Useful for surfaces that don't have a scribble strip. +* use current zoom setting and snap state for shuttle wheel + +Actual Mackie +------------- +* docs claim that unit will send a host query on init. +* test Mackie surface object. Apparently led rings don't work. Stereo busses? +* timecode & 55 char displays +* midi bandwidth + +Bugs +---- + +* definitely something wrong with remote_id assignment on session create + (master strip assigned 0). diff --git a/libs/surfaces/mackie/bcf_surface.cc b/libs/surfaces/mackie/bcf_surface.cc new file mode 100644 index 0000000000..2aaa70fc3e --- /dev/null +++ b/libs/surfaces/mackie/bcf_surface.cc @@ -0,0 +1,1473 @@ +/* + Generated by scripts/generate-surface.rb +*/ + +#include "bcf_surface.h" + +#include "controls.h" +#include "mackie_button_handler.h" + +using namespace Mackie; + +void Mackie::BcfSurface::init_controls() +{ + // intialise groups and strips + Group * group = 0; + + // make sure there are enough strips + strips.resize( 7 ); + + group = new Group ( "user" ); + groups["user"] = group; + + group = new Group ( "assignment" ); + groups["assignment"] = group; + + group = new Group ( "none" ); + groups["none"] = group; + + group = new MasterStrip ( "master", 0 ); + groups["master"] = group; + strips[0] = dynamic_cast<Strip*>( group ); + + group = new Strip ( "strip_1", 0 ); + groups["strip_1"] = group; + strips[0] = dynamic_cast<Strip*>( group ); + + group = new Group ( "cursor" ); + groups["cursor"] = group; + + group = new Strip ( "strip_2", 1 ); + groups["strip_2"] = group; + strips[1] = dynamic_cast<Strip*>( group ); + + group = new Group ( "functions" ); + groups["functions"] = group; + + group = new Group ( "automation" ); + groups["automation"] = group; + + group = new Strip ( "strip_3", 2 ); + groups["strip_3"] = group; + strips[2] = dynamic_cast<Strip*>( group ); + + group = new Group ( "display" ); + groups["display"] = group; + + group = new Strip ( "strip_4", 3 ); + groups["strip_4"] = group; + strips[3] = dynamic_cast<Strip*>( group ); + + group = new Strip ( "strip_5", 4 ); + groups["strip_5"] = group; + strips[4] = dynamic_cast<Strip*>( group ); + + group = new Strip ( "strip_6", 5 ); + groups["strip_6"] = group; + strips[5] = dynamic_cast<Strip*>( group ); + + group = new Group ( "transport" ); + groups["transport"] = group; + + group = new Strip ( "strip_7", 6 ); + groups["strip_7"] = group; + strips[6] = dynamic_cast<Strip*>( group ); + + group = new Group ( "modifiers" ); + groups["modifiers"] = group; + + group = new Group ( "bank" ); + groups["bank"] = group; + + + // initialise controls + Control * control = 0; + + group = groups["strip_1"]; + control = new Fader ( 0, 1, "gain", *group ); + faders[0x00] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Fader ( 1, 2, "gain", *group ); + faders[0x01] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Fader ( 2, 3, "gain", *group ); + faders[0x02] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Fader ( 3, 4, "gain", *group ); + faders[0x03] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Fader ( 4, 5, "gain", *group ); + faders[0x04] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Fader ( 5, 6, "gain", *group ); + faders[0x05] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Fader ( 6, 7, "gain", *group ); + faders[0x06] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["master"]; + control = new Fader ( 7, 1, "gain", *group ); + faders[0x07] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_1"]; + control = new Pot ( 16, 1, "vpot", *group ); + pots[0x10] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Pot ( 17, 2, "vpot", *group ); + pots[0x11] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Pot ( 18, 3, "vpot", *group ); + pots[0x12] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Pot ( 19, 4, "vpot", *group ); + pots[0x13] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Pot ( 20, 5, "vpot", *group ); + pots[0x14] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Pot ( 21, 6, "vpot", *group ); + pots[0x15] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Pot ( 22, 7, "vpot", *group ); + pots[0x16] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["none"]; + control = new Pot ( 23, 1, "jog", *group ); + pots[0x17] = control; + controls.push_back( control ); + controls_by_name["jog"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Pot ( 46, 1, "external", *group ); + pots[0x2e] = control; + controls.push_back( control ); + controls_by_name["external"] = control; + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 24, 1, "recenable", *group ); + buttons[0x18] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 25, 2, "recenable", *group ); + buttons[0x19] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 26, 3, "recenable", *group ); + buttons[0x1a] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 27, 4, "recenable", *group ); + buttons[0x1b] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 28, 5, "recenable", *group ); + buttons[0x1c] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 29, 6, "recenable", *group ); + buttons[0x1d] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 30, 7, "recenable", *group ); + buttons[0x1e] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 32, 1, "solo", *group ); + buttons[0x20] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 33, 2, "solo", *group ); + buttons[0x21] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 34, 3, "solo", *group ); + buttons[0x22] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 35, 4, "solo", *group ); + buttons[0x23] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 36, 5, "solo", *group ); + buttons[0x24] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 37, 6, "solo", *group ); + buttons[0x25] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 38, 7, "solo", *group ); + buttons[0x26] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 16, 1, "mute", *group ); + buttons[0x10] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 17, 2, "mute", *group ); + buttons[0x11] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 18, 3, "mute", *group ); + buttons[0x12] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 19, 4, "mute", *group ); + buttons[0x13] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 20, 5, "mute", *group ); + buttons[0x14] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 21, 6, "mute", *group ); + buttons[0x15] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 22, 7, "mute", *group ); + buttons[0x16] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 0, 1, "select", *group ); + buttons[0x00] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 1, 2, "select", *group ); + buttons[0x01] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 2, 3, "select", *group ); + buttons[0x02] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 3, 4, "select", *group ); + buttons[0x03] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 4, 5, "select", *group ); + buttons[0x04] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 5, 6, "select", *group ); + buttons[0x05] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 6, 7, "select", *group ); + buttons[0x06] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 8, 1, "vselect", *group ); + buttons[0x08] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 9, 2, "vselect", *group ); + buttons[0x09] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 10, 3, "vselect", *group ); + buttons[0x0a] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 11, 4, "vselect", *group ); + buttons[0x0b] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 12, 5, "vselect", *group ); + buttons[0x0c] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 13, 6, "vselect", *group ); + buttons[0x0d] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 14, 7, "vselect", *group ); + buttons[0x0e] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 40, 1, "io", *group ); + buttons[0x28] = control; + controls.push_back( control ); + controls_by_name["io"] = control; + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 90, 1, "sends", *group ); + buttons[0x5a] = control; + controls.push_back( control ); + controls_by_name["sends"] = control; + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 89, 1, "pan", *group ); + buttons[0x59] = control; + controls.push_back( control ); + controls_by_name["pan"] = control; + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 87, 1, "plugin", *group ); + buttons[0x57] = control; + controls.push_back( control ); + controls_by_name["plugin"] = control; + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 88, 1, "eq", *group ); + buttons[0x58] = control; + controls.push_back( control ); + controls_by_name["eq"] = control; + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 45, 1, "dyn", *group ); + buttons[0x2d] = control; + controls.push_back( control ); + controls_by_name["dyn"] = control; + group->add( *control ); + + group = groups["bank"]; + control = new Button ( 46, 1, "left", *group ); + buttons[0x2e] = control; + controls.push_back( control ); + controls_by_name["left"] = control; + group->add( *control ); + + group = groups["bank"]; + control = new Button ( 47, 1, "right", *group ); + buttons[0x2f] = control; + controls.push_back( control ); + controls_by_name["right"] = control; + group->add( *control ); + + group = groups["bank"]; + control = new Button ( 48, 1, "channel_left", *group ); + buttons[0x30] = control; + controls.push_back( control ); + controls_by_name["channel_left"] = control; + group->add( *control ); + + group = groups["bank"]; + control = new Button ( 49, 1, "channel_right", *group ); + buttons[0x31] = control; + controls.push_back( control ); + controls_by_name["channel_right"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 50, 1, "flip", *group ); + buttons[0x32] = control; + controls.push_back( control ); + controls_by_name["flip"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 86, 1, "edit", *group ); + buttons[0x56] = control; + controls.push_back( control ); + controls_by_name["edit"] = control; + group->add( *control ); + + group = groups["display"]; + control = new Button ( 52, 1, "name_value", *group ); + buttons[0x34] = control; + controls.push_back( control ); + controls_by_name["name_value"] = control; + group->add( *control ); + + group = groups["display"]; + control = new Button ( 53, 1, "smpte_beats", *group ); + buttons[0x35] = control; + controls.push_back( control ); + controls_by_name["smpte_beats"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 54, 1, "F1", *group ); + buttons[0x36] = control; + controls.push_back( control ); + controls_by_name["F1"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 55, 1, "F2", *group ); + buttons[0x37] = control; + controls.push_back( control ); + controls_by_name["F2"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 56, 1, "F3", *group ); + buttons[0x38] = control; + controls.push_back( control ); + controls_by_name["F3"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 57, 1, "F4", *group ); + buttons[0x39] = control; + controls.push_back( control ); + controls_by_name["F4"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 58, 1, "F5", *group ); + buttons[0x3a] = control; + controls.push_back( control ); + controls_by_name["F5"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 59, 1, "F6", *group ); + buttons[0x3b] = control; + controls.push_back( control ); + controls_by_name["F6"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 60, 1, "F7", *group ); + buttons[0x3c] = control; + controls.push_back( control ); + controls_by_name["F7"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 61, 1, "F8", *group ); + buttons[0x3d] = control; + controls.push_back( control ); + controls_by_name["F8"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 62, 1, "F9", *group ); + buttons[0x3e] = control; + controls.push_back( control ); + controls_by_name["F9"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 63, 1, "F10", *group ); + buttons[0x3f] = control; + controls.push_back( control ); + controls_by_name["F10"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 64, 1, "F11", *group ); + buttons[0x40] = control; + controls.push_back( control ); + controls_by_name["F11"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 65, 1, "F12", *group ); + buttons[0x41] = control; + controls.push_back( control ); + controls_by_name["F12"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 66, 1, "F13", *group ); + buttons[0x42] = control; + controls.push_back( control ); + controls_by_name["F13"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 67, 1, "F14", *group ); + buttons[0x43] = control; + controls.push_back( control ); + controls_by_name["F14"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 68, 1, "F15", *group ); + buttons[0x44] = control; + controls.push_back( control ); + controls_by_name["F15"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 69, 1, "F16", *group ); + buttons[0x45] = control; + controls.push_back( control ); + controls_by_name["F16"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 39, 1, "global_solo", *group ); + buttons[0x27] = control; + controls.push_back( control ); + controls_by_name["global_solo"] = control; + group->add( *control ); + + group = groups["modifiers"]; + control = new Button ( 71, 1, "option", *group ); + buttons[0x47] = control; + controls.push_back( control ); + controls_by_name["option"] = control; + group->add( *control ); + + group = groups["modifiers"]; + control = new Button ( 72, 1, "control", *group ); + buttons[0x48] = control; + controls.push_back( control ); + controls_by_name["control"] = control; + group->add( *control ); + + group = groups["modifiers"]; + control = new Button ( 73, 1, "cmd_alt", *group ); + buttons[0x49] = control; + controls.push_back( control ); + controls_by_name["cmd_alt"] = control; + group->add( *control ); + + group = groups["automation"]; + control = new Button ( 74, 1, "on", *group ); + buttons[0x4a] = control; + controls.push_back( control ); + controls_by_name["on"] = control; + group->add( *control ); + + group = groups["automation"]; + control = new Button ( 75, 1, "rec_ready", *group ); + buttons[0x4b] = control; + controls.push_back( control ); + controls_by_name["rec_ready"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 76, 1, "undo", *group ); + buttons[0x4c] = control; + controls.push_back( control ); + controls_by_name["undo"] = control; + group->add( *control ); + + group = groups["automation"]; + control = new Button ( 77, 1, "snapshot", *group ); + buttons[0x4d] = control; + controls.push_back( control ); + controls_by_name["snapshot"] = control; + group->add( *control ); + + group = groups["automation"]; + control = new Button ( 78, 1, "touch", *group ); + buttons[0x4e] = control; + controls.push_back( control ); + controls_by_name["touch"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 79, 1, "redo", *group ); + buttons[0x4f] = control; + controls.push_back( control ); + controls_by_name["redo"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 80, 1, "marker", *group ); + buttons[0x50] = control; + controls.push_back( control ); + controls_by_name["marker"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 81, 1, "enter", *group ); + buttons[0x51] = control; + controls.push_back( control ); + controls_by_name["enter"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 82, 1, "cancel", *group ); + buttons[0x52] = control; + controls.push_back( control ); + controls_by_name["cancel"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 83, 1, "mixer", *group ); + buttons[0x53] = control; + controls.push_back( control ); + controls_by_name["mixer"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 84, 1, "frm_left", *group ); + buttons[0x54] = control; + controls.push_back( control ); + controls_by_name["frm_left"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 85, 1, "frm_right", *group ); + buttons[0x55] = control; + controls.push_back( control ); + controls_by_name["frm_right"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 70, 1, "loop", *group ); + buttons[0x46] = control; + controls.push_back( control ); + controls_by_name["loop"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 44, 1, "punch_in", *group ); + buttons[0x2c] = control; + controls.push_back( control ); + controls_by_name["punch_in"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 43, 1, "punch_out", *group ); + buttons[0x2b] = control; + controls.push_back( control ); + controls_by_name["punch_out"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 42, 1, "home", *group ); + buttons[0x2a] = control; + controls.push_back( control ); + controls_by_name["home"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 41, 1, "end", *group ); + buttons[0x29] = control; + controls.push_back( control ); + controls_by_name["end"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 91, 1, "rewind", *group ); + buttons[0x5b] = control; + controls.push_back( control ); + controls_by_name["rewind"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 92, 1, "ffwd", *group ); + buttons[0x5c] = control; + controls.push_back( control ); + controls_by_name["ffwd"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 93, 1, "stop", *group ); + buttons[0x5d] = control; + controls.push_back( control ); + controls_by_name["stop"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 94, 1, "play", *group ); + buttons[0x5e] = control; + controls.push_back( control ); + controls_by_name["play"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 31, 1, "record", *group ); + buttons[0x1f] = control; + controls.push_back( control ); + controls_by_name["record"] = control; + group->add( *control ); + + group = groups["cursor"]; + control = new Button ( 96, 1, "cursor_up", *group ); + buttons[0x60] = control; + controls.push_back( control ); + controls_by_name["cursor_up"] = control; + group->add( *control ); + + group = groups["cursor"]; + control = new Button ( 97, 1, "cursor_down", *group ); + buttons[0x61] = control; + controls.push_back( control ); + controls_by_name["cursor_down"] = control; + group->add( *control ); + + group = groups["cursor"]; + control = new Button ( 98, 1, "cursor_left", *group ); + buttons[0x62] = control; + controls.push_back( control ); + controls_by_name["cursor_left"] = control; + group->add( *control ); + + group = groups["cursor"]; + control = new Button ( 99, 1, "cursor_right", *group ); + buttons[0x63] = control; + controls.push_back( control ); + controls_by_name["cursor_right"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 100, 1, "zoom", *group ); + buttons[0x64] = control; + controls.push_back( control ); + controls_by_name["zoom"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 101, 1, "scrub", *group ); + buttons[0x65] = control; + controls.push_back( control ); + controls_by_name["scrub"] = control; + group->add( *control ); + + group = groups["user"]; + control = new Button ( 102, 1, "user_a", *group ); + buttons[0x66] = control; + controls.push_back( control ); + controls_by_name["user_a"] = control; + group->add( *control ); + + group = groups["user"]; + control = new Button ( 103, 1, "user_b", *group ); + buttons[0x67] = control; + controls.push_back( control ); + controls_by_name["user_b"] = control; + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 104, 1, "fader_touch", *group ); + buttons[0x68] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 105, 2, "fader_touch", *group ); + buttons[0x69] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 106, 3, "fader_touch", *group ); + buttons[0x6a] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 107, 4, "fader_touch", *group ); + buttons[0x6b] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 108, 5, "fader_touch", *group ); + buttons[0x6c] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 109, 6, "fader_touch", *group ); + buttons[0x6d] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 110, 7, "fader_touch", *group ); + buttons[0x6e] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["master"]; + control = new Button ( 111, 1, "fader_touch", *group ); + buttons[0x6f] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["master"]; + control = new Button ( 23, 1, "mute", *group ); + buttons[0x17] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["none"]; + control = new Button ( 51, 1, "clicking", *group ); + buttons[0x33] = control; + controls.push_back( control ); + controls_by_name["clicking"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Led ( 113, 1, "smpte", *group ); + leds[0x71] = control; + controls.push_back( control ); + controls_by_name["smpte"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Led ( 114, 1, "beats", *group ); + leds[0x72] = control; + controls.push_back( control ); + controls_by_name["beats"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Led ( 115, 1, "solo", *group ); + leds[0x73] = control; + controls.push_back( control ); + controls_by_name["solo"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Led ( 118, 1, "relay_click", *group ); + leds[0x76] = control; + controls.push_back( control ); + controls_by_name["relay_click"] = control; + group->add( *control ); + +} + +void Mackie::BcfSurface::handle_button( MackieButtonHandler & mbh, ButtonState bs, Button & button ) +{ + if ( bs != press && bs != release ) + { + mbh.update_led( button, none ); + return; + } + + LedState ls; + switch ( button.id() ) + { + + case 0x28: // io + switch ( bs ) { + case press: ls = mbh.io_press( button ); break; + case release: ls = mbh.io_release( button ); break; + case neither: break; + } + break; + + case 0x5a: // sends + switch ( bs ) { + case press: ls = mbh.sends_press( button ); break; + case release: ls = mbh.sends_release( button ); break; + case neither: break; + } + break; + + case 0x59: // pan + switch ( bs ) { + case press: ls = mbh.pan_press( button ); break; + case release: ls = mbh.pan_release( button ); break; + case neither: break; + } + break; + + case 0x57: // plugin + switch ( bs ) { + case press: ls = mbh.plugin_press( button ); break; + case release: ls = mbh.plugin_release( button ); break; + case neither: break; + } + break; + + case 0x58: // eq + switch ( bs ) { + case press: ls = mbh.eq_press( button ); break; + case release: ls = mbh.eq_release( button ); break; + case neither: break; + } + break; + + case 0x2d: // dyn + switch ( bs ) { + case press: ls = mbh.dyn_press( button ); break; + case release: ls = mbh.dyn_release( button ); break; + case neither: break; + } + break; + + case 0x2e: // left + switch ( bs ) { + case press: ls = mbh.left_press( button ); break; + case release: ls = mbh.left_release( button ); break; + case neither: break; + } + break; + + case 0x2f: // right + switch ( bs ) { + case press: ls = mbh.right_press( button ); break; + case release: ls = mbh.right_release( button ); break; + case neither: break; + } + break; + + case 0x30: // channel_left + switch ( bs ) { + case press: ls = mbh.channel_left_press( button ); break; + case release: ls = mbh.channel_left_release( button ); break; + case neither: break; + } + break; + + case 0x31: // channel_right + switch ( bs ) { + case press: ls = mbh.channel_right_press( button ); break; + case release: ls = mbh.channel_right_release( button ); break; + case neither: break; + } + break; + + case 0x32: // flip + switch ( bs ) { + case press: ls = mbh.flip_press( button ); break; + case release: ls = mbh.flip_release( button ); break; + case neither: break; + } + break; + + case 0x56: // edit + switch ( bs ) { + case press: ls = mbh.edit_press( button ); break; + case release: ls = mbh.edit_release( button ); break; + case neither: break; + } + break; + + case 0x34: // name_value + switch ( bs ) { + case press: ls = mbh.name_value_press( button ); break; + case release: ls = mbh.name_value_release( button ); break; + case neither: break; + } + break; + + case 0x35: // smpte_beats + switch ( bs ) { + case press: ls = mbh.smpte_beats_press( button ); break; + case release: ls = mbh.smpte_beats_release( button ); break; + case neither: break; + } + break; + + case 0x36: // F1 + switch ( bs ) { + case press: ls = mbh.F1_press( button ); break; + case release: ls = mbh.F1_release( button ); break; + case neither: break; + } + break; + + case 0x37: // F2 + switch ( bs ) { + case press: ls = mbh.F2_press( button ); break; + case release: ls = mbh.F2_release( button ); break; + case neither: break; + } + break; + + case 0x38: // F3 + switch ( bs ) { + case press: ls = mbh.F3_press( button ); break; + case release: ls = mbh.F3_release( button ); break; + case neither: break; + } + break; + + case 0x39: // F4 + switch ( bs ) { + case press: ls = mbh.F4_press( button ); break; + case release: ls = mbh.F4_release( button ); break; + case neither: break; + } + break; + + case 0x3a: // F5 + switch ( bs ) { + case press: ls = mbh.F5_press( button ); break; + case release: ls = mbh.F5_release( button ); break; + case neither: break; + } + break; + + case 0x3b: // F6 + switch ( bs ) { + case press: ls = mbh.F6_press( button ); break; + case release: ls = mbh.F6_release( button ); break; + case neither: break; + } + break; + + case 0x3c: // F7 + switch ( bs ) { + case press: ls = mbh.F7_press( button ); break; + case release: ls = mbh.F7_release( button ); break; + case neither: break; + } + break; + + case 0x3d: // F8 + switch ( bs ) { + case press: ls = mbh.F8_press( button ); break; + case release: ls = mbh.F8_release( button ); break; + case neither: break; + } + break; + + case 0x3e: // F9 + switch ( bs ) { + case press: ls = mbh.F9_press( button ); break; + case release: ls = mbh.F9_release( button ); break; + case neither: break; + } + break; + + case 0x3f: // F10 + switch ( bs ) { + case press: ls = mbh.F10_press( button ); break; + case release: ls = mbh.F10_release( button ); break; + case neither: break; + } + break; + + case 0x40: // F11 + switch ( bs ) { + case press: ls = mbh.F11_press( button ); break; + case release: ls = mbh.F11_release( button ); break; + case neither: break; + } + break; + + case 0x41: // F12 + switch ( bs ) { + case press: ls = mbh.F12_press( button ); break; + case release: ls = mbh.F12_release( button ); break; + case neither: break; + } + break; + + case 0x42: // F13 + switch ( bs ) { + case press: ls = mbh.F13_press( button ); break; + case release: ls = mbh.F13_release( button ); break; + case neither: break; + } + break; + + case 0x43: // F14 + switch ( bs ) { + case press: ls = mbh.F14_press( button ); break; + case release: ls = mbh.F14_release( button ); break; + case neither: break; + } + break; + + case 0x44: // F15 + switch ( bs ) { + case press: ls = mbh.F15_press( button ); break; + case release: ls = mbh.F15_release( button ); break; + case neither: break; + } + break; + + case 0x45: // F16 + switch ( bs ) { + case press: ls = mbh.F16_press( button ); break; + case release: ls = mbh.F16_release( button ); break; + case neither: break; + } + break; + + case 0x27: // global_solo + switch ( bs ) { + case press: ls = mbh.global_solo_press( button ); break; + case release: ls = mbh.global_solo_release( button ); break; + case neither: break; + } + break; + + case 0x47: // option + switch ( bs ) { + case press: ls = mbh.option_press( button ); break; + case release: ls = mbh.option_release( button ); break; + case neither: break; + } + break; + + case 0x48: // control + switch ( bs ) { + case press: ls = mbh.control_press( button ); break; + case release: ls = mbh.control_release( button ); break; + case neither: break; + } + break; + + case 0x49: // cmd_alt + switch ( bs ) { + case press: ls = mbh.cmd_alt_press( button ); break; + case release: ls = mbh.cmd_alt_release( button ); break; + case neither: break; + } + break; + + case 0x4a: // on + switch ( bs ) { + case press: ls = mbh.on_press( button ); break; + case release: ls = mbh.on_release( button ); break; + case neither: break; + } + break; + + case 0x4b: // rec_ready + switch ( bs ) { + case press: ls = mbh.rec_ready_press( button ); break; + case release: ls = mbh.rec_ready_release( button ); break; + case neither: break; + } + break; + + case 0x4c: // undo + switch ( bs ) { + case press: ls = mbh.undo_press( button ); break; + case release: ls = mbh.undo_release( button ); break; + case neither: break; + } + break; + + case 0x4d: // snapshot + switch ( bs ) { + case press: ls = mbh.snapshot_press( button ); break; + case release: ls = mbh.snapshot_release( button ); break; + case neither: break; + } + break; + + case 0x4e: // touch + switch ( bs ) { + case press: ls = mbh.touch_press( button ); break; + case release: ls = mbh.touch_release( button ); break; + case neither: break; + } + break; + + case 0x4f: // redo + switch ( bs ) { + case press: ls = mbh.redo_press( button ); break; + case release: ls = mbh.redo_release( button ); break; + case neither: break; + } + break; + + case 0x50: // marker + switch ( bs ) { + case press: ls = mbh.marker_press( button ); break; + case release: ls = mbh.marker_release( button ); break; + case neither: break; + } + break; + + case 0x51: // enter + switch ( bs ) { + case press: ls = mbh.enter_press( button ); break; + case release: ls = mbh.enter_release( button ); break; + case neither: break; + } + break; + + case 0x52: // cancel + switch ( bs ) { + case press: ls = mbh.cancel_press( button ); break; + case release: ls = mbh.cancel_release( button ); break; + case neither: break; + } + break; + + case 0x53: // mixer + switch ( bs ) { + case press: ls = mbh.mixer_press( button ); break; + case release: ls = mbh.mixer_release( button ); break; + case neither: break; + } + break; + + case 0x54: // frm_left + switch ( bs ) { + case press: ls = mbh.frm_left_press( button ); break; + case release: ls = mbh.frm_left_release( button ); break; + case neither: break; + } + break; + + case 0x55: // frm_right + switch ( bs ) { + case press: ls = mbh.frm_right_press( button ); break; + case release: ls = mbh.frm_right_release( button ); break; + case neither: break; + } + break; + + case 0x46: // loop + switch ( bs ) { + case press: ls = mbh.loop_press( button ); break; + case release: ls = mbh.loop_release( button ); break; + case neither: break; + } + break; + + case 0x2c: // punch_in + switch ( bs ) { + case press: ls = mbh.punch_in_press( button ); break; + case release: ls = mbh.punch_in_release( button ); break; + case neither: break; + } + break; + + case 0x2b: // punch_out + switch ( bs ) { + case press: ls = mbh.punch_out_press( button ); break; + case release: ls = mbh.punch_out_release( button ); break; + case neither: break; + } + break; + + case 0x2a: // home + switch ( bs ) { + case press: ls = mbh.home_press( button ); break; + case release: ls = mbh.home_release( button ); break; + case neither: break; + } + break; + + case 0x29: // end + switch ( bs ) { + case press: ls = mbh.end_press( button ); break; + case release: ls = mbh.end_release( button ); break; + case neither: break; + } + break; + + case 0x5b: // rewind + switch ( bs ) { + case press: ls = mbh.rewind_press( button ); break; + case release: ls = mbh.rewind_release( button ); break; + case neither: break; + } + break; + + case 0x5c: // ffwd + switch ( bs ) { + case press: ls = mbh.ffwd_press( button ); break; + case release: ls = mbh.ffwd_release( button ); break; + case neither: break; + } + break; + + case 0x5d: // stop + switch ( bs ) { + case press: ls = mbh.stop_press( button ); break; + case release: ls = mbh.stop_release( button ); break; + case neither: break; + } + break; + + case 0x5e: // play + switch ( bs ) { + case press: ls = mbh.play_press( button ); break; + case release: ls = mbh.play_release( button ); break; + case neither: break; + } + break; + + case 0x1f: // record + switch ( bs ) { + case press: ls = mbh.record_press( button ); break; + case release: ls = mbh.record_release( button ); break; + case neither: break; + } + break; + + case 0x60: // cursor_up + switch ( bs ) { + case press: ls = mbh.cursor_up_press( button ); break; + case release: ls = mbh.cursor_up_release( button ); break; + case neither: break; + } + break; + + case 0x61: // cursor_down + switch ( bs ) { + case press: ls = mbh.cursor_down_press( button ); break; + case release: ls = mbh.cursor_down_release( button ); break; + case neither: break; + } + break; + + case 0x62: // cursor_left + switch ( bs ) { + case press: ls = mbh.cursor_left_press( button ); break; + case release: ls = mbh.cursor_left_release( button ); break; + case neither: break; + } + break; + + case 0x63: // cursor_right + switch ( bs ) { + case press: ls = mbh.cursor_right_press( button ); break; + case release: ls = mbh.cursor_right_release( button ); break; + case neither: break; + } + break; + + case 0x64: // zoom + switch ( bs ) { + case press: ls = mbh.zoom_press( button ); break; + case release: ls = mbh.zoom_release( button ); break; + case neither: break; + } + break; + + case 0x65: // scrub + switch ( bs ) { + case press: ls = mbh.scrub_press( button ); break; + case release: ls = mbh.scrub_release( button ); break; + case neither: break; + } + break; + + case 0x66: // user_a + switch ( bs ) { + case press: ls = mbh.user_a_press( button ); break; + case release: ls = mbh.user_a_release( button ); break; + case neither: break; + } + break; + + case 0x67: // user_b + switch ( bs ) { + case press: ls = mbh.user_b_press( button ); break; + case release: ls = mbh.user_b_release( button ); break; + case neither: break; + } + break; + + case 0x33: // clicking + switch ( bs ) { + case press: ls = mbh.clicking_press( button ); break; + case release: ls = mbh.clicking_release( button ); break; + case neither: break; + } + break; + + } + mbh.update_led( button, ls ); +} diff --git a/libs/surfaces/mackie/bcf_surface.h b/libs/surfaces/mackie/bcf_surface.h new file mode 100644 index 0000000000..a5fd3bf5a3 --- /dev/null +++ b/libs/surfaces/mackie/bcf_surface.h @@ -0,0 +1,27 @@ +#ifndef mackie_surface_bcf_h +#define mackie_surface_bcf_h +/* + Generated by scripts/generate-surface.rb +*/ + +#include "surface.h" + +namespace Mackie +{ + +class MackieButtonHandler; + +class BcfSurface : public Surface +{ +public: + BcfSurface( uint32_t max_strips ) : Surface( max_strips ) + { + } + + virtual void handle_button( MackieButtonHandler & mbh, ButtonState bs, Button & button ); + virtual void init_controls(); +}; + +} + +#endif diff --git a/libs/surfaces/mackie/controls.cc b/libs/surfaces/mackie/controls.cc new file mode 100644 index 0000000000..e9808119b2 --- /dev/null +++ b/libs/surfaces/mackie/controls.cc @@ -0,0 +1,109 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "controls.h" +#include "types.h" +#include "mackie_midi_builder.h" + +#include <iostream> +#include <iomanip> +#include <sstream> + +using namespace Mackie; +using namespace std; + +void Group::add( Control & control ) +{ + _controls.push_back( &control ); +} + +Strip::Strip( const std::string & name, int index ) + : Group( name ) + , _solo( 0 ) + , _recenable( 0 ) + , _mute( 0 ) + , _select( 0 ) + , _vselect( 0 ) + , _fader_touch( 0 ) + , _vpot( 0 ) + , _gain( 0 ) + , _index( index ) +{ +} + +/** + generated with + +controls[1].each do |x| + puts <<EOF +#{x.class.name} & Strip::#{x.name}() +{ + if ( _#{x.name} == 0 ) + throw MackieControlException( "#{x.name} is null" ); + return *_#{x.name}; +} +EOF +end +*/ +Fader & Strip::gain() +{ + if ( _gain == 0 ) + throw MackieControlException( "gain is null" ); + return *_gain; +} +Pot & Strip::vpot() +{ + if ( _vpot == 0 ) + throw MackieControlException( "vpot is null" ); + return *_vpot; +} +Button & Strip::recenable() +{ + if ( _recenable == 0 ) + throw MackieControlException( "recenable is null" ); + return *_recenable; +} +Button & Strip::solo() +{ + if ( _solo == 0 ) + throw MackieControlException( "solo is null" ); + return *_solo; +} +Button & Strip::mute() +{ + if ( _mute == 0 ) + throw MackieControlException( "mute is null" ); + return *_mute; +} +Button & Strip::select() +{ + if ( _select == 0 ) + throw MackieControlException( "select is null" ); + return *_select; +} +Button & Strip::vselect() +{ + if ( _vselect == 0 ) + throw MackieControlException( "vselect is null" ); + return *_vselect; +} +Button & Strip::fader_touch() +{ + if ( _fader_touch == 0 ) + throw MackieControlException( "fader_touch is null" ); + return *_fader_touch; +} diff --git a/libs/surfaces/mackie/controls.h b/libs/surfaces/mackie/controls.h new file mode 100644 index 0000000000..1092c40453 --- /dev/null +++ b/libs/surfaces/mackie/controls.h @@ -0,0 +1,301 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#ifndef mackie_controls_h +#define mackie_controls_h + +#include <map> +#include <vector> +#include <string> + +#include "mackie_control_exception.h" + +namespace Mackie +{ + +class Control; + +/** + This is a loose group of controls, eg cursor buttons, + transport buttons, functions buttons etc. +*/ +class Group +{ +public: + Group( const std::string & name ) + : _name( name ) + { + } + + virtual ~Group() {} + + virtual bool is_strip() const + { + return false; + } + + virtual bool is_master() const + { + return false; + } + + virtual void add( Control & control ); + + const std::string & name() const + { + return _name; + } + + // This is for Surface only + void name( const std::string & rhs ) { _name = rhs; } + + typedef std::vector<Control*> Controls; + const Controls & controls() const { return _controls; } + +protected: + Controls _controls; + +private: + std::string _name; +}; + +class Button; +class Pot; +class Fader; + +/** + This is the set of controls that make up a strip. +*/ +class Strip : public Group +{ +public: + Strip( const std::string & name, int index ); + + virtual bool is_strip() const + { + return true; + } + + virtual void add( Control & control ); + + /// This is the index of the strip + int index() const { return _index; } + + /// This is for Surface only + void index( int rhs ) { _index = rhs; } + + Button & solo(); + Button & recenable(); + Button & mute(); + Button & select(); + Button & vselect(); + Button & fader_touch(); + Pot & vpot(); + Fader & gain(); + + bool has_solo() { return _solo != 0; } + bool has_recenable() { return _recenable != 0; } + bool has_mute() { return _mute != 0; } + bool has_select() { return _select != 0; } + bool has_vselect() { return _vselect != 0; } + bool has_fader_touch() { return _fader_touch != 0; } + bool has_vpot() { return _vpot != 0; } + bool has_gain() { return _gain != 0; } + +private: + Button * _solo; + Button * _recenable; + Button * _mute; + Button * _select; + Button * _vselect; + Button * _fader_touch; + Pot * _vpot; + Fader * _gain; + int _index; +}; + +class MasterStrip : public Strip +{ +public: + MasterStrip( const std::string & name, int index ) + : Strip( name, index ) + { + } + + virtual bool is_master() const + { + return true; + } +}; + +class Led; + +/** + The base class for controls on the surface. They deliberately + don't know the midi protocol for updating them. +*/ +class Control +{ +public: + enum type_t { type_fader, type_button, type_pot, type_led, type_led_ring }; + + Control( int id, int ordinal, std::string name, Group & group ) + : _id( id ), _ordinal( ordinal ), _name( name ), _group( group ) + { + } + + virtual ~Control() {} + + virtual const Led & led() const + { + throw MackieControlException( "no led available" ); + } + + /// The midi id of the control + int id() const + { + return _id; + } + + /// The 1-based number of the control + int ordinal() const + { + return _ordinal; + } + + const std::string & name() const + { + return _name; + } + + const Group & group() const + { + return _group; + } + + const Strip & strip() const + { + return dynamic_cast<const Strip&>( _group ); + } + + Strip & strip() + { + return dynamic_cast<Strip&>( _group ); + } + + virtual bool accepts_feedback() const + { + return true; + } + + virtual type_t type() const = 0; + +private: + int _id; + int _ordinal; + std::string _name; + Group & _group; +}; + +std::ostream & operator << ( std::ostream & os, const Control & control ); + +class Fader : public Control +{ +public: + Fader( int id, int ordinal, std::string name, Group & group ) + : Control( id, ordinal, name, group ) + , _touch( false ) + { + } + + bool touch() const { return _touch; } + + void touch( bool yn ) { _touch = yn; } + + virtual type_t type() const { return type_fader; } + +private: + bool _touch; +}; + +class Led : public Control +{ +public: + Led( int id, int ordinal, std::string name, Group & group ) + : Control( id, ordinal, name, group ) + { + } + + virtual const Led & led() const { return *this; } + + virtual type_t type() const { return type_led; } +}; + +class Button : public Control +{ +public: + Button( int id, int ordinal, std::string name, Group & group ) + : Control( id, ordinal, name, group ) + , _led( id, ordinal, name + "_led", group ) + { + } + + virtual const Led & led() const + { + return _led; + } + + virtual type_t type() const { return type_button; }; + +private: + Led _led; +}; + +class LedRing : public Led +{ +public: + LedRing( int id, int ordinal, std::string name, Group & group ) + : Led( id, ordinal, name, group ) + { + } + + virtual type_t type() const { return type_led_ring; } +}; + +class Pot : public Control +{ +public: + Pot( int id, int ordinal, std::string name, Group & group ) + : Control( id, ordinal, name, group ) + , _led_ring( id, ordinal, name + "_ring", group ) + { + } + + virtual type_t type() const { return type_pot; } + + virtual const LedRing & led_ring() const + { + return _led_ring; + } + +private: + LedRing _led_ring; +}; + +} + +#endif diff --git a/libs/surfaces/mackie/interface.cc b/libs/surfaces/mackie/interface.cc new file mode 100644 index 0000000000..eda485b5d6 --- /dev/null +++ b/libs/surfaces/mackie/interface.cc @@ -0,0 +1,96 @@ +/* + Copyright (C) 2006,2007 Paul Davis + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include <control_protocol/control_protocol.h> +#include "mackie_control_protocol.h" + +#include <pbd/error.h> + +#include <stdexcept> + +using namespace ARDOUR; +using namespace PBD; +using namespace std; + +ControlProtocol* +new_mackie_protocol (ControlProtocolDescriptor* descriptor, Session* s) +{ + if ( Config->get_mmc_port_name().substr(0,3) == "mcu" ) + { + error << "mcu already used as mmc port" << endmsg; + } + else if ( Config->get_mtc_port_name().substr(0,3) == "mcu" ) + { + error << "mcu already used as mtc port" << endmsg; + } + else if ( Config->get_midi_port_name().substr(0,3) == "mcu" ) + { + error << "mcu already used as midi port" << endmsg; + } + else + { + // no one else is using the port, so try instantiate the object + MackieControlProtocol * mcp = 0; + try + { + mcp = new MackieControlProtocol (*s); + mcp->set_active( true ); + } + catch( exception & e ) + { + error << "Error instantiating MackieControlProtocol: " << e.what() << endmsg; + delete mcp; + mcp = 0; + } + return mcp; + } + return 0; +} + +void +delete_mackie_protocol (ControlProtocolDescriptor* descriptor, ControlProtocol* cp) +{ + delete cp; +} + +bool +probe_mackie_protocol (ControlProtocolDescriptor* descriptor) +{ + return MackieControlProtocol::probe(); +} + +static ControlProtocolDescriptor mackie_descriptor = { + name : "Mackie", + id : "uri://ardour.org/surfaces/mackie:0", + ptr : 0, + module : 0, + mandatory : 0, + supports_feedback : true, + probe : probe_mackie_protocol, + initialize : new_mackie_protocol, + destroy : delete_mackie_protocol +}; + + +extern "C" { + +ControlProtocolDescriptor* +protocol_descriptor () { + return &mackie_descriptor; +} + +} diff --git a/libs/surfaces/mackie/mackie_button_handler.cc b/libs/surfaces/mackie/mackie_button_handler.cc new file mode 100644 index 0000000000..f7ac2ab6d5 --- /dev/null +++ b/libs/surfaces/mackie/mackie_button_handler.cc @@ -0,0 +1,691 @@ +/* + Generated by scripts/generate-button-handlers.erb +*/ +#include "mackie_button_handler.h" +#include "controls.h" + +#include <iostream> + +using namespace std; +using namespace Mackie; + +LedState MackieButtonHandler::default_button_press( Button & button ) +{ + cout << "press: " << button << endl; + return on; +} +LedState MackieButtonHandler::default_button_release( Button & button ) +{ + cout << "release: " << button << endl; + return off; +} + +LedState MackieButtonHandler::io_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::io_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::sends_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::sends_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::pan_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::pan_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::plugin_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::plugin_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::eq_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::eq_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::dyn_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::dyn_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::left_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::left_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::right_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::right_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::channel_left_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::channel_left_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::channel_right_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::channel_right_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::flip_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::flip_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::edit_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::edit_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::name_value_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::name_value_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::smpte_beats_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::smpte_beats_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F1_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F1_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F2_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F2_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F3_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F3_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F4_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F4_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F5_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F5_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F6_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F6_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F7_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F7_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F8_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F8_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F9_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F9_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F10_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F10_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F11_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F11_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F12_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F12_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F13_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F13_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F14_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F14_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F15_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F15_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::F16_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::F16_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::shift_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::shift_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::option_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::option_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::control_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::control_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::cmd_alt_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::cmd_alt_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::on_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::on_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::rec_ready_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::rec_ready_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::undo_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::undo_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::snapshot_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::snapshot_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::touch_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::touch_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::redo_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::redo_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::marker_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::marker_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::enter_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::enter_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::cancel_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::cancel_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::mixer_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::mixer_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::frm_left_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::frm_left_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::frm_right_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::frm_right_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::loop_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::loop_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::punch_in_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::punch_in_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::punch_out_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::punch_out_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::home_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::home_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::end_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::end_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::rewind_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::rewind_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::ffwd_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::ffwd_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::stop_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::stop_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::play_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::play_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::record_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::record_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::cursor_up_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::cursor_up_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::cursor_down_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::cursor_down_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::cursor_left_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::cursor_left_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::cursor_right_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::cursor_right_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::zoom_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::zoom_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::scrub_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::scrub_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::user_a_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::user_a_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::user_b_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::user_b_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::fader_touch_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::fader_touch_release( Button & button ) +{ + return default_button_release( button ); +} + +LedState MackieButtonHandler::clicking_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::clicking_release( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::global_solo_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::global_solo_release( Button & button ) +{ + return default_button_press( button ); +} diff --git a/libs/surfaces/mackie/mackie_button_handler.h b/libs/surfaces/mackie/mackie_button_handler.h new file mode 100644 index 0000000000..ee4187c7ce --- /dev/null +++ b/libs/surfaces/mackie/mackie_button_handler.h @@ -0,0 +1,227 @@ +#ifndef mackie_button_handler_h +#define mackie_button_handler_h +/* + Generated by scripts/generate-button-handlers.erb +*/ + +#include "types.h" + +namespace Mackie +{ + +class MackieButtonHandler +{ +public: + virtual ~MackieButtonHandler() {} + + virtual LedState default_button_press( Button & button ); + virtual LedState default_button_release( Button & button ); + + virtual void update_led( Button & button, LedState ls ) = 0; + + + virtual LedState io_press( Button & ); + virtual LedState io_release( Button & ); + + virtual LedState sends_press( Button & ); + virtual LedState sends_release( Button & ); + + virtual LedState pan_press( Button & ); + virtual LedState pan_release( Button & ); + + virtual LedState plugin_press( Button & ); + virtual LedState plugin_release( Button & ); + + virtual LedState eq_press( Button & ); + virtual LedState eq_release( Button & ); + + virtual LedState dyn_press( Button & ); + virtual LedState dyn_release( Button & ); + + virtual LedState left_press( Button & ); + virtual LedState left_release( Button & ); + + virtual LedState right_press( Button & ); + virtual LedState right_release( Button & ); + + virtual LedState channel_left_press( Button & ); + virtual LedState channel_left_release( Button & ); + + virtual LedState channel_right_press( Button & ); + virtual LedState channel_right_release( Button & ); + + virtual LedState flip_press( Button & ); + virtual LedState flip_release( Button & ); + + virtual LedState edit_press( Button & ); + virtual LedState edit_release( Button & ); + + virtual LedState name_value_press( Button & ); + virtual LedState name_value_release( Button & ); + + virtual LedState smpte_beats_press( Button & ); + virtual LedState smpte_beats_release( Button & ); + + virtual LedState F1_press( Button & ); + virtual LedState F1_release( Button & ); + + virtual LedState F2_press( Button & ); + virtual LedState F2_release( Button & ); + + virtual LedState F3_press( Button & ); + virtual LedState F3_release( Button & ); + + virtual LedState F4_press( Button & ); + virtual LedState F4_release( Button & ); + + virtual LedState F5_press( Button & ); + virtual LedState F5_release( Button & ); + + virtual LedState F6_press( Button & ); + virtual LedState F6_release( Button & ); + + virtual LedState F7_press( Button & ); + virtual LedState F7_release( Button & ); + + virtual LedState F8_press( Button & ); + virtual LedState F8_release( Button & ); + + virtual LedState F9_press( Button & ); + virtual LedState F9_release( Button & ); + + virtual LedState F10_press( Button & ); + virtual LedState F10_release( Button & ); + + virtual LedState F11_press( Button & ); + virtual LedState F11_release( Button & ); + + virtual LedState F12_press( Button & ); + virtual LedState F12_release( Button & ); + + virtual LedState F13_press( Button & ); + virtual LedState F13_release( Button & ); + + virtual LedState F14_press( Button & ); + virtual LedState F14_release( Button & ); + + virtual LedState F15_press( Button & ); + virtual LedState F15_release( Button & ); + + virtual LedState F16_press( Button & ); + virtual LedState F16_release( Button & ); + + virtual LedState shift_press( Button & ); + virtual LedState shift_release( Button & ); + + virtual LedState option_press( Button & ); + virtual LedState option_release( Button & ); + + virtual LedState control_press( Button & ); + virtual LedState control_release( Button & ); + + virtual LedState cmd_alt_press( Button & ); + virtual LedState cmd_alt_release( Button & ); + + virtual LedState on_press( Button & ); + virtual LedState on_release( Button & ); + + virtual LedState rec_ready_press( Button & ); + virtual LedState rec_ready_release( Button & ); + + virtual LedState undo_press( Button & ); + virtual LedState undo_release( Button & ); + + virtual LedState snapshot_press( Button & ); + virtual LedState snapshot_release( Button & ); + + virtual LedState touch_press( Button & ); + virtual LedState touch_release( Button & ); + + virtual LedState redo_press( Button & ); + virtual LedState redo_release( Button & ); + + virtual LedState marker_press( Button & ); + virtual LedState marker_release( Button & ); + + virtual LedState enter_press( Button & ); + virtual LedState enter_release( Button & ); + + virtual LedState cancel_press( Button & ); + virtual LedState cancel_release( Button & ); + + virtual LedState mixer_press( Button & ); + virtual LedState mixer_release( Button & ); + + virtual LedState frm_left_press( Button & ); + virtual LedState frm_left_release( Button & ); + + virtual LedState frm_right_press( Button & ); + virtual LedState frm_right_release( Button & ); + + virtual LedState loop_press( Button & ); + virtual LedState loop_release( Button & ); + + virtual LedState punch_in_press( Button & ); + virtual LedState punch_in_release( Button & ); + + virtual LedState punch_out_press( Button & ); + virtual LedState punch_out_release( Button & ); + + virtual LedState home_press( Button & ); + virtual LedState home_release( Button & ); + + virtual LedState end_press( Button & ); + virtual LedState end_release( Button & ); + + virtual LedState rewind_press( Button & ); + virtual LedState rewind_release( Button & ); + + virtual LedState ffwd_press( Button & ); + virtual LedState ffwd_release( Button & ); + + virtual LedState stop_press( Button & ); + virtual LedState stop_release( Button & ); + + virtual LedState play_press( Button & ); + virtual LedState play_release( Button & ); + + virtual LedState record_press( Button & ); + virtual LedState record_release( Button & ); + + virtual LedState cursor_up_press( Button & ); + virtual LedState cursor_up_release( Button & ); + + virtual LedState cursor_down_press( Button & ); + virtual LedState cursor_down_release( Button & ); + + virtual LedState cursor_left_press( Button & ); + virtual LedState cursor_left_release( Button & ); + + virtual LedState cursor_right_press( Button & ); + virtual LedState cursor_right_release( Button & ); + + virtual LedState zoom_press( Button & ); + virtual LedState zoom_release( Button & ); + + virtual LedState scrub_press( Button & ); + virtual LedState scrub_release( Button & ); + + virtual LedState user_a_press( Button & ); + virtual LedState user_a_release( Button & ); + + virtual LedState user_b_press( Button & ); + virtual LedState user_b_release( Button & ); + + virtual LedState fader_touch_press( Button & ); + virtual LedState fader_touch_release( Button & ); + + virtual LedState clicking_press( Button & ); + virtual LedState clicking_release( Button & ); + + virtual LedState global_solo_press( Button & ); + virtual LedState global_solo_release( Button & ); +}; + +} + +#endif diff --git a/libs/surfaces/mackie/mackie_control_exception.h b/libs/surfaces/mackie/mackie_control_exception.h new file mode 100644 index 0000000000..afe7016948 --- /dev/null +++ b/libs/surfaces/mackie/mackie_control_exception.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#ifndef mackie_control_exception_h +#define mackie_control_exception_h + +#include <stdexcept> + +namespace Mackie +{ + +class MackieControlException : public std::exception +{ +public: + MackieControlException( const std::string & msg ) + : _msg( msg ) + { + } + + virtual ~MackieControlException() throw () {} + + const char * what() const throw () + { + return _msg.c_str(); + } + +private: + std::string _msg; +}; + +} + +#endif diff --git a/libs/surfaces/mackie/mackie_control_protocol.cc b/libs/surfaces/mackie/mackie_control_protocol.cc new file mode 100644 index 0000000000..81d249588e --- /dev/null +++ b/libs/surfaces/mackie/mackie_control_protocol.cc @@ -0,0 +1,1378 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include <iostream> +#include <algorithm> +#include <cmath> +#include <sstream> +#include <vector> + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> +#include <float.h> +#include <sys/time.h> +#include <errno.h> +#include <poll.h> + +#include <boost/shared_array.hpp> + +#include <midi++/types.h> +#include <midi++/port.h> +#include <midi++/manager.h> +#include <pbd/pthread_utils.h> +#include <pbd/error.h> + +#include <ardour/route.h> +#include <ardour/session.h> +#include <ardour/location.h> +#include <ardour/dB.h> +#include <ardour/panner.h> + +#include "mackie_control_protocol.h" + +#include "midi_byte_array.h" +#include "mackie_control_exception.h" +#include "route_signal.h" +#include "mackie_midi_builder.h" +#include "surface_port.h" +#include "surface.h" +#include "bcf_surface.h" +#include "mackie_surface.h" + +using namespace ARDOUR; +using namespace std; +using namespace sigc; +using namespace Mackie; +using namespace PBD; + +using boost::shared_ptr; + +#include "i18n.h" + +MackieMidiBuilder builder; + +// Copied from tranzport_control_protocol.cc +static inline double +gain_to_slider_position (ARDOUR::gain_t g) +{ + if (g == 0) return 0; + return pow((6.0*log(g)/log(2.0)+192.0)/198.0, 8.0); +} + +/* + Copied from tranzport_control_protocol.cc + TODO this seems to return slightly wrong values, namely + with the UI slider at max, we get a 0.99something value. +*/ +static inline ARDOUR::gain_t +slider_position_to_gain (double pos) +{ + /* XXX Marcus writes: this doesn't seem right to me. but i don't have a better answer ... */ + if (pos == 0.0) return 0; + return pow (2.0,(sqrt(sqrt(sqrt(pos)))*198.0-192.0)/6.0); +} + +MackieControlProtocol::MackieControlProtocol (Session& session) + : ControlProtocol (session, X_("Mackie")) + , _current_initial_bank( 0 ) + , connections_back( _connections ) + , _surface( 0 ) + , _ports_changed( false ) + , _polling( true ) + , pfd( 0 ) + , nfds( 0 ) +{ + //cout << "MackieControlProtocol::MackieControlProtocol" << endl; + // will start reading from ports, as soon as there are some + pthread_create_and_store (X_("mackie monitor"), &thread, 0, _monitor_work, this); +} + +MackieControlProtocol::~MackieControlProtocol() +{ + //cout << "~MackieControlProtocol::MackieControlProtocol" << endl; + try + { + close(); + } + catch ( exception & e ) + { + cout << "~MackieControlProtocol caught " << e.what() << endl; + } + catch ( ... ) + { + cout << "~MackieControlProtocol caught unknown" << endl; + } + //cout << "finished ~MackieControlProtocol::MackieControlProtocol" << endl; +} + +Mackie::Surface & MackieControlProtocol::surface() +{ + if ( _surface == 0 ) + { + throw MackieControlException( "_surface is 0 in MackieControlProtocol::surface" ); + } + return *_surface; +} + +const Mackie::MackiePort & MackieControlProtocol::mcu_port() const +{ + return dynamic_cast<const MackiePort &>( *_ports[0] ); +} + +Mackie::MackiePort & MackieControlProtocol::mcu_port() +{ + return dynamic_cast<const MackiePort &>( *_ports[0] ); +} + +// go to the previous track. +// Assume that get_sorted_routes().size() > route_table.size() +void MackieControlProtocol::prev_track() +{ + if ( _current_initial_bank >= 1 ) + { + session->set_dirty(); + switch_banks( _current_initial_bank - 1 ); + } +} + +// go to the next track. +// Assume that get_sorted_routes().size() > route_table.size() +void MackieControlProtocol::next_track() +{ + Sorted sorted = get_sorted_routes(); + if ( _current_initial_bank + route_table.size() < sorted.size() ) + { + session->set_dirty(); + switch_banks( _current_initial_bank + 1 ); + } +} + +void MackieControlProtocol::clear_route_signals() +{ + for( RouteSignals::iterator it = route_signals.begin(); it != route_signals.end(); ++it ) + { + delete *it; + } + route_signals.clear(); +} + +// return the port for a given id - 0 based +// throws an exception if no port found +MackiePort & MackieControlProtocol::port_for_id( uint32_t index ) +{ + uint32_t current_max = 0; + for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) + { + current_max += (*it)->strips(); + if ( index < current_max ) return **it; + } + + // oops - no matching port + ostringstream os; + os << "No port for index " << index; + throw MackieControlException( os.str() ); +} + +// predicate for sort call in get_sorted_routes +struct RouteByRemoteId +{ + bool operator () ( const shared_ptr<Route> & a, const shared_ptr<Route> & b ) const + { + return a->remote_control_id() < b->remote_control_id(); + } + + bool operator () ( const Route & a, const Route & b ) const + { + return a.remote_control_id() < b.remote_control_id(); + } + + bool operator () ( const Route * a, const Route * b ) const + { + return a->remote_control_id() < b->remote_control_id(); + } +}; + +MackieControlProtocol::Sorted MackieControlProtocol::get_sorted_routes() +{ + Sorted sorted; + + // fetch all routes + boost::shared_ptr<Session::RouteList> routes = session->get_routes(); + set<uint32_t> remote_ids; + + // routes with remote_id 0 should never be added + // TODO verify this with ardour devs + // remote_ids.insert( 0 ); + + // sort in remote_id order, and exclude master, control and hidden routes + // and any routes that are already set. + for ( Session::RouteList::iterator it = routes->begin(); it != routes->end(); ++it ) + { + Route & route = **it; + if ( + route.active() + && !route.master() + && !route.hidden() + && !route.control() + && remote_ids.find( route.remote_control_id() ) == remote_ids.end() + ) + { + sorted.push_back( *it ); + remote_ids.insert( route.remote_control_id() ); + } + } + sort( sorted.begin(), sorted.end(), RouteByRemoteId() ); + return sorted; +} + +void MackieControlProtocol::refresh_current_bank() +{ + switch_banks( _current_initial_bank ); +} + +void MackieControlProtocol::switch_banks( int initial ) +{ + // DON'T prevent bank switch if initial == _current_initial_bank + // because then this method can't be used as a refresh + + // sanity checking + Sorted sorted = get_sorted_routes(); + int delta = sorted.size() - route_table.size(); + if ( initial < 0 || ( delta > 0 && initial > delta ) ) + { + cout << "not switching to " << initial << endl; + return; + } + _current_initial_bank = initial; + + // first clear the signals from old routes + // taken care of by the RouteSignal destructors + clear_route_signals(); + + // now set the signals for new routes + if ( _current_initial_bank <= sorted.size() ) + { + // fetch the bank start and end to switch to + uint32_t end_pos = min( route_table.size(), sorted.size() ); + Sorted::iterator it = sorted.begin() + _current_initial_bank; + Sorted::iterator end = sorted.begin() + _current_initial_bank + end_pos; + //cout << "switch to " << _current_initial_bank << ", " << end_pos << endl; + + // link routes to strips + uint32_t i = 0; + for ( ; it != end && it != sorted.end(); ++it, ++i ) + { + boost::shared_ptr<Route> route = *it; + Strip & strip = *surface().strips[i]; + //cout << "remote id " << route->remote_control_id() << " connecting " << route->name() << " to " << strip.name() << " with port " << port_for_id(i) << endl; + route_table[i] = route; + RouteSignal * rs = new RouteSignal( *route, *this, strip, port_for_id(i) ); + route_signals.push_back( rs ); + // update strip from route + rs->notify_all(); + } + + // create dead strips if there aren't enough routes to + // fill a bank + for ( ; i < route_table.size(); ++i ) + { + Strip & strip = *surface().strips[i]; + // send zero for this strip + port_for_id(i).write( builder.zero_strip( strip ) ); + } + } + + // display the current start bank. + if ( mcu_port().emulation() == MackiePort::bcf2000 ) + { + if ( _current_initial_bank == 0 ) + { + // send Ar. to 2-char display on the master + mcu_port().write( builder.two_char_display( "Ar", ".." ) ); + } + else + { + // write the current first remote_id to the 2-char display + mcu_port().write( builder.two_char_display( _current_initial_bank ) ); + } + } +} + +void MackieControlProtocol::zero_all() +{ + // TODO turn off 55-char and SMPTE displays + + if ( mcu_port().emulation() == MackiePort::bcf2000 ) + { + // clear 2-char display + mcu_port().write( builder.two_char_display( "LC" ) ); + } + + // zero all strips + for ( Surface::Strips::iterator it = surface().strips.begin(); it != surface().strips.end(); ++it ) + { + port_for_id( (*it)->index() ).write( builder.zero_strip( **it ) ); + } + + // and the master strip + mcu_port().write( builder.zero_strip( master_strip() ) ); + + // and the led ring for the master strip, in bcf mode + if ( mcu_port().emulation() == MackiePort::bcf2000 ) + { + Control & control = *surface().controls_by_name["jog"]; + mcu_port().write( builder.build_led_ring( dynamic_cast<Pot &>( control ), off ) ); + } + + // turn off global buttons and leds + // global buttons are only ever on mcu_port, so we don't have + // to figure out which port. + for ( Surface::Controls::iterator it = surface().controls.begin(); it != surface().controls.end(); ++it ) + { + Control & control = **it; + if ( !control.group().is_strip() && control.accepts_feedback() ) + { + mcu_port().write( builder.zero_control( control ) ); + } + } +} + +int MackieControlProtocol::set_active (bool yn) +{ + if ( yn != _active ) + { + try + { + // the reason for the locking and unlocking is that + // glibmm can't do a condition wait on a RecMutex + if ( yn ) + { + // TODO what happens if this fails half way? + + // create MackiePorts + { + Glib::Mutex::Lock lock( update_mutex ); + create_ports(); + } + + // make sure the ports are being listened to + update_ports(); + + // wait until poll thread is running, with ports to poll + // the mutex is only there because conditions require a mutex + { + Glib::Mutex::Lock lock( update_mutex ); + while ( nfds == 0 ) update_cond.wait( update_mutex ); + } + + // now initialise MackiePorts - ie exchange sysex messages + for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) + { + (*it)->open(); + } + + // wait until all ports are active + // TODO a more sophisticated approach would + // allow things to start up with only an MCU, even if + // extenders were specified but not responding. + for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) + { + (*it)->wait_for_init(); + } + + // create surface object. This depends on the ports being + // correctly initialised + initialize_surface(); + connect_session_signals(); + + // yeehah! + _active = true; + + // send current control positions to surface + // must come after _active = true otherwise it won't run + update_surface(); + } + else + { + close(); + _active = false; + } + } + catch( exception & e ) + { + cout << "set_active to false because exception caught: " << e.what() << endl; + _active = false; + throw; + } + } + + return 0; +} + +bool MackieControlProtocol::handle_strip_button( Control & control, ButtonState bs, boost::shared_ptr<Route> route ) +{ + bool state = false; + + if ( bs == press ) + { + if ( control.name() == "recenable" ) + { + state = !route->record_enabled(); + route->set_record_enable( state, this ); + } + else if ( control.name() == "mute" ) + { + state = !route->muted(); + route->set_mute( state, this ); + } + else if ( control.name() == "solo" ) + { + state = !route->soloed(); + route->set_solo( state, this ); + } + else if ( control.name() == "select" ) + { + // TODO make the track selected. Whatever that means. + //state = default_button_press( dynamic_cast<Button&>( control ) ); + } + else if ( control.name() == "vselect" ) + { + // TODO could be used to select different things to apply the pot to? + //state = default_button_press( dynamic_cast<Button&>( control ) ); + } + } + + if ( control.name() == "fader_touch" ) + { + state = bs == press; + control.strip().gain().touch( state ); + } + + return state; +} + +void MackieControlProtocol::update_led( Mackie::Button & button, Mackie::LedState ls ) +{ + MackiePort * port = 0; + if ( button.group().is_strip() ) + { + if ( button.group().is_master() ) + { + port = &mcu_port(); + } + else + { + port = &port_for_id( dynamic_cast<const Strip&>( button.group() ).index() ); + } + } + else + { + port = &mcu_port(); + } + if ( ls != none ) port->write( builder.build_led( button, ls ) ); +} + +void MackieControlProtocol::update_global_button( const string & name, LedState ls ) +{ + if ( surface().controls_by_name.find( name ) !=surface().controls_by_name.end() ) + { + Button * button = dynamic_cast<Button*>( surface().controls_by_name[name] ); + mcu_port().write( builder.build_led( button->led(), ls ) ); + } + else + { + cout << "Button " << name << " not found" << endl; + } +} + +// send messages to surface to set controls to correct values +void MackieControlProtocol::update_surface() +{ + if ( _active ) + { + // do the initial bank switch to connect signals + // _current_initial_bank is initialised by set_state + switch_banks( _current_initial_bank ); + + // create a RouteSignal for the master route + // but only the first time around + master_route_signal = shared_ptr<RouteSignal>( new RouteSignal( *master_route(), *this, master_strip(), mcu_port() ) ); + // update strip from route + master_route_signal->notify_all(); + + // update global buttons and displays + notify_record_state_changed(); + notify_transport_state_changed(); + } +} + +void MackieControlProtocol::connect_session_signals() +{ + // receive routes added + connections_back = session->RouteAdded.connect( ( mem_fun (*this, &MackieControlProtocol::notify_route_added) ) ); + // receive record state toggled + connections_back = session->RecordStateChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_record_state_changed) ) ); + // receive transport state changed + connections_back = session->TransportStateChange.connect( ( mem_fun (*this, &MackieControlProtocol::notify_transport_state_changed) ) ); + // receive punch-in and punch-out + connections_back = Config->ParameterChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_parameter_changed) ) ); + // receive rude solo changed + connections_back = session->SoloActive.connect( ( mem_fun (*this, &MackieControlProtocol::notify_solo_active_changed) ) ); + + // make sure remote id changed signals reach here + // see also notify_route_added + Sorted sorted = get_sorted_routes(); + for ( Sorted::iterator it = sorted.begin(); it != sorted.end(); ++it ) + { + connections_back = (*it)->RemoteControlIDChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_remote_id_changed) ) ); + } +} + +void MackieControlProtocol::add_port( MIDI::Port & midi_port, int number ) +{ + MackiePort * sport = new MackiePort( *this, midi_port, number ); + _ports.push_back( sport ); + + connections_back = sport->init_event.connect( + sigc::bind ( + mem_fun (*this, &MackieControlProtocol::handle_port_init) + , sport + ) + ); + + connections_back = sport->active_event.connect( + sigc::bind ( + mem_fun (*this, &MackieControlProtocol::handle_port_active) + , sport + ) + ); + + connections_back = sport->inactive_event.connect( + sigc::bind ( + mem_fun (*this, &MackieControlProtocol::handle_port_inactive) + , sport + ) + ); + + _ports_changed = true; +} + +void MackieControlProtocol::create_ports() +{ + MIDI::Manager * mm = MIDI::Manager::instance(); + + // open main port + { + MIDI::Port * midi_port = mm->port( default_port_name ); + + if ( midi_port == 0 ) { + ostringstream os; + os << string_compose( _("no MIDI port named \"%1\" exists - Mackie control disabled"), default_port_name ); + error << os.str() << endmsg; + throw MackieControlException( os.str() ); + } + add_port( *midi_port, 0 ); + } + + // open extender ports. Up to 9. Should be enough. + // could also use mm->get_midi_ports() + string ext_port_base = "mcu_xt_"; + for ( int index = 1; index <= 9; ++index ) + { + ostringstream os; + os << ext_port_base << index; + MIDI::Port * midi_port = mm->port( os.str() ); + if ( midi_port != 0 ) add_port( *midi_port, index ); + } +} + +shared_ptr<Route> MackieControlProtocol::master_route() +{ + shared_ptr<Route> retval; + retval = session->route_by_name( "master" ); + if ( retval == 0 ) + { + // TODO search through all routes for one with the master attribute set + } + return retval; +} + +Strip & MackieControlProtocol::master_strip() +{ + return dynamic_cast<Strip&>( *surface().groups["master"] ); +} + +void MackieControlProtocol::initialize_surface() +{ + // set up the route table + int strips = 0; + for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) + { + strips += (*it)->strips(); + } + + set_route_table_size( strips ); + + switch ( mcu_port().emulation() ) + { + case MackiePort::bcf2000: _surface = new BcfSurface( strips ); break; + case MackiePort::mackie: _surface = new MackieSurface( strips ); break; + default: + ostringstream os; + os << "no Surface class found for emulation: " << mcu_port().emulation(); + throw MackieControlException( os.str() ); + } + _surface->init(); + + // Connect events. Must be after route table otherwise there will be trouble + for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) + { + connections_back = (*it)->control_event.connect( ( mem_fun (*this, &MackieControlProtocol::handle_control_event) ) ); + } +} + +void MackieControlProtocol::close() +{ + // TODO disconnect port active/inactive signals + // Or at least put a lock here + + // disconnect global signals from Session + // TODO Since *this is a sigc::trackable, this shouldn't be necessary + // but it is for some reason +#if 0 + for( vector<sigc::connection>::iterator it = _connections.begin(); it != _connections.end(); ++it ) + { + it->disconnect(); + } +#endif + + if ( _surface != 0 ) + { + // These will fail if the port has gone away. + // So catch the exception and do the rest of the + // close afterwards + // because the bcf doesn't respond to the next 3 sysex messages + try + { + zero_all(); + } + catch ( exception & e ) + { + cout << "MackieControlProtocol::close caught exception: " << e.what() << endl; + } + + for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) + { + try + { + MackiePort & port = **it; + // faders to minimum + port.write_sysex( 0x61 ); + // All LEDs off + port.write_sysex( 0x62 ); + // Reset (reboot into offline mode) + port.write_sysex( 0x63 ); + } + catch ( exception & e ) + { + cout << "MackieControlProtocol::close caught exception: " << e.what() << endl; + } + } + + // disconnect routes from strips + clear_route_signals(); + + delete _surface; + _surface = 0; + } + + // stop polling, and wait for it... + _polling = false; + pthread_join( thread, 0 ); + + // shut down MackiePorts + for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) + { + delete *it; + } + _ports.clear(); + + // this is done already in monitor_work. But it's here so we know. + delete[] pfd; + pfd = 0; + nfds = 0; +} + +void* MackieControlProtocol::_monitor_work (void* arg) +{ + return static_cast<MackieControlProtocol*>(arg)->monitor_work (); +} + +XMLNode & MackieControlProtocol::get_state() +{ + //cout << "MackieControlProtocol::get_state" << endl; + + // add name of protocol + XMLNode* node = new XMLNode( X_("Protocol") ); + node->add_property( X_("name"), _name ); + + // add current bank + ostringstream os; + os << _current_initial_bank; + node->add_property( X_("bank"), os.str() ); + + return *node; +} + +int MackieControlProtocol::set_state( const XMLNode & node ) +{ + //cout << "MackieControlProtocol::set_state: active " << _active << endl; + int retval = 0; + + // fetch current bank + if ( node.property( X_("bank") ) != 0 ) + { + string bank = node.property( X_("bank") )->value(); + try + { + set_active( true ); + uint32_t new_bank = atoi( bank.c_str() ); + if ( _current_initial_bank != new_bank ) switch_banks( new_bank ); + } + catch ( exception & e ) + { + cout << "exception in MackieControlProtocol::set_state: " << e.what() << endl; + return -1; + } + } + + return retval; +} + +void MackieControlProtocol::handle_control_event( SurfacePort & port, Control & control, const ControlState & state ) +{ + uint32_t index = control.ordinal() - 1 + ( port.number() * port.strips() ); + boost::shared_ptr<Route> route; + if ( control.group().is_strip() ) + { + if ( control.group().is_master() ) + { + route = master_route(); + } + else if ( index < route_table.size() ) + route = route_table[index]; + else + cerr << "Warning: index is " << index << " which is not in the route table, size: " << route_table.size() << endl; + } + + // This handles control element events from the surface + // the state of the controls on the surface is usually updated + // from UI events. + switch ( control.type() ) + { + case Control::type_fader: + if ( control.group().is_strip() ) + { + // find the route in the route table for the id + // if the route isn't available, skip it + // at which point the fader should just reset itself + if ( route != 0 ) + { + route->set_gain( slider_position_to_gain( state.pos ), this ); + + // must echo bytes back to slider now, because + // the notifier only works if the fader is not being + // touched. Which it is if we're getting input. + port.write( builder.build_fader( (Fader&)control, state.pos ) ); + } + } + else + { + // master fader + boost::shared_ptr<Route> route = master_route(); + if ( route ) + { + route->set_gain( slider_position_to_gain( state.pos ), this ); + port.write( builder.build_fader( (Fader&)control, state.pos ) ); + } + } + break; + + case Control::type_button: + if ( control.group().is_strip() ) + { + // strips + if ( route != 0 ) + { + handle_strip_button( control, state.button_state, route ); + } + else + { + // no route so always switch the light off + // because no signals will be emitted by a non-route + port.write( builder.build_led( control.led(), off ) ); + } + } + else if ( control.group().is_master() ) + { + // master fader touch + boost::shared_ptr<Route> route = master_route(); + if ( route ) + handle_strip_button( control, state.button_state, route ); + } + else + { + // handle all non-strip buttons + surface().handle_button( *this, state.button_state, dynamic_cast<Button&>( control ) ); + } + break; + + // pot (jog wheel, external control) + case Control::type_pot: + if ( control.group().is_strip() ) + { + if ( route != 0 ) + { + if ( route->panner().size() == 1 ) + { + // assume pan for now + float xpos; + route->panner()[0]->get_effective_position (xpos); + + // calculate new value, and trim + xpos += state.delta; + if ( xpos > 1.0 ) + xpos = 1.0; + else if ( xpos < 0.0 ) + xpos = 0.0; + + route->panner()[0]->set_position( xpos ); + } + } + else + { + // it's a pot for an umnapped route, so turn all the lights off + port.write( builder.build_led_ring( dynamic_cast<Pot &>( control ), off ) ); + } + } + else + { + if ( control.name() == "jog" ) + { + // TODO use current snap-to setting? + long delta = state.ticks * 1000; + nframes_t next = session->transport_frame() + delta; + if ( delta < 0 && session->transport_frame() < (nframes_t) abs( delta ) ) + { + next = session->current_start_frame(); + } + else if ( next > session->current_end_frame() ) + { + next = session->current_end_frame(); + } + + // doesn't work very well + session->request_locate( next, session->transport_rolling() ); + + // turn off the led ring, for bcf emulation mode + port.write( builder.build_led_ring( dynamic_cast<Pot &>( control ), off ) ); + } + else + { + cout << "external controller" << state.ticks << endl; + } + } + break; + + default: + cout << "Control::type not handled: " << control.type() << endl; + } +} + +///////////////////////////////////////////////// +// handlers for Route signals +// TODO should these be part of RouteSignal? +// They started off as sigc handlers for signals +// from Route, but they're also used in polling for automation +///////////////////////////////////////////////// + +void MackieControlProtocol::notify_solo_changed( RouteSignal * route_signal ) +{ + try + { + Button & button = route_signal->strip().solo(); + route_signal->port().write( builder.build_led( button, route_signal->route().soloed() ) ); + } + catch( exception & e ) + { + cout << e.what() << endl; + } +} + +void MackieControlProtocol::notify_mute_changed( RouteSignal * route_signal ) +{ + try + { + Button & button = route_signal->strip().mute(); + route_signal->port().write( builder.build_led( button, route_signal->route().muted() ) ); + } + catch( exception & e ) + { + cout << e.what() << endl; + } +} + +void MackieControlProtocol::notify_record_enable_changed( RouteSignal * route_signal ) +{ + try + { + Button & button = route_signal->strip().recenable(); + route_signal->port().write( builder.build_led( button, route_signal->route().record_enabled() ) ); + } + catch( exception & e ) + { + cout << e.what() << endl; + } +} + +void MackieControlProtocol::notify_gain_changed( RouteSignal * route_signal ) +{ + try + { + Fader & fader = route_signal->strip().gain(); + if ( !fader.touch() ) + { + route_signal->port().write( builder.build_fader( fader, gain_to_slider_position( route_signal->route().effective_gain() ) ) ); + } + } + catch( exception & e ) + { + cout << e.what() << endl; + } +} + +void MackieControlProtocol::notify_name_changed( void *, RouteSignal * route_signal ) +{ + try + { + // TODO implement MackieControlProtocol::notify_name_changed + } + catch( exception & e ) + { + cout << e.what() << endl; + } +} + +// TODO deal with > 1 channel being panned +void MackieControlProtocol::notify_panner_changed( RouteSignal * route_signal ) +{ + try + { + Pot & pot = route_signal->strip().vpot(); + + if ( route_signal->route().panner().size() == 1 ) + { + float pos; + route_signal->route().panner()[0]->get_effective_position( pos); + route_signal->port().write( builder.build_led_ring( pot, ControlState( on, pos ) ) ); + } + else + { + route_signal->port().write( builder.zero_control( pot ) ); + } + } + catch( exception & e ) + { + cout << e.what() << endl; + } +} + +// TODO handle plugin automation polling +void MackieControlProtocol::update_automation( RouteSignal & rs ) +{ + ARDOUR::AutoState gain_state = rs.route().gain_automation_state(); + if ( gain_state == Touch || gain_state == Play ) + { + notify_gain_changed( &rs ); + } + + ARDOUR::AutoState panner_state = rs.route().panner().automation_state(); + if ( panner_state == Touch || panner_state == Play ) + { + notify_panner_changed( &rs ); + } +} + +void MackieControlProtocol::poll_automation() +{ + if ( _active ) + { + // do all currently mapped routes + for( RouteSignals::iterator it = route_signals.begin(); it != route_signals.end(); ++it ) + { + update_automation( **it ); + } + + // and the master strip + if ( master_route_signal != 0 ) update_automation( *master_route_signal ); + } +} + +///////////////////////////////////// +// Transport Buttons +///////////////////////////////////// + +LedState MackieControlProtocol::rewind_press( Button & button ) +{ + // can use first_mark_before/after as well + Location * loc = session->locations()->first_location_before ( + session->transport_frame() + ); + if ( loc != 0 ) session->request_locate( loc->start(), session->transport_rolling() ); + return on; +} + +LedState MackieControlProtocol::rewind_release( Button & button ) +{ + return off; +} + +LedState MackieControlProtocol::ffwd_press( Button & button ) +{ + // can use first_mark_before/after as well + Location * loc = session->locations()->first_location_after ( + session->transport_frame() + ); + if ( loc != 0 ) session->request_locate( loc->start(), session->transport_rolling() ); + return on; +} + +LedState MackieControlProtocol::ffwd_release( Button & button ) +{ + return off; +} + +LedState MackieControlProtocol::stop_press( Button & button ) +{ + session->request_stop(); + return on; +} + +LedState MackieControlProtocol::stop_release( Button & button ) +{ + return session->transport_stopped(); +} + +LedState MackieControlProtocol::play_press( Button & button ) +{ + session->request_transport_speed( 1.0 ); + return on; +} + +LedState MackieControlProtocol::play_release( Button & button ) +{ + return session->transport_rolling(); +} + +LedState MackieControlProtocol::record_press( Button & button ) +{ + if ( session->get_record_enabled() ) + session->disable_record( false ); + else + session->maybe_enable_record(); + return on; +} + +LedState MackieControlProtocol::record_release( Button & button ) +{ + if ( session->get_record_enabled() ) + { + if ( session->transport_rolling() ) + return on; + else + return flashing; + } + else + return off; +} + +/////////////////////////////////////////// +// Session signals +/////////////////////////////////////////// + +void MackieControlProtocol::notify_parameter_changed( const char * name_str ) +{ + string name( name_str ); + if ( name == "punch-in" ) + { + update_global_button( "punch_in", Config->get_punch_in() ); + } + else if ( name == "punch-out" ) + { + update_global_button( "punch_out", Config->get_punch_out() ); + } + else if ( name == "clicking" ) + { + update_global_button( "clicking", Config->get_clicking() ); + } + else + { + cout << "parameter changed: " << name << endl; + } +} + +// RouteList is the set of routes that have just been added +void MackieControlProtocol::notify_route_added( ARDOUR::Session::RouteList & rl ) +{ + // currently assigned banks are less than the full set of + // strips, so activate the new strip now. + if ( route_signals.size() < route_table.size() ) + { + refresh_current_bank(); + } + // otherwise route added, but current bank needs no updating + + // make sure remote id changes in the new route are handled + typedef ARDOUR::Session::RouteList ARS; + for ( ARS::iterator it = rl.begin(); it != rl.end(); ++it ) + { + connections_back = (*it)->RemoteControlIDChanged.connect( ( mem_fun (*this, &MackieControlProtocol::notify_remote_id_changed) ) ); + } +} + +void MackieControlProtocol::notify_solo_active_changed( bool active ) +{ + Button * rude_solo = reinterpret_cast<Button*>( surface().controls_by_name["solo"] ); + mcu_port().write( builder.build_led( *rude_solo, active ? flashing : off ) ); +} + +void MackieControlProtocol::notify_remote_id_changed() +{ + Sorted sorted = get_sorted_routes(); + + // if a remote id has been moved off the end, we need to shift + // the current bank backwards. + if ( sorted.size() - _current_initial_bank < route_signals.size() ) + { + // but don't shift backwards past the zeroth channel + switch_banks( max((Sorted::size_type) 0, sorted.size() - route_signals.size() ) ); + } + // Otherwise just refresh the current bank + else + { + refresh_current_bank(); + } +} + +/////////////////////////////////////////// +// Transport signals +/////////////////////////////////////////// + +void MackieControlProtocol::notify_record_state_changed() +{ + // switch rec button on / off / flashing + Button * rec = reinterpret_cast<Button*>( surface().controls_by_name["record"] ); + mcu_port().write( builder.build_led( *rec, record_release( *rec ) ) ); +} + +void MackieControlProtocol::notify_transport_state_changed() +{ + // switch various play and stop buttons on / off + update_global_button( "play", session->transport_rolling() ); + update_global_button( "stop", !session->transport_rolling() ); + update_global_button( "loop", session->get_play_loop() ); + + // rec is special because it's tristate + Button * rec = reinterpret_cast<Button*>( surface().controls_by_name["record"] ); + mcu_port().write( builder.build_led( *rec, record_release( *rec ) ) ); +} + +LedState MackieControlProtocol::loop_press( Button & button ) +{ + session->request_play_loop( !session->get_play_loop() ); + return on; +} + +LedState MackieControlProtocol::loop_release( Button & button ) +{ + return session->get_play_loop(); +} + +LedState MackieControlProtocol::punch_in_press( Button & button ) +{ + bool state = !Config->get_punch_in(); + Config->set_punch_in( state ); + return state; +} + +LedState MackieControlProtocol::punch_in_release( Button & button ) +{ + return Config->get_punch_in(); +} + +LedState MackieControlProtocol::punch_out_press( Button & button ) +{ + bool state = !Config->get_punch_out(); + Config->set_punch_out( state ); + return state; +} + +LedState MackieControlProtocol::punch_out_release( Button & button ) +{ + return Config->get_punch_out(); +} + +LedState MackieControlProtocol::home_press( Button & button ) +{ + session->goto_start(); + return on; +} + +LedState MackieControlProtocol::home_release( Button & button ) +{ + return off; +} + +LedState MackieControlProtocol::end_press( Button & button ) +{ + session->goto_end(); + return on; +} + +LedState MackieControlProtocol::end_release( Button & button ) +{ + return off; +} + +LedState MackieControlProtocol::clicking_press( Button & button ) +{ + bool state = !Config->get_clicking(); + Config->set_clicking( state ); + return state; +} + +LedState MackieControlProtocol::clicking_release( Button & button ) +{ + return Config->get_clicking(); +} + +LedState MackieControlProtocol::global_solo_press( Button & button ) +{ + bool state = !session->soloing(); + session->set_all_solo ( state ); + return state; +} + +LedState MackieControlProtocol::global_solo_release( Button & button ) +{ + return session->soloing(); +} + +///////////////////////////////////// +// Bank Switching +///////////////////////////////////// +LedState MackieControlProtocol::left_press( Button & button ) +{ + Sorted sorted = get_sorted_routes(); + if ( sorted.size() > route_table.size() ) + { + int new_initial = _current_initial_bank - route_table.size(); + if ( new_initial < 0 ) new_initial = 0; + if ( new_initial != int( _current_initial_bank ) ) + { + session->set_dirty(); + switch_banks( new_initial ); + } + + return on; + } + else + { + return flashing; + } +} + +LedState MackieControlProtocol::left_release( Button & button ) +{ + return off; +} + +LedState MackieControlProtocol::right_press( Button & button ) +{ + Sorted sorted = get_sorted_routes(); + if ( sorted.size() > route_table.size() ) + { + uint32_t delta = sorted.size() - ( route_table.size() + _current_initial_bank ); + if ( delta > route_table.size() ) delta = route_table.size(); + if ( delta > 0 ) + { + session->set_dirty(); + switch_banks( _current_initial_bank + delta ); + } + + return on; + } + else + { + return flashing; + } +} + +LedState MackieControlProtocol::right_release( Button & button ) +{ + return off; +} + +LedState MackieControlProtocol::channel_left_press( Button & button ) +{ + Sorted sorted = get_sorted_routes(); + if ( sorted.size() > route_table.size() ) + { + prev_track(); + return on; + } + else + { + return flashing; + } +} + +LedState MackieControlProtocol::channel_left_release( Button & button ) +{ + return off; +} + +LedState MackieControlProtocol::channel_right_press( Button & button ) +{ + Sorted sorted = get_sorted_routes(); + if ( sorted.size() > route_table.size() ) + { + next_track(); + return on; + } + else + { + return flashing; + } +} + +LedState MackieControlProtocol::channel_right_release( Button & button ) +{ + return off; +} diff --git a/libs/surfaces/mackie/mackie_control_protocol.h b/libs/surfaces/mackie/mackie_control_protocol.h new file mode 100644 index 0000000000..d71979b463 --- /dev/null +++ b/libs/surfaces/mackie/mackie_control_protocol.h @@ -0,0 +1,307 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#ifndef ardour_mackie_control_protocol_h +#define ardour_mackie_control_protocol_h + +#include <vector> + +#include <sys/time.h> +#include <pthread.h> + +#include <glibmm/thread.h> + +#include <ardour/types.h> +#include <ardour/session.h> +#include <midi++/types.h> + +#include <control_protocol/control_protocol.h> +#include "midi_byte_array.h" +#include "controls.h" +#include "route_signal.h" +#include "mackie_button_handler.h" +#include "mackie_port.h" + +namespace MIDI { + class Port; + class Parser; +} + +namespace Mackie { + class Surface; +} + +/** + This handles the plugin duties, and the midi encoding and decoding, + and the signal callbacks, mostly from ARDOUR::Route. + + The model of the control surface is handled by classes in controls.h + + What happens is that each strip on the control surface has + a corresponding route in ControlProtocol::route_table. When + an incoming midi message is signaled, the correct route + is looked up, and the relevant changes made to it. + + For each route currently in route_table, there's a RouteSignal object + which encapsulates the signals that indicate that there are changes + to be sent to the surface. The signals are handled by this class. + + Calls to signal handlers pass a Route object which is used to look + up the relevant Strip in Surface. Then the state is retrieved from + the Route and encoded as the correct midi message. +*/ +class MackieControlProtocol +: public ARDOUR::ControlProtocol +, public Mackie::MackieButtonHandler +{ + public: + MackieControlProtocol( ARDOUR::Session & ); + virtual ~MackieControlProtocol(); + + int set_active (bool yn); + + XMLNode& get_state (); + int set_state (const XMLNode&); + + static bool probe(); + + Mackie::Surface & surface(); + + // control events + void handle_control_event( Mackie::SurfacePort & port, Mackie::Control & control, const Mackie::ControlState & state ); + + // strip/route related stuff + public: + /// Signal handler for Route::solo + void notify_solo_changed( Mackie::RouteSignal * ); + /// Signal handler for Route::mute + void notify_mute_changed( Mackie::RouteSignal * ); + /// Signal handler for Route::record_enable_changed + void notify_record_enable_changed( Mackie::RouteSignal * ); + /// Signal handler for Route::gain_changed ( from IO ) + void notify_gain_changed( Mackie::RouteSignal * ); + /// Signal handler for Route::name_change + void notify_name_changed( void *, Mackie::RouteSignal * ); + /// Signal handler from Panner::Change + void notify_panner_changed( Mackie::RouteSignal * ); + /// Signal handler for new routes added + void notify_route_added( ARDOUR::Session::RouteList & ); + + void notify_remote_id_changed(); + + /// rebuild the current bank. Called on route added/removed and + /// remote id changed. + void refresh_current_bank(); + + // global buttons (ie button not part of strips) + public: + // button-related signals + void notify_record_state_changed(); + void notify_transport_state_changed(); + // mainly to pick up punch-in and punch-out + void notify_parameter_changed( const char * ); + void notify_solo_active_changed( bool ); + + // this is called to generate the midi to send in response to + // a button press. + void update_led( Mackie::Button & button, Mackie::LedState ); + + // calls update_led, but looks up the button by name + void update_global_button( const std::string & name, Mackie::LedState ); + + // transport button handler methods from MackieButtonHandler + virtual Mackie::LedState rewind_press( Mackie::Button & ); + virtual Mackie::LedState rewind_release( Mackie::Button & ); + + virtual Mackie::LedState ffwd_press( Mackie::Button & ); + virtual Mackie::LedState ffwd_release( Mackie::Button & ); + + virtual Mackie::LedState stop_press( Mackie::Button & ); + virtual Mackie::LedState stop_release( Mackie::Button & ); + + virtual Mackie::LedState play_press( Mackie::Button & ); + virtual Mackie::LedState play_release( Mackie::Button & ); + + virtual Mackie::LedState record_press( Mackie::Button & ); + virtual Mackie::LedState record_release( Mackie::Button & ); + + virtual Mackie::LedState loop_press( Mackie::Button & ); + virtual Mackie::LedState loop_release( Mackie::Button & ); + + virtual Mackie::LedState punch_in_press( Mackie::Button & ); + virtual Mackie::LedState punch_in_release( Mackie::Button & ); + + virtual Mackie::LedState punch_out_press( Mackie::Button & ); + virtual Mackie::LedState punch_out_release( Mackie::Button & ); + + virtual Mackie::LedState home_press( Mackie::Button & ); + virtual Mackie::LedState home_release( Mackie::Button & ); + + virtual Mackie::LedState end_press( Mackie::Button & ); + virtual Mackie::LedState end_release( Mackie::Button & ); + + // bank switching button handler methods from MackieButtonHandler + virtual Mackie::LedState left_press( Mackie::Button & ); + virtual Mackie::LedState left_release( Mackie::Button & ); + + virtual Mackie::LedState right_press( Mackie::Button & ); + virtual Mackie::LedState right_release( Mackie::Button & ); + + virtual Mackie::LedState channel_left_press( Mackie::Button & ); + virtual Mackie::LedState channel_left_release( Mackie::Button & ); + + virtual Mackie::LedState channel_right_press( Mackie::Button & ); + virtual Mackie::LedState channel_right_release( Mackie::Button & ); + + virtual Mackie::LedState clicking_press( Mackie::Button & ); + virtual Mackie::LedState clicking_release( Mackie::Button & ); + + virtual Mackie::LedState global_solo_press( Mackie::Button & ); + virtual Mackie::LedState global_solo_release( Mackie::Button & ); + + protected: + // create instances of MackiePort, depending on what's found in ardour.rc + void create_ports(); + + // shut down the surface + void close(); + + // create the Surface object, with the correct number + // of strips for the currently connected ports and + // hook up the control event notification + void initialize_surface(); + + // This sets up the notifications and sets the + // controls to the correct values + void update_surface(); + + // connects global (not strip) signals from the Session to here + // so the surface can be notified of changes from the other UIs. + void connect_session_signals(); + + // set all controls to their zero position + void zero_all(); + + /** + Fetch the set of routes to be considered for control by the + surface. Excluding master, hidden and control routes, and inactive routes + */ + typedef std::vector<boost::shared_ptr<ARDOUR::Route> > Sorted; + Sorted get_sorted_routes(); + + // bank switching + void switch_banks( int initial ); + void prev_track(); + void next_track(); + + // delete all RouteSignal objects connecting Routes to Strips + void clear_route_signals(); + + /// This is the main MCU port, ie not an extender port + const Mackie::MackiePort & mcu_port() const; + Mackie::MackiePort & mcu_port(); + + typedef std::vector<Mackie::RouteSignal*> RouteSignals; + RouteSignals route_signals; + + // return which of the ports a particular route_table + // index belongs to + Mackie::MackiePort & port_for_id( uint32_t index ); + + /** + Handle a button press for the control and return whether + the corresponding light should be on or off. + */ + bool handle_strip_button( Mackie::Control &, Mackie::ButtonState, boost::shared_ptr<ARDOUR::Route> ); + + /// thread started. Calls monitor_work. + static void* _monitor_work (void* arg); + + /// Polling midi port(s) for incoming messages + void* monitor_work (); + + /// rebuild the set of ports for this surface + void update_ports(); + + /// Returns true if there is pending data, false otherwise + bool poll_ports(); + + /// Trigger the MIDI::Parser + void read_ports(); + + void add_port( MIDI::Port &, int number ); + + /// read automation data from the currently active routes and send to surface + void poll_automation(); + + // called from poll_automation to figure out which automations need to be sent + void update_automation( Mackie::RouteSignal & ); + + /** + notification that the port is about to start it's init sequence. + We must make sure that before this exits, the port is being polled + for new data. + */ + void handle_port_init( Mackie::SurfacePort * ); + + /// notification from a MackiePort that it's now active + void handle_port_active( Mackie::SurfacePort * ); + + /// notification from a MackiePort that it's now inactive + void handle_port_inactive( Mackie::SurfacePort * ); + + boost::shared_ptr<ARDOUR::Route> master_route(); + Mackie::Strip & master_strip(); + + private: + boost::shared_ptr<Mackie::RouteSignal> master_route_signal; + + static const char * default_port_name; + + /// The Midi port(s) connected to the units + typedef vector<Mackie::MackiePort*> MackiePorts; + MackiePorts _ports; + + // the thread that polls the ports for incoming midi data + pthread_t thread; + + /// The initial remote_id of the currently switched in bank. + uint32_t _current_initial_bank; + + /// protects the port list, and polling structures + Glib::Mutex update_mutex; + + /// Protects set_active, and allows waiting on the poll thread + Glib::Cond update_cond; + + // because sigc::trackable doesn't seem to be working + std::vector<sigc::connection> _connections; + std::back_insert_iterator<std::vector<sigc::connection> > connections_back; + + /// The representation of the physical controls on the surface. + Mackie::Surface * _surface; + + /// If a port is opened or closed, this will be + /// true until the port configuration is updated; + bool _ports_changed; + + bool _polling; + struct pollfd * pfd; + int nfds; +}; + +#endif // ardour_mackie_control_protocol_h diff --git a/libs/surfaces/mackie/mackie_control_protocol_poll.cc b/libs/surfaces/mackie/mackie_control_protocol_poll.cc new file mode 100644 index 0000000000..05681c0c25 --- /dev/null +++ b/libs/surfaces/mackie/mackie_control_protocol_poll.cc @@ -0,0 +1,192 @@ +#include "mackie_control_protocol.h" + +#include "midi_byte_array.h" +#include "surface_port.h" + +#include <pbd/pthread_utils.h> +#include <pbd/error.h> + +#include <midi++/types.h> +#include <midi++/port.h> +#include <midi++/manager.h> +#include <midi++/port_request.h> +#include "i18n.h" + +#include <unistd.h> +#include <fcntl.h> +#include <poll.h> +#include <errno.h> + +#include <iostream> +#include <string> +#include <vector> + +using namespace std; +using namespace Mackie; +using namespace PBD; + +const char * MackieControlProtocol::default_port_name = "mcu"; + +bool MackieControlProtocol::probe() +{ + return MIDI::Manager::instance()->port( default_port_name ) != 0; +} + +void * MackieControlProtocol::monitor_work() +{ + // What does ThreadCreatedWithRequestSize do? + PBD::ThreadCreated (pthread_self(), X_("Mackie")); + + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, 0); + pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0); + + // read from midi ports + while ( _polling ) + { + try + { + if ( poll_ports() ) + { + try { read_ports(); } + catch ( exception & e ) { + cout << "MackieControlProtocol::poll_ports caught exception: " << e.what() << endl; + _ports_changed = true; + update_ports(); + } + } + // poll for automation data from the routes + poll_automation(); + } + catch ( exception & e ) + { + cout << "caught exception in MackieControlProtocol::monitor_work " << e.what() << endl; + } + } + + // TODO ports and pfd and nfds should be in a separate class + delete[] pfd; + pfd = 0; + nfds = 0; + + return (void*) 0; +} + +void MackieControlProtocol::update_ports() +{ + if ( _ports_changed ) + { + Glib::Mutex::Lock ul( update_mutex ); + // yes, this is a double-test locking paradigm, or whatever it's called + // because we don't *always* need to acquire the lock for the first test + if ( _ports_changed ) + { + // create new pollfd structures + if ( pfd != 0 ) delete[] pfd; + // TODO This might be a memory leak. How does thread cancellation cleanup work? + pfd = new pollfd[_ports.size()]; + nfds = 0; + + for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it ) + { + //cout << "adding port " << (*it)->port().name() << " to pollfd" << endl; + pfd[nfds].fd = (*it)->port().selectable(); + pfd[nfds].events = POLLIN|POLLHUP|POLLERR; + ++nfds; + } + _ports_changed = false; + } + update_cond.signal(); + } +} + +void MackieControlProtocol::read_ports() +{ + /* now read any data on the ports */ + Glib::Mutex::Lock lock( update_mutex ); + for ( int p = 0; p < nfds; ++p ) + { + // this will cause handle_midi_any in the MackiePort to be triggered + if ( pfd[p].revents & POLLIN > 0 ) + { + // avoid deadlocking? + // doesn't seem to make a difference + //lock.release(); + _ports[p]->read(); + //lock.acquire(); + } + } +} + +bool MackieControlProtocol::poll_ports() +{ + int timeout = 10; // milliseconds + int no_ports_sleep = 1000; // milliseconds + + Glib::Mutex::Lock lock( update_mutex ); + // if there are no ports + if ( nfds < 1 ) + { + lock.release(); + //cout << "poll_ports no ports" << endl; + usleep( no_ports_sleep * 1000 ); + return false; + } + + int retval = poll( pfd, nfds, timeout ); + if ( retval < 0 ) + { + // gdb at work, perhaps + if ( errno != EINTR ) + { + error << string_compose(_("Mackie MIDI thread poll failed (%1)"), strerror( errno ) ) << endmsg; + } + return false; + } + + return retval > 0; +} + +void MackieControlProtocol::handle_port_inactive( SurfacePort * port ) +{ + // port gone away. So stop polling it ASAP + { + // delete the port instance + Glib::Mutex::Lock lock( update_mutex ); + MackiePorts::iterator it = find( _ports.begin(), _ports.end(), port ); + if ( it != _ports.end() ) + { + delete *it; + _ports.erase( it ); + } + } + _ports_changed = true; + update_ports(); + + // TODO all the rebuilding of surfaces and so on +} + +void MackieControlProtocol::handle_port_active( SurfacePort * port ) +{ + // no need to re-add port because it was already added + // during the init phase. So just update the local surface + // representation and send the representation to + // all existing ports + + // TODO update bank size + + // TODO rebuild surface, to have new units + + // finally update session state to the surface + // TODO but this is also done in set_active, and + // in fact update_surface won't execute unless + // _active == true + //cout << "update_surface in handle_port_active" << endl; + update_surface(); +} + +void MackieControlProtocol::handle_port_init( Mackie::SurfacePort * sport ) +{ + //cout << "MackieControlProtocol::handle_port_init" << endl; + _ports_changed = true; + update_ports(); +} diff --git a/libs/surfaces/mackie/mackie_midi_builder.cc b/libs/surfaces/mackie/mackie_midi_builder.cc new file mode 100644 index 0000000000..8ed98a5720 --- /dev/null +++ b/libs/surfaces/mackie/mackie_midi_builder.cc @@ -0,0 +1,173 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "mackie_midi_builder.h" + +#include <typeinfo> +#include <sstream> +#include <iomanip> + +#include "controls.h" +#include "midi_byte_array.h" + +using namespace Mackie; +using namespace std; + +MIDI::byte MackieMidiBuilder::calculate_pot_value( midi_pot_mode mode, const ControlState & state ) +{ + // TODO do an exact calc for 0.50? To allow manually re-centering the port. + + // center on or off + MIDI::byte retval = ( state.pos > 0.45 && state.pos < 0.55 ? 1 : 0 ) << 6; + + // mode + retval |= ( mode << 4 ); + + // value, but only if off hasn't explicitly been set + if ( state.led_state != off ) + retval += ( int(state.pos * 10.0) + 1 ) & 0x0f; // 0b00001111 + + return retval; +} + +MidiByteArray MackieMidiBuilder::build_led_ring( const Pot & pot, const ControlState & state ) +{ + return build_led_ring( pot.led_ring(), state ); +} + +MidiByteArray MackieMidiBuilder::build_led_ring( const LedRing & led_ring, const ControlState & state ) +{ + // The other way of doing this: + // 0x30 + pot/ring number (0-7) + //, 0x30 + led_ring.ordinal() - 1 + return MidiByteArray ( 3 + // the control type + , midi_pot_id + // the id + , 0x20 + led_ring.id() + // the value + , calculate_pot_value( midi_pot_mode_dot, state ) + ); +} + +MidiByteArray MackieMidiBuilder::build_led( const Button & button, LedState ls ) +{ + return build_led( button.led(), ls ); +} + +MidiByteArray MackieMidiBuilder::build_led( const Led & led, LedState ls ) +{ + MIDI::byte state = 0; + switch ( ls.state() ) + { + case LedState::on: state = 0x7f; break; + case LedState::off: state = 0x00; break; + case LedState::none: state = 0x00; break; // actually, this should never happen. + case LedState::flashing: state = 0x01; break; + } + + return MidiByteArray ( 3 + , midi_button_id + , led.id() + , state + ); +} + +MidiByteArray MackieMidiBuilder::build_fader( const Fader & fader, float pos ) +{ + int posi = int( 0x3fff * pos ); + + return MidiByteArray ( 3 + , midi_fader_id | fader.id() + // lower-order bits + , posi & 0x7f + // higher-order bits + , ( posi >> 7 ) + ); +} + +MidiByteArray MackieMidiBuilder::zero_strip( const Strip & strip ) +{ + Group::Controls::const_iterator it = strip.controls().begin(); + MidiByteArray retval; + for (; it != strip.controls().end(); ++it ) + { + Control & control = **it; + if ( control.accepts_feedback() ) + retval << zero_control( control ); + } + return retval; +} + +MidiByteArray MackieMidiBuilder::zero_control( const Control & control ) +{ + switch( control.type() ) + { + case Control::type_button: + return build_led( (Button&)control, off ); + + case Control::type_led: + return build_led( (Led&)control, off ); + + case Control::type_fader: + return build_fader( (Fader&)control, 0.0 ); + + case Control::type_pot: + return build_led_ring( dynamic_cast<const Pot&>( control ), off ); + + case Control::type_led_ring: + return build_led_ring( dynamic_cast<const LedRing&>( control ), off ); + + default: + ostringstream os; + os << "Unknown control type " << control << " in Strip::zero_control"; + throw MackieControlException( os.str() ); + } +} + +char translate_seven_segment( char achar ) +{ + achar = toupper( achar ); + if ( achar >= 0x40 && achar <= 0x60 ) + return achar - 0x40; + else if ( achar >= 0x21 && achar <= 0x3f ) + return achar; + else + return 0x00; +} + +MidiByteArray MackieMidiBuilder::two_char_display( const std::string & msg, const std::string & dots ) +{ + if ( msg.length() != 2 ) throw MackieControlException( "MackieMidiBuilder::two_char_display: msg must be exactly 2 characters" ); + if ( dots.length() != 2 ) throw MackieControlException( "MackieMidiBuilder::two_char_display: dots must be exactly 2 characters" ); + + MidiByteArray bytes( 5, 0xb0, 0x4a, 0x00, 0x4b, 0x00 ); + + // chars are understood by the surface in right-to-left order + // could also exchange the 0x4a and 0x4b, above + bytes[4] = translate_seven_segment( msg[0] ) + ( dots[0] == '.' ? 0x40 : 0x00 ); + bytes[2] = translate_seven_segment( msg[1] ) + ( dots[1] == '.' ? 0x40 : 0x00 ); + + return bytes; +} + +MidiByteArray MackieMidiBuilder::two_char_display( unsigned int value, const std::string & dots ) +{ + ostringstream os; + os << setfill('0') << setw(2) << value % 100; + return two_char_display( os.str() ); +} diff --git a/libs/surfaces/mackie/mackie_midi_builder.h b/libs/surfaces/mackie/mackie_midi_builder.h new file mode 100644 index 0000000000..f0c3d51a54 --- /dev/null +++ b/libs/surfaces/mackie/mackie_midi_builder.h @@ -0,0 +1,81 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#ifndef mackie_midi_builder_h +#define mackie_midi_builder_h + +#include "midi_byte_array.h" +#include "types.h" + +namespace Mackie +{ + +/** + This knows how to build midi messages given a control and + a state. +*/ +class MackieMidiBuilder +{ +public: + /** + The first byte of a midi message from the surface + will contain one of these, sometimes bitmasked + with the control id + */ + enum midi_types { + midi_fader_id = 0xe0 + , midi_button_id = 0x90 + , midi_pot_id = 0xb0 + }; + + /** + The LED rings have these modes. + */ + enum midi_pot_mode { + midi_pot_mode_dot = 0 + , midi_pot_mode_boost_cut = 1 + , midi_pot_mode_wrap = 2 + , midi_pot_mode_spread = 3 + }; + + MidiByteArray build_led_ring( const Pot & pot, const ControlState & ); + MidiByteArray build_led_ring( const LedRing & led_ring, const ControlState & ); + + MidiByteArray build_led( const Led & led, LedState ls ); + MidiByteArray build_led( const Button & button, LedState ls ); + + MidiByteArray build_fader( const Fader & fader, float pos ); + + /// return bytes that will reset all controls to their zero positions + MidiByteArray zero_strip( const Strip & strip ); + + // provide bytes to zero the given control + MidiByteArray zero_control( const Control & control ); + + // display the first 2 chars of the msg in the 2 char display + // . is appended to the previous character, so A.B. would + // be two characters + MidiByteArray two_char_display( const std::string & msg, const std::string & dots = " " ); + MidiByteArray two_char_display( unsigned int value, const std::string & dots = " " ); + +protected: + static MIDI::byte calculate_pot_value( midi_pot_mode mode, const ControlState & ); +}; + +} + +#endif diff --git a/libs/surfaces/mackie/mackie_port.cc b/libs/surfaces/mackie/mackie_port.cc new file mode 100644 index 0000000000..9bcee638fb --- /dev/null +++ b/libs/surfaces/mackie/mackie_port.cc @@ -0,0 +1,399 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "mackie_port.h" + +#include "mackie_control_exception.h" +#include "mackie_control_protocol.h" +#include "mackie_midi_builder.h" +#include "controls.h" +#include "surface.h" + +#include <midi++/types.h> +#include <midi++/port.h> +#include <sigc++/sigc++.h> +#include <boost/shared_array.hpp> +#include <ardour/configuration.h> + +#include "i18n.h" + +#include <sstream> + +using namespace std; +using namespace Mackie; + +// The MCU sysex header +MidiByteArray mackie_sysex_hdr ( 5, MIDI::sysex, 0x0, 0x0, 0x66, 0x10 ); + +// The MCU extender sysex header +MidiByteArray mackie_sysex_hdr_xt ( 5, MIDI::sysex, 0x0, 0x0, 0x66, 0x11 ); + +MackiePort::MackiePort( MackieControlProtocol & mcp, MIDI::Port & port, int number, port_type_t port_type ) +: SurfacePort( port, number ) +, _mcp( mcp ) +, _port_type( port_type ) +, _emulation( none ) +, _initialising( true ) +{ + //cout << "MackiePort::MackiePort" <<endl; +} + +MackiePort::~MackiePort() +{ + //cout << "~MackiePort" << endl; + close(); + //cout << "~MackiePort finished" << endl; +} + +int MackiePort::strips() const +{ + if ( _port_type == mcu ) + { + switch ( _emulation ) + { + // BCF2000 only has 8 faders, so reserve one for master + case bcf2000: return 7; + case mackie: return 8; + case none: + default: + throw MackieControlException( "MackiePort::strips: don't know what emulation we're using" ); + } + } + else + { + // must be an extender, ie no master fader + return 8; + } +} + +// should really be in MackiePort +void MackiePort::open() +{ + //cout << "MackiePort::open " << *this << endl; + _sysex = port().input()->sysex.connect( ( mem_fun (*this, &MackiePort::handle_midi_sysex) ) ); + + // make sure the device is connected + init(); +} + +void MackiePort::close() +{ + //cout << "MackiePort::close" << endl; + + // disconnect signals + _any.disconnect(); + _sysex.disconnect(); + + // TODO emit a "closing" signal? + //cout << "MackiePort::close finished" << endl; +} + +const MidiByteArray & MackiePort::sysex_hdr() const +{ + switch ( _port_type ) + { + case mcu: return mackie_sysex_hdr; + case ext: return mackie_sysex_hdr_xt; + } + cout << "MackiePort::sysex_hdr _port_type not known" << endl; + return mackie_sysex_hdr; +} + +Control & MackiePort::lookup_control( const MidiByteArray & bytes ) +{ + Control * control = 0; + int midi_id = -1; + MIDI::byte midi_type = bytes[0] & 0xf0; //0b11110000 + switch( midi_type ) + { + // fader + case MackieMidiBuilder::midi_fader_id: + midi_id = bytes[0] & 0x0f; + control = _mcp.surface().faders[midi_id]; + if ( control == 0 ) + { + ostringstream os; + os << "control for fader" << midi_id << " is null"; + throw MackieControlException( os.str() ); + } + break; + + // button + case MackieMidiBuilder::midi_button_id: + midi_id = bytes[1]; + control = _mcp.surface().buttons[midi_id]; + if ( control == 0 ) + { + ostringstream os; + os << "control for button" << midi_id << " is null"; + throw MackieControlException( os.str() ); + } + break; + + // pot (jog wheel, external control) + case MackieMidiBuilder::midi_pot_id: + midi_id = bytes[1] & 0x1f; + control = _mcp.surface().pots[midi_id]; + if ( control == 0 ) + { + ostringstream os; + os << "control for button" << midi_id << " is null"; + throw MackieControlException( os.str() ); + } + break; + + default: + ostringstream os; + os << "Cannot find control for " << bytes; + throw MackieControlException( os.str() ); + } + return *control; +} + +MidiByteArray calculate_challenge_response( MidiByteArray::iterator begin, MidiByteArray::iterator end ) +{ + MidiByteArray l; + back_insert_iterator<MidiByteArray> back ( l ); + copy( begin, end, back ); + + MidiByteArray retval; + + // this is how to calculate the response to the challenge. + // from the Logic docs. + retval << ( 0x7f & ( l[0] + ( l[1] ^ 0xa ) - l[3] ) ); + retval << ( 0x7f & ( ( l[2] >> l[3] ) ^ ( l[0] + l[3] ) ) ); + retval << ( 0x7f & ( l[3] - ( l[2] << 2 ) ^ ( l[0] | l[1] ) ) ); + retval << ( 0x7f & ( l[1] - l[2] + ( 0xf0 ^ ( l[3] << 4 ) ) ) ); + + return retval; +} + +// not used right now +MidiByteArray MackiePort::host_connection_query( MidiByteArray & bytes ) +{ + // handle host connection query + //cout << "host connection query: " << bytes << endl; + + if ( bytes.size() != 18 ) + { + finalise_init( false ); + ostringstream os; + os << "expecting 18 bytes, read " << bytes << " from " << port().name(); + throw MackieControlException( os.str() ); + } + + // build and send host connection reply + MidiByteArray response; + response << 0x02; + copy( bytes.begin() + 6, bytes.begin() + 6 + 7, back_inserter( response ) ); + response << calculate_challenge_response( bytes.begin() + 6 + 7, bytes.begin() + 6 + 7 + 4 ); + return response; +} + +// not used right now +MidiByteArray MackiePort::host_connection_confirmation( const MidiByteArray & bytes ) +{ + //cout << "host_connection_confirmation: " << bytes << endl; + + // decode host connection confirmation + if ( bytes.size() != 14 ) + { + finalise_init( false ); + ostringstream os; + os << "expecting 14 bytes, read " << bytes << " from " << port().name(); + throw MackieControlException( os.str() ); + } + + // send version request + return MidiByteArray( 2, 0x13, 0x00 ); +} + +void MackiePort::probe_emulation( const MidiByteArray & bytes ) +{ + //cout << "MackiePort::probe_emulation: " << bytes.size() << ", " << bytes << endl; + string version_string; + for ( int i = 6; i < 11; ++i ) version_string.append( 1, (char)bytes[i] ); + //cout << "version_string: " << version_string << endl; + + // TODO investigate using serial number. Also, possibly size of bytes might + // give an indication. Also, apparently MCU sends non-documented messages + // sometimes. + if (!_initialising) + { + cout << "MackiePort::probe_emulation out of sequence." << endl; + return; + } + + finalise_init( true ); +} + +void MackiePort::init() +{ + //cout << "MackiePort::init" << endl; + init_mutex.lock(); + _initialising = true; + + //cout << "MackiePort::lock acquired" << endl; + // emit pre-init signal + init_event(); + + // kick off initialisation. See docs in header file for init() + + // bypass the init sequence because sometimes the first + // message doesn't get to the unit, and there's no way + // to do a timed lock in Glib. + //write_sysex ( MidiByteArray ( 2, 0x13, 0x00 ) ); + + finalise_init( true ); +} + +void MackiePort::finalise_init( bool yn ) +{ + //cout << "MackiePort::finalise_init" << endl; + bool emulation_ok = false; + + // probing doesn't work very well, so just use a config variable + // to set the emulation mode + if ( _emulation == none ) + { + if ( ARDOUR::Config->get_mackie_emulation() == "bcf" ) + { + _emulation = bcf2000; + emulation_ok = true; + } + else if ( ARDOUR::Config->get_mackie_emulation() == "mcu" ) + { + _emulation = mackie; + emulation_ok = true; + } + else + { + cout << "unknown mackie emulation: " << ARDOUR::Config->get_mackie_emulation() << endl; + emulation_ok = false; + } + } + + yn = yn && emulation_ok; + + SurfacePort::active( yn ); + + if ( yn ) + { + active_event(); + + // start handling messages from controls + _any = port().input()->any.connect( ( mem_fun (*this, &MackiePort::handle_midi_any) ) ); + } + _initialising = false; + init_cond.signal(); + init_mutex.unlock(); +} + +bool MackiePort::wait_for_init() +{ + Glib::Mutex::Lock lock( init_mutex ); + while ( _initialising ) + { + //cout << "MackiePort::wait_for_active waiting" << endl; + init_cond.wait( init_mutex ); + //cout << "MackiePort::wait_for_active released" << endl; + } + //cout << "MackiePort::wait_for_active returning" << endl; + return SurfacePort::active(); +} + +void MackiePort::handle_midi_sysex (MIDI::Parser & parser, MIDI::byte * raw_bytes, size_t count ) +{ + MidiByteArray bytes( count, raw_bytes ); + //cout << "handle_midi_sysex: " << bytes << endl; + switch( bytes[5] ) + { + case 0x01: + // not used right now + write_sysex( host_connection_query( bytes ) ); + break; + case 0x03: + // not used right now + write_sysex( host_connection_confirmation( bytes ) ); + break; + case 0x04: + inactive_event(); + cout << "host connection error" << bytes << endl; + break; + case 0x14: + probe_emulation( bytes ); + break; + default: + cout << "unknown sysex: " << bytes << endl; + } +} + +// converts midi messages into control_event signals +void MackiePort::handle_midi_any (MIDI::Parser & parser, MIDI::byte * raw_bytes, size_t count ) +{ + MidiByteArray bytes( count, raw_bytes ); + try + { + // ignore sysex messages + if ( bytes[0] == MIDI::sysex ) return; + + Control & control = lookup_control( bytes ); + + // This handles incoming bytes. Outgoing bytes + // are sent by the signal handlers. + switch ( control.type() ) + { + // fader + case Control::type_fader: + { + // for a BCF2000, max is 7f for high-order byte and 0x70 for low-order byte + // According to the Logic docs, these should both be 0x7f. + // Although it does mention something about only the top-order + // 10 bits out of 14 being used + int midi_pos = ( bytes[2] << 7 ) + bytes[1]; + control_event( *this, control, float(midi_pos) / float(0x3fff) ); + } + break; + + // button + case Control::type_button: + control_event( *this, control, bytes[2] == 0x7f ? press : release ); + break; + + // pot (jog wheel, external control) + case Control::type_pot: + { + ControlState state; + + // bytes[2] & 0b01000000 (0x40) give sign + int sign = ( bytes[2] & 0x40 ) == 0 ? 1 : -1; + // bytes[2] & 0b00111111 (0x3f) gives delta + state.ticks = ( bytes[2] & 0x3f) * sign; + state.delta = float( state.ticks ) / float( 0x3f ); + + control_event( *this, control, state ); + } + break; + default: + cerr << "Do not understand control type " << control; + } + } + catch( MackieControlException & e ) + { + //cout << bytes << ' ' << e.what() << endl; + } +} diff --git a/libs/surfaces/mackie/mackie_port.h b/libs/surfaces/mackie/mackie_port.h new file mode 100644 index 0000000000..2ad5cf6154 --- /dev/null +++ b/libs/surfaces/mackie/mackie_port.h @@ -0,0 +1,122 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#ifndef mackie_port_h +#define mackie_port_h + +#include "surface_port.h" + +#include <midi++/types.h> +#include <sigc++/signal.h> +#include <sigc++/connection.h> + +#include <glibmm/thread.h> + +#include "midi_byte_array.h" +#include "types.h" + +namespace MIDI { + class Port; + class Parser; +} + +class MackieControlProtocol; + +namespace Mackie +{ + +class MackiePort : public SurfacePort +{ +public: + enum port_type_t { mcu, ext }; + enum emulation_t { none, mackie, bcf2000 }; + + MackiePort( MackieControlProtocol & mcp, MIDI::Port & port, int number, port_type_t = mcu ); + ~MackiePort(); + + virtual void open(); + virtual void close(); + + /// MCU and extenders have different sysex headers + virtual const MidiByteArray & sysex_hdr() const; + + /// Handle device initialisation + void handle_midi_sysex( MIDI::Parser &, MIDI::byte *, size_t ); + + /// Handle all control messags + void handle_midi_any( MIDI::Parser &, MIDI::byte *, size_t ); + + Control & lookup_control( const MidiByteArray & bytes ); + + /// return the number of strips associated with this port + virtual int strips() const; + + /// Block until the port has finished initialising, and then return + /// whether the intialisation succeeded + bool wait_for_init(); + + emulation_t emulation() const { return _emulation; } + +protected: + /** + The initialisation sequence is fairly complex. First a lock is acquired + so that a condition can be used to signal the end of the init process. + Then a sysex is sent to the device. The response to the sysex + is handled by a switch in handle_midi_sysex which calls one of the + other methods. + + However, windows DAWs ignore the documented init sequence and so we + do too. Thanks to Essox for helping with this. + + So we use the version firmware to figure out what device is on + the other end of the cable. + */ + void init(); + + /** + Once the device is initialised, finalise_init(true) is called, which + releases the lock and signals the condition, and starts handling incoming + messages. finalise_init(false) will also release the lock but doesn't + start handling messages. + */ + void finalise_init( bool yn ); + + MidiByteArray host_connection_query( MidiByteArray & bytes ); + MidiByteArray host_connection_confirmation( const MidiByteArray & bytes ); + + /** + Will set _emulation to what it thinks is correct, based + on responses from the device. Or get/set parameters. Or + environment variables. Or existence of a file. + */ + void probe_emulation( const MidiByteArray & bytes ); + +private: + MackieControlProtocol & _mcp; + port_type_t _port_type; + sigc::connection _any; + sigc::connection _sysex; + emulation_t _emulation; + + bool _initialising; + Glib::Cond init_cond; + Glib::Mutex init_mutex; +}; + +} + +#endif diff --git a/libs/surfaces/mackie/mackie_surface.cc b/libs/surfaces/mackie/mackie_surface.cc new file mode 100644 index 0000000000..b527f710cc --- /dev/null +++ b/libs/surfaces/mackie/mackie_surface.cc @@ -0,0 +1,1504 @@ +/* + Generated by scripts/generate-surface.rb +*/ + +#include "mackie_surface.h" + +#include "controls.h" +#include "mackie_button_handler.h" + +using namespace Mackie; + +void Mackie::MackieSurface::init_controls() +{ + // intialise groups and strips + Group * group = 0; + + // make sure there are enough strips + strips.resize( 8 ); + + group = new Group ( "user" ); + groups["user"] = group; + + group = new Group ( "assignment" ); + groups["assignment"] = group; + + group = new Group ( "none" ); + groups["none"] = group; + + group = new MasterStrip ( "master", 0 ); + groups["master"] = group; + strips[0] = dynamic_cast<Strip*>( group ); + + group = new Strip ( "strip_1", 0 ); + groups["strip_1"] = group; + strips[0] = dynamic_cast<Strip*>( group ); + + group = new Group ( "cursor" ); + groups["cursor"] = group; + + group = new Strip ( "strip_2", 1 ); + groups["strip_2"] = group; + strips[1] = dynamic_cast<Strip*>( group ); + + group = new Group ( "functions" ); + groups["functions"] = group; + + group = new Group ( "automation" ); + groups["automation"] = group; + + group = new Strip ( "strip_3", 2 ); + groups["strip_3"] = group; + strips[2] = dynamic_cast<Strip*>( group ); + + group = new Group ( "display" ); + groups["display"] = group; + + group = new Strip ( "strip_4", 3 ); + groups["strip_4"] = group; + strips[3] = dynamic_cast<Strip*>( group ); + + group = new Strip ( "strip_5", 4 ); + groups["strip_5"] = group; + strips[4] = dynamic_cast<Strip*>( group ); + + group = new Strip ( "strip_6", 5 ); + groups["strip_6"] = group; + strips[5] = dynamic_cast<Strip*>( group ); + + group = new Group ( "transport" ); + groups["transport"] = group; + + group = new Strip ( "strip_7", 6 ); + groups["strip_7"] = group; + strips[6] = dynamic_cast<Strip*>( group ); + + group = new Group ( "modifiers" ); + groups["modifiers"] = group; + + group = new Group ( "bank" ); + groups["bank"] = group; + + group = new Strip ( "strip_8", 7 ); + groups["strip_8"] = group; + strips[7] = dynamic_cast<Strip*>( group ); + + + // initialise controls + Control * control = 0; + + group = groups["strip_1"]; + control = new Fader ( 0, 1, "gain", *group ); + faders[0x00] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Fader ( 1, 2, "gain", *group ); + faders[0x01] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Fader ( 2, 3, "gain", *group ); + faders[0x02] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Fader ( 3, 4, "gain", *group ); + faders[0x03] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Fader ( 4, 5, "gain", *group ); + faders[0x04] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Fader ( 5, 6, "gain", *group ); + faders[0x05] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Fader ( 6, 7, "gain", *group ); + faders[0x06] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_8"]; + control = new Fader ( 7, 8, "gain", *group ); + faders[0x07] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["master"]; + control = new Fader ( 8, 1, "gain", *group ); + faders[0x08] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_1"]; + control = new Pot ( 16, 1, "vpot", *group ); + pots[0x10] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Pot ( 17, 2, "vpot", *group ); + pots[0x11] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Pot ( 18, 3, "vpot", *group ); + pots[0x12] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Pot ( 19, 4, "vpot", *group ); + pots[0x13] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Pot ( 20, 5, "vpot", *group ); + pots[0x14] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Pot ( 21, 6, "vpot", *group ); + pots[0x15] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Pot ( 22, 7, "vpot", *group ); + pots[0x16] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_8"]; + control = new Pot ( 23, 8, "vpot", *group ); + pots[0x17] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["none"]; + control = new Pot ( 60, 1, "jog", *group ); + pots[0x3c] = control; + controls.push_back( control ); + controls_by_name["jog"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Pot ( 46, 1, "external", *group ); + pots[0x2e] = control; + controls.push_back( control ); + controls_by_name["external"] = control; + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 0, 1, "recenable", *group ); + buttons[0x00] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 1, 2, "recenable", *group ); + buttons[0x01] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 2, 3, "recenable", *group ); + buttons[0x02] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 3, 4, "recenable", *group ); + buttons[0x03] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 4, 5, "recenable", *group ); + buttons[0x04] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 5, 6, "recenable", *group ); + buttons[0x05] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 6, 7, "recenable", *group ); + buttons[0x06] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_8"]; + control = new Button ( 7, 8, "recenable", *group ); + buttons[0x07] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 8, 1, "solo", *group ); + buttons[0x08] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 9, 2, "solo", *group ); + buttons[0x09] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 10, 3, "solo", *group ); + buttons[0x0a] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 11, 4, "solo", *group ); + buttons[0x0b] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 12, 5, "solo", *group ); + buttons[0x0c] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 13, 6, "solo", *group ); + buttons[0x0d] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 14, 7, "solo", *group ); + buttons[0x0e] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_8"]; + control = new Button ( 15, 8, "solo", *group ); + buttons[0x0f] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 16, 1, "mute", *group ); + buttons[0x10] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 17, 2, "mute", *group ); + buttons[0x11] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 18, 3, "mute", *group ); + buttons[0x12] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 19, 4, "mute", *group ); + buttons[0x13] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 20, 5, "mute", *group ); + buttons[0x14] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 21, 6, "mute", *group ); + buttons[0x15] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 22, 7, "mute", *group ); + buttons[0x16] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_8"]; + control = new Button ( 23, 8, "mute", *group ); + buttons[0x17] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 24, 1, "select", *group ); + buttons[0x18] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 25, 2, "select", *group ); + buttons[0x19] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 26, 3, "select", *group ); + buttons[0x1a] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 27, 4, "select", *group ); + buttons[0x1b] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 28, 5, "select", *group ); + buttons[0x1c] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 29, 6, "select", *group ); + buttons[0x1d] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 30, 7, "select", *group ); + buttons[0x1e] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_8"]; + control = new Button ( 31, 8, "select", *group ); + buttons[0x1f] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 32, 1, "vselect", *group ); + buttons[0x20] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 33, 2, "vselect", *group ); + buttons[0x21] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 34, 3, "vselect", *group ); + buttons[0x22] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 35, 4, "vselect", *group ); + buttons[0x23] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 36, 5, "vselect", *group ); + buttons[0x24] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 37, 6, "vselect", *group ); + buttons[0x25] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 38, 7, "vselect", *group ); + buttons[0x26] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_8"]; + control = new Button ( 39, 8, "vselect", *group ); + buttons[0x27] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 40, 1, "io", *group ); + buttons[0x28] = control; + controls.push_back( control ); + controls_by_name["io"] = control; + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 41, 1, "sends", *group ); + buttons[0x29] = control; + controls.push_back( control ); + controls_by_name["sends"] = control; + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 42, 1, "pan", *group ); + buttons[0x2a] = control; + controls.push_back( control ); + controls_by_name["pan"] = control; + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 43, 1, "plugin", *group ); + buttons[0x2b] = control; + controls.push_back( control ); + controls_by_name["plugin"] = control; + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 44, 1, "eq", *group ); + buttons[0x2c] = control; + controls.push_back( control ); + controls_by_name["eq"] = control; + group->add( *control ); + + group = groups["assignment"]; + control = new Button ( 45, 1, "dyn", *group ); + buttons[0x2d] = control; + controls.push_back( control ); + controls_by_name["dyn"] = control; + group->add( *control ); + + group = groups["bank"]; + control = new Button ( 46, 1, "left", *group ); + buttons[0x2e] = control; + controls.push_back( control ); + controls_by_name["left"] = control; + group->add( *control ); + + group = groups["bank"]; + control = new Button ( 47, 1, "right", *group ); + buttons[0x2f] = control; + controls.push_back( control ); + controls_by_name["right"] = control; + group->add( *control ); + + group = groups["bank"]; + control = new Button ( 48, 1, "channel_left", *group ); + buttons[0x30] = control; + controls.push_back( control ); + controls_by_name["channel_left"] = control; + group->add( *control ); + + group = groups["bank"]; + control = new Button ( 49, 1, "channel_right", *group ); + buttons[0x31] = control; + controls.push_back( control ); + controls_by_name["channel_right"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 50, 1, "flip", *group ); + buttons[0x32] = control; + controls.push_back( control ); + controls_by_name["flip"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 51, 1, "edit", *group ); + buttons[0x33] = control; + controls.push_back( control ); + controls_by_name["edit"] = control; + group->add( *control ); + + group = groups["display"]; + control = new Button ( 52, 1, "name_value", *group ); + buttons[0x34] = control; + controls.push_back( control ); + controls_by_name["name_value"] = control; + group->add( *control ); + + group = groups["display"]; + control = new Button ( 53, 1, "smpte_beats", *group ); + buttons[0x35] = control; + controls.push_back( control ); + controls_by_name["smpte_beats"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 54, 1, "F1", *group ); + buttons[0x36] = control; + controls.push_back( control ); + controls_by_name["F1"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 55, 1, "F2", *group ); + buttons[0x37] = control; + controls.push_back( control ); + controls_by_name["F2"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 56, 1, "F3", *group ); + buttons[0x38] = control; + controls.push_back( control ); + controls_by_name["F3"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 57, 1, "F4", *group ); + buttons[0x39] = control; + controls.push_back( control ); + controls_by_name["F4"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 58, 1, "F5", *group ); + buttons[0x3a] = control; + controls.push_back( control ); + controls_by_name["F5"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 59, 1, "F6", *group ); + buttons[0x3b] = control; + controls.push_back( control ); + controls_by_name["F6"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 60, 1, "F7", *group ); + buttons[0x3c] = control; + controls.push_back( control ); + controls_by_name["F7"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 61, 1, "F8", *group ); + buttons[0x3d] = control; + controls.push_back( control ); + controls_by_name["F8"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 62, 1, "F9", *group ); + buttons[0x3e] = control; + controls.push_back( control ); + controls_by_name["F9"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 63, 1, "F10", *group ); + buttons[0x3f] = control; + controls.push_back( control ); + controls_by_name["F10"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 64, 1, "F11", *group ); + buttons[0x40] = control; + controls.push_back( control ); + controls_by_name["F11"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 65, 1, "F12", *group ); + buttons[0x41] = control; + controls.push_back( control ); + controls_by_name["F12"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 66, 1, "F13", *group ); + buttons[0x42] = control; + controls.push_back( control ); + controls_by_name["F13"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 67, 1, "F14", *group ); + buttons[0x43] = control; + controls.push_back( control ); + controls_by_name["F14"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 68, 1, "F15", *group ); + buttons[0x44] = control; + controls.push_back( control ); + controls_by_name["F15"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 69, 1, "F16", *group ); + buttons[0x45] = control; + controls.push_back( control ); + controls_by_name["F16"] = control; + group->add( *control ); + + group = groups["modifiers"]; + control = new Button ( 70, 1, "shift", *group ); + buttons[0x46] = control; + controls.push_back( control ); + controls_by_name["shift"] = control; + group->add( *control ); + + group = groups["modifiers"]; + control = new Button ( 71, 1, "option", *group ); + buttons[0x47] = control; + controls.push_back( control ); + controls_by_name["option"] = control; + group->add( *control ); + + group = groups["modifiers"]; + control = new Button ( 72, 1, "control", *group ); + buttons[0x48] = control; + controls.push_back( control ); + controls_by_name["control"] = control; + group->add( *control ); + + group = groups["modifiers"]; + control = new Button ( 73, 1, "cmd_alt", *group ); + buttons[0x49] = control; + controls.push_back( control ); + controls_by_name["cmd_alt"] = control; + group->add( *control ); + + group = groups["automation"]; + control = new Button ( 74, 1, "on", *group ); + buttons[0x4a] = control; + controls.push_back( control ); + controls_by_name["on"] = control; + group->add( *control ); + + group = groups["automation"]; + control = new Button ( 75, 1, "rec_ready", *group ); + buttons[0x4b] = control; + controls.push_back( control ); + controls_by_name["rec_ready"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 76, 1, "undo", *group ); + buttons[0x4c] = control; + controls.push_back( control ); + controls_by_name["undo"] = control; + group->add( *control ); + + group = groups["automation"]; + control = new Button ( 77, 1, "snapshot", *group ); + buttons[0x4d] = control; + controls.push_back( control ); + controls_by_name["snapshot"] = control; + group->add( *control ); + + group = groups["automation"]; + control = new Button ( 78, 1, "touch", *group ); + buttons[0x4e] = control; + controls.push_back( control ); + controls_by_name["touch"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 79, 1, "redo", *group ); + buttons[0x4f] = control; + controls.push_back( control ); + controls_by_name["redo"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 80, 1, "marker", *group ); + buttons[0x50] = control; + controls.push_back( control ); + controls_by_name["marker"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 81, 1, "enter", *group ); + buttons[0x51] = control; + controls.push_back( control ); + controls_by_name["enter"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 82, 1, "cancel", *group ); + buttons[0x52] = control; + controls.push_back( control ); + controls_by_name["cancel"] = control; + group->add( *control ); + + group = groups["functions"]; + control = new Button ( 83, 1, "mixer", *group ); + buttons[0x53] = control; + controls.push_back( control ); + controls_by_name["mixer"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 84, 1, "frm_left", *group ); + buttons[0x54] = control; + controls.push_back( control ); + controls_by_name["frm_left"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 85, 1, "frm_right", *group ); + buttons[0x55] = control; + controls.push_back( control ); + controls_by_name["frm_right"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 86, 1, "loop", *group ); + buttons[0x56] = control; + controls.push_back( control ); + controls_by_name["loop"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 87, 1, "punch_in", *group ); + buttons[0x57] = control; + controls.push_back( control ); + controls_by_name["punch_in"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 88, 1, "punch_out", *group ); + buttons[0x58] = control; + controls.push_back( control ); + controls_by_name["punch_out"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 89, 1, "home", *group ); + buttons[0x59] = control; + controls.push_back( control ); + controls_by_name["home"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 90, 1, "end", *group ); + buttons[0x5a] = control; + controls.push_back( control ); + controls_by_name["end"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 91, 1, "rewind", *group ); + buttons[0x5b] = control; + controls.push_back( control ); + controls_by_name["rewind"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 92, 1, "ffwd", *group ); + buttons[0x5c] = control; + controls.push_back( control ); + controls_by_name["ffwd"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 93, 1, "stop", *group ); + buttons[0x5d] = control; + controls.push_back( control ); + controls_by_name["stop"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 94, 1, "play", *group ); + buttons[0x5e] = control; + controls.push_back( control ); + controls_by_name["play"] = control; + group->add( *control ); + + group = groups["transport"]; + control = new Button ( 95, 1, "record", *group ); + buttons[0x5f] = control; + controls.push_back( control ); + controls_by_name["record"] = control; + group->add( *control ); + + group = groups["cursor"]; + control = new Button ( 96, 1, "cursor_up", *group ); + buttons[0x60] = control; + controls.push_back( control ); + controls_by_name["cursor_up"] = control; + group->add( *control ); + + group = groups["cursor"]; + control = new Button ( 97, 1, "cursor_down", *group ); + buttons[0x61] = control; + controls.push_back( control ); + controls_by_name["cursor_down"] = control; + group->add( *control ); + + group = groups["cursor"]; + control = new Button ( 98, 1, "cursor_left", *group ); + buttons[0x62] = control; + controls.push_back( control ); + controls_by_name["cursor_left"] = control; + group->add( *control ); + + group = groups["cursor"]; + control = new Button ( 99, 1, "cursor_right", *group ); + buttons[0x63] = control; + controls.push_back( control ); + controls_by_name["cursor_right"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 100, 1, "zoom", *group ); + buttons[0x64] = control; + controls.push_back( control ); + controls_by_name["zoom"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Button ( 101, 1, "scrub", *group ); + buttons[0x65] = control; + controls.push_back( control ); + controls_by_name["scrub"] = control; + group->add( *control ); + + group = groups["user"]; + control = new Button ( 102, 1, "user_a", *group ); + buttons[0x66] = control; + controls.push_back( control ); + controls_by_name["user_a"] = control; + group->add( *control ); + + group = groups["user"]; + control = new Button ( 103, 1, "user_b", *group ); + buttons[0x67] = control; + controls.push_back( control ); + controls_by_name["user_b"] = control; + group->add( *control ); + + group = groups["strip_1"]; + control = new Button ( 104, 1, "fader_touch", *group ); + buttons[0x68] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_2"]; + control = new Button ( 105, 2, "fader_touch", *group ); + buttons[0x69] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_3"]; + control = new Button ( 106, 3, "fader_touch", *group ); + buttons[0x6a] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_4"]; + control = new Button ( 107, 4, "fader_touch", *group ); + buttons[0x6b] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_5"]; + control = new Button ( 108, 5, "fader_touch", *group ); + buttons[0x6c] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_6"]; + control = new Button ( 109, 6, "fader_touch", *group ); + buttons[0x6d] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_7"]; + control = new Button ( 110, 7, "fader_touch", *group ); + buttons[0x6e] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["strip_8"]; + control = new Button ( 111, 8, "fader_touch", *group ); + buttons[0x6f] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["master"]; + control = new Button ( 112, 1, "fader_touch", *group ); + buttons[0x70] = control; + controls.push_back( control ); + group->add( *control ); + + group = groups["none"]; + control = new Led ( 113, 1, "smpte", *group ); + leds[0x71] = control; + controls.push_back( control ); + controls_by_name["smpte"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Led ( 114, 1, "beats", *group ); + leds[0x72] = control; + controls.push_back( control ); + controls_by_name["beats"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Led ( 115, 1, "solo", *group ); + leds[0x73] = control; + controls.push_back( control ); + controls_by_name["solo"] = control; + group->add( *control ); + + group = groups["none"]; + control = new Led ( 118, 1, "relay_click", *group ); + leds[0x76] = control; + controls.push_back( control ); + controls_by_name["relay_click"] = control; + group->add( *control ); + +} + +void Mackie::MackieSurface::handle_button( MackieButtonHandler & mbh, ButtonState bs, Button & button ) +{ + if ( bs != press && bs != release ) + { + mbh.update_led( button, none ); + return; + } + + LedState ls; + switch ( button.id() ) + { + + case 0x28: // io + switch ( bs ) { + case press: ls = mbh.io_press( button ); break; + case release: ls = mbh.io_release( button ); break; + case neither: break; + } + break; + + case 0x29: // sends + switch ( bs ) { + case press: ls = mbh.sends_press( button ); break; + case release: ls = mbh.sends_release( button ); break; + case neither: break; + } + break; + + case 0x2a: // pan + switch ( bs ) { + case press: ls = mbh.pan_press( button ); break; + case release: ls = mbh.pan_release( button ); break; + case neither: break; + } + break; + + case 0x2b: // plugin + switch ( bs ) { + case press: ls = mbh.plugin_press( button ); break; + case release: ls = mbh.plugin_release( button ); break; + case neither: break; + } + break; + + case 0x2c: // eq + switch ( bs ) { + case press: ls = mbh.eq_press( button ); break; + case release: ls = mbh.eq_release( button ); break; + case neither: break; + } + break; + + case 0x2d: // dyn + switch ( bs ) { + case press: ls = mbh.dyn_press( button ); break; + case release: ls = mbh.dyn_release( button ); break; + case neither: break; + } + break; + + case 0x2e: // left + switch ( bs ) { + case press: ls = mbh.left_press( button ); break; + case release: ls = mbh.left_release( button ); break; + case neither: break; + } + break; + + case 0x2f: // right + switch ( bs ) { + case press: ls = mbh.right_press( button ); break; + case release: ls = mbh.right_release( button ); break; + case neither: break; + } + break; + + case 0x30: // channel_left + switch ( bs ) { + case press: ls = mbh.channel_left_press( button ); break; + case release: ls = mbh.channel_left_release( button ); break; + case neither: break; + } + break; + + case 0x31: // channel_right + switch ( bs ) { + case press: ls = mbh.channel_right_press( button ); break; + case release: ls = mbh.channel_right_release( button ); break; + case neither: break; + } + break; + + case 0x32: // flip + switch ( bs ) { + case press: ls = mbh.flip_press( button ); break; + case release: ls = mbh.flip_release( button ); break; + case neither: break; + } + break; + + case 0x33: // edit + switch ( bs ) { + case press: ls = mbh.edit_press( button ); break; + case release: ls = mbh.edit_release( button ); break; + case neither: break; + } + break; + + case 0x34: // name_value + switch ( bs ) { + case press: ls = mbh.name_value_press( button ); break; + case release: ls = mbh.name_value_release( button ); break; + case neither: break; + } + break; + + case 0x35: // smpte_beats + switch ( bs ) { + case press: ls = mbh.smpte_beats_press( button ); break; + case release: ls = mbh.smpte_beats_release( button ); break; + case neither: break; + } + break; + + case 0x36: // F1 + switch ( bs ) { + case press: ls = mbh.F1_press( button ); break; + case release: ls = mbh.F1_release( button ); break; + case neither: break; + } + break; + + case 0x37: // F2 + switch ( bs ) { + case press: ls = mbh.F2_press( button ); break; + case release: ls = mbh.F2_release( button ); break; + case neither: break; + } + break; + + case 0x38: // F3 + switch ( bs ) { + case press: ls = mbh.F3_press( button ); break; + case release: ls = mbh.F3_release( button ); break; + case neither: break; + } + break; + + case 0x39: // F4 + switch ( bs ) { + case press: ls = mbh.F4_press( button ); break; + case release: ls = mbh.F4_release( button ); break; + case neither: break; + } + break; + + case 0x3a: // F5 + switch ( bs ) { + case press: ls = mbh.F5_press( button ); break; + case release: ls = mbh.F5_release( button ); break; + case neither: break; + } + break; + + case 0x3b: // F6 + switch ( bs ) { + case press: ls = mbh.F6_press( button ); break; + case release: ls = mbh.F6_release( button ); break; + case neither: break; + } + break; + + case 0x3c: // F7 + switch ( bs ) { + case press: ls = mbh.F7_press( button ); break; + case release: ls = mbh.F7_release( button ); break; + case neither: break; + } + break; + + case 0x3d: // F8 + switch ( bs ) { + case press: ls = mbh.F8_press( button ); break; + case release: ls = mbh.F8_release( button ); break; + case neither: break; + } + break; + + case 0x3e: // F9 + switch ( bs ) { + case press: ls = mbh.F9_press( button ); break; + case release: ls = mbh.F9_release( button ); break; + case neither: break; + } + break; + + case 0x3f: // F10 + switch ( bs ) { + case press: ls = mbh.F10_press( button ); break; + case release: ls = mbh.F10_release( button ); break; + case neither: break; + } + break; + + case 0x40: // F11 + switch ( bs ) { + case press: ls = mbh.F11_press( button ); break; + case release: ls = mbh.F11_release( button ); break; + case neither: break; + } + break; + + case 0x41: // F12 + switch ( bs ) { + case press: ls = mbh.F12_press( button ); break; + case release: ls = mbh.F12_release( button ); break; + case neither: break; + } + break; + + case 0x42: // F13 + switch ( bs ) { + case press: ls = mbh.F13_press( button ); break; + case release: ls = mbh.F13_release( button ); break; + case neither: break; + } + break; + + case 0x43: // F14 + switch ( bs ) { + case press: ls = mbh.F14_press( button ); break; + case release: ls = mbh.F14_release( button ); break; + case neither: break; + } + break; + + case 0x44: // F15 + switch ( bs ) { + case press: ls = mbh.F15_press( button ); break; + case release: ls = mbh.F15_release( button ); break; + case neither: break; + } + break; + + case 0x45: // F16 + switch ( bs ) { + case press: ls = mbh.F16_press( button ); break; + case release: ls = mbh.F16_release( button ); break; + case neither: break; + } + break; + + case 0x46: // shift + switch ( bs ) { + case press: ls = mbh.shift_press( button ); break; + case release: ls = mbh.shift_release( button ); break; + case neither: break; + } + break; + + case 0x47: // option + switch ( bs ) { + case press: ls = mbh.option_press( button ); break; + case release: ls = mbh.option_release( button ); break; + case neither: break; + } + break; + + case 0x48: // control + switch ( bs ) { + case press: ls = mbh.control_press( button ); break; + case release: ls = mbh.control_release( button ); break; + case neither: break; + } + break; + + case 0x49: // cmd_alt + switch ( bs ) { + case press: ls = mbh.cmd_alt_press( button ); break; + case release: ls = mbh.cmd_alt_release( button ); break; + case neither: break; + } + break; + + case 0x4a: // on + switch ( bs ) { + case press: ls = mbh.on_press( button ); break; + case release: ls = mbh.on_release( button ); break; + case neither: break; + } + break; + + case 0x4b: // rec_ready + switch ( bs ) { + case press: ls = mbh.rec_ready_press( button ); break; + case release: ls = mbh.rec_ready_release( button ); break; + case neither: break; + } + break; + + case 0x4c: // undo + switch ( bs ) { + case press: ls = mbh.undo_press( button ); break; + case release: ls = mbh.undo_release( button ); break; + case neither: break; + } + break; + + case 0x4d: // snapshot + switch ( bs ) { + case press: ls = mbh.snapshot_press( button ); break; + case release: ls = mbh.snapshot_release( button ); break; + case neither: break; + } + break; + + case 0x4e: // touch + switch ( bs ) { + case press: ls = mbh.touch_press( button ); break; + case release: ls = mbh.touch_release( button ); break; + case neither: break; + } + break; + + case 0x4f: // redo + switch ( bs ) { + case press: ls = mbh.redo_press( button ); break; + case release: ls = mbh.redo_release( button ); break; + case neither: break; + } + break; + + case 0x50: // marker + switch ( bs ) { + case press: ls = mbh.marker_press( button ); break; + case release: ls = mbh.marker_release( button ); break; + case neither: break; + } + break; + + case 0x51: // enter + switch ( bs ) { + case press: ls = mbh.enter_press( button ); break; + case release: ls = mbh.enter_release( button ); break; + case neither: break; + } + break; + + case 0x52: // cancel + switch ( bs ) { + case press: ls = mbh.cancel_press( button ); break; + case release: ls = mbh.cancel_release( button ); break; + case neither: break; + } + break; + + case 0x53: // mixer + switch ( bs ) { + case press: ls = mbh.mixer_press( button ); break; + case release: ls = mbh.mixer_release( button ); break; + case neither: break; + } + break; + + case 0x54: // frm_left + switch ( bs ) { + case press: ls = mbh.frm_left_press( button ); break; + case release: ls = mbh.frm_left_release( button ); break; + case neither: break; + } + break; + + case 0x55: // frm_right + switch ( bs ) { + case press: ls = mbh.frm_right_press( button ); break; + case release: ls = mbh.frm_right_release( button ); break; + case neither: break; + } + break; + + case 0x56: // loop + switch ( bs ) { + case press: ls = mbh.loop_press( button ); break; + case release: ls = mbh.loop_release( button ); break; + case neither: break; + } + break; + + case 0x57: // punch_in + switch ( bs ) { + case press: ls = mbh.punch_in_press( button ); break; + case release: ls = mbh.punch_in_release( button ); break; + case neither: break; + } + break; + + case 0x58: // punch_out + switch ( bs ) { + case press: ls = mbh.punch_out_press( button ); break; + case release: ls = mbh.punch_out_release( button ); break; + case neither: break; + } + break; + + case 0x59: // home + switch ( bs ) { + case press: ls = mbh.home_press( button ); break; + case release: ls = mbh.home_release( button ); break; + case neither: break; + } + break; + + case 0x5a: // end + switch ( bs ) { + case press: ls = mbh.end_press( button ); break; + case release: ls = mbh.end_release( button ); break; + case neither: break; + } + break; + + case 0x5b: // rewind + switch ( bs ) { + case press: ls = mbh.rewind_press( button ); break; + case release: ls = mbh.rewind_release( button ); break; + case neither: break; + } + break; + + case 0x5c: // ffwd + switch ( bs ) { + case press: ls = mbh.ffwd_press( button ); break; + case release: ls = mbh.ffwd_release( button ); break; + case neither: break; + } + break; + + case 0x5d: // stop + switch ( bs ) { + case press: ls = mbh.stop_press( button ); break; + case release: ls = mbh.stop_release( button ); break; + case neither: break; + } + break; + + case 0x5e: // play + switch ( bs ) { + case press: ls = mbh.play_press( button ); break; + case release: ls = mbh.play_release( button ); break; + case neither: break; + } + break; + + case 0x5f: // record + switch ( bs ) { + case press: ls = mbh.record_press( button ); break; + case release: ls = mbh.record_release( button ); break; + case neither: break; + } + break; + + case 0x60: // cursor_up + switch ( bs ) { + case press: ls = mbh.cursor_up_press( button ); break; + case release: ls = mbh.cursor_up_release( button ); break; + case neither: break; + } + break; + + case 0x61: // cursor_down + switch ( bs ) { + case press: ls = mbh.cursor_down_press( button ); break; + case release: ls = mbh.cursor_down_release( button ); break; + case neither: break; + } + break; + + case 0x62: // cursor_left + switch ( bs ) { + case press: ls = mbh.cursor_left_press( button ); break; + case release: ls = mbh.cursor_left_release( button ); break; + case neither: break; + } + break; + + case 0x63: // cursor_right + switch ( bs ) { + case press: ls = mbh.cursor_right_press( button ); break; + case release: ls = mbh.cursor_right_release( button ); break; + case neither: break; + } + break; + + case 0x64: // zoom + switch ( bs ) { + case press: ls = mbh.zoom_press( button ); break; + case release: ls = mbh.zoom_release( button ); break; + case neither: break; + } + break; + + case 0x65: // scrub + switch ( bs ) { + case press: ls = mbh.scrub_press( button ); break; + case release: ls = mbh.scrub_release( button ); break; + case neither: break; + } + break; + + case 0x66: // user_a + switch ( bs ) { + case press: ls = mbh.user_a_press( button ); break; + case release: ls = mbh.user_a_release( button ); break; + case neither: break; + } + break; + + case 0x67: // user_b + switch ( bs ) { + case press: ls = mbh.user_b_press( button ); break; + case release: ls = mbh.user_b_release( button ); break; + case neither: break; + } + break; + + } + mbh.update_led( button, ls ); +} diff --git a/libs/surfaces/mackie/mackie_surface.h b/libs/surfaces/mackie/mackie_surface.h new file mode 100644 index 0000000000..735cbc5851 --- /dev/null +++ b/libs/surfaces/mackie/mackie_surface.h @@ -0,0 +1,27 @@ +#ifndef mackie_surface_mackie_h +#define mackie_surface_mackie_h +/* + Generated by scripts/generate-surface.rb +*/ + +#include "surface.h" + +namespace Mackie +{ + +class MackieButtonHandler; + +class MackieSurface : public Surface +{ +public: + MackieSurface( uint32_t max_strips ) : Surface( max_strips ) + { + } + + virtual void handle_button( MackieButtonHandler & mbh, ButtonState bs, Button & button ); + virtual void init_controls(); +}; + +} + +#endif diff --git a/libs/surfaces/mackie/midi_byte_array.cc b/libs/surfaces/mackie/midi_byte_array.cc new file mode 100644 index 0000000000..192af6a1ce --- /dev/null +++ b/libs/surfaces/mackie/midi_byte_array.cc @@ -0,0 +1,98 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "midi_byte_array.h" + +#include <iostream> +#include <string> +#include <sstream> +#include <vector> +#include <algorithm> +#include <cstdarg> +#include <iomanip> + +using namespace std; + +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 ); +} + +boost::shared_array<MIDI::byte> MidiByteArray::bytes() const +{ + MIDI::byte * buf = new MIDI::byte[size()]; + const_iterator it = begin(); + for( MIDI::byte * ptr = buf; it != end(); ++it ) + { + *ptr++ = *it; + } + return boost::shared_array<MIDI::byte>( buf ); +} + +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 ) +{ + back_insert_iterator<MidiByteArray> bit( mba ); + copy( barr.begin(), barr.end(), bit ); + return mba; +} + +ostream & operator << ( 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 << hex << setw(2) << (int)*it; + } + os.fill( fill ); + os << dec; + os << "]"; + return os; +} diff --git a/libs/surfaces/mackie/midi_byte_array.h b/libs/surfaces/mackie/midi_byte_array.h new file mode 100644 index 0000000000..e77ece1b88 --- /dev/null +++ b/libs/surfaces/mackie/midi_byte_array.h @@ -0,0 +1,76 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, 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[] ); + + /** + Accepts a preceding count, and then a list of bytes + */ + MidiByteArray( size_t count, MIDI::byte first, ... ); + + /// return smart pointer to a copy of the bytes + boost::shared_array<MIDI::byte> bytes() const; + + /// 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 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 diff --git a/libs/surfaces/mackie/route_signal.cc b/libs/surfaces/mackie/route_signal.cc new file mode 100644 index 0000000000..d77d0520a1 --- /dev/null +++ b/libs/surfaces/mackie/route_signal.cc @@ -0,0 +1,95 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "route_signal.h" + +#include <ardour/route.h> +#include <ardour/track.h> +#include <ardour/panner.h> + +#include "mackie_control_protocol.h" + +#include <stdexcept> + +using namespace Mackie; + +void RouteSignal::connect() +{ + if ( _strip.has_solo() ) + _solo_changed_connection = _route.solo_control().Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_solo_changed ), this ) ); + + if ( _strip.has_mute() ) + _mute_changed_connection = _route.mute_control().Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_mute_changed ), this ) ); + + if ( _strip.has_gain() ) + _gain_changed_connection = _route.gain_control().Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_gain_changed ), this ) ); + + _name_changed_connection = _route.name_changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_name_changed ), this ) ); + + if ( _route.panner().size() == 1 ) + { + _panner_changed_connection = _route.panner()[0]->Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_panner_changed ), this ) ); + } + + try + { + _record_enable_changed_connection = + dynamic_cast<ARDOUR::Track&>( _route ).rec_enable_control().Changed + .connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_record_enable_changed ), this ) ) + ; + } + catch ( std::bad_cast & ) + { + // this should catch the dynamic_cast to Track, if what we're working + // with can't be record-enabled + } + + // TODO + // active_changed + // SelectedChanged + // RemoteControlIDChanged. Better handled at Session level. +} + +void RouteSignal::disconnect() +{ + _solo_changed_connection.disconnect(); + _mute_changed_connection.disconnect(); + _gain_changed_connection.disconnect(); + _name_changed_connection.disconnect(); + _panner_changed_connection.disconnect(); + _record_enable_changed_connection.disconnect(); +} + +void RouteSignal::notify_all() +{ + if ( _strip.has_solo() ) + _mcp.notify_solo_changed( this ); + + if ( _strip.has_mute() ) + _mcp.notify_mute_changed( this ); + + if ( _strip.has_gain() ) + _mcp.notify_gain_changed( this ); + + _mcp.notify_name_changed( &_route, this ); + + if ( _strip.has_vpot() ) + _mcp.notify_panner_changed( this ); + + if ( _strip.has_recenable() ) + _mcp.notify_record_enable_changed( this ); +} diff --git a/libs/surfaces/mackie/route_signal.h b/libs/surfaces/mackie/route_signal.h new file mode 100644 index 0000000000..0239980fd4 --- /dev/null +++ b/libs/surfaces/mackie/route_signal.h @@ -0,0 +1,81 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#ifndef route_signal_h +#define route_signal_h + +#include <sigc++/sigc++.h> + +class MackieControlProtocol; + +namespace ARDOUR { + class Route; +} + +namespace Mackie +{ + +class Strip; +class MackiePort; + +/** + This class is intended to easily create and destroy the set of + connections from a route to a control surface strip. Instantiating + it will connect the signals, and destructing it will disconnect + the signals. +*/ +class RouteSignal +{ +public: + RouteSignal( ARDOUR::Route & route, MackieControlProtocol & mcp, Strip & strip, MackiePort & port ) + : _route( route ), _mcp( mcp ), _strip( strip ), _port( port ) + { + connect(); + } + + ~RouteSignal() + { + disconnect(); + } + + void connect(); + void disconnect(); + + // call all signal handlers manually + void notify_all(); + + const ARDOUR::Route & route() const { return _route; } + Strip & strip() { return _strip; } + MackiePort & port() { return _port; } + +private: + ARDOUR::Route & _route; + MackieControlProtocol & _mcp; + Strip & _strip; + MackiePort & _port; + + sigc::connection _solo_changed_connection; + sigc::connection _mute_changed_connection; + sigc::connection _record_enable_changed_connection; + sigc::connection _gain_changed_connection; + sigc::connection _name_changed_connection; + sigc::connection _panner_changed_connection; +}; + +} + +#endif diff --git a/libs/surfaces/mackie/scripts/bank.rb b/libs/surfaces/mackie/scripts/bank.rb new file mode 100644 index 0000000000..e1482545ee --- /dev/null +++ b/libs/surfaces/mackie/scripts/bank.rb @@ -0,0 +1,32 @@ +#! /usr/bin/ruby + +class Bank + attr_accessor :routes, :strips, :current + + def initialize( routes = 17, strips = 8, current = 0 ) + @routes = routes + @strips = strips + @current = current + end + + def left + new_initial = current - routes + if new_initial < 0 + new_initial = 0 + end + current = new_initial + self + end + + def right + delta = routes - ( strips + current ) - 1 + puts "delta: #{delta}" + if delta > strips + delta = strips + end + @current += delta + self + end +end + +b=Bank.new diff --git a/libs/surfaces/mackie/scripts/bcf-controls.csv b/libs/surfaces/mackie/scripts/bcf-controls.csv new file mode 100644 index 0000000000..6a6d66f6ac --- /dev/null +++ b/libs/surfaces/mackie/scripts/bcf-controls.csv @@ -0,0 +1,96 @@ +type,count,group,name,switch,led,id +# faders +fader,7,strip,gain,1,0,0x00 +fader,1,master,gain,1,0,0x07 + +# pots +pot,7,strip,vpot,1,1,0x10 +pot,1,,jog,1,0,0x17 +pot,1,,external,1,0,0x2e + +# strip buttons +button,7,strip,recenable,1,1,0x18 +button,7,strip,solo,1,1,0x20 +button,7,strip,mute,1,1,0x10 +button,7,strip,select,1,1,0x0 +button,7,strip,vselect,1,0,0x08 + +# overlay buttons +button,1,assignment,io,1,1,0x28 +button,1,assignment,sends,1,1,0x5a +button,1,assignment,pan,1,1,0x59 +button,1,assignment,plugin,1,1,0x57 +button,1,assignment,eq,1,1,0x58 +button,1,assignment,dyn,1,1,0x2d +button,1,bank,left,1,0,0x2e +button,1,bank,right,1,0,0x2f +button,1,bank,channel_left,1,0,0x30 +button,1,bank,channel_right,1,0,0x31 +button,1,,flip,1,1,0x32 +button,1,,edit,1,1,0x56 + +button,1,display,name_value,1,0,0x34 +button,1,display,smpte_beats,1,0,0x35 +button,1,,F1,1,0,0x36 +button,1,,F2,1,0,0x37 +button,1,,F3,1,0,0x38 +button,1,,F4,1,0,0x39 +button,1,,F5,1,0,0x3a +button,1,,F6,1,0,0x3b +button,1,,F7,1,0,0x3c +button,1,,F8,1,0,0x3d +button,1,,F9,1,0,0x3e +button,1,,F10,1,0,0x3f +button,1,,F11,1,0,0x40 +button,1,,F12,1,0,0x41 +button,1,,F13,1,0,0x42 +button,1,,F14,1,0,0x43 +button,1,,F15,1,0,0x44 +button,1,,F16,1,0,0x45 +# turn on/off all solos +button,1,,global_solo,1,0,0x27 +button,1,modifiers,option,1,0,0x47 +button,1,modifiers,control,1,0,0x48 +button,1,modifiers,cmd_alt,1,0,0x49 +button,1,automation,on,1,1,0x4a +button,1,automation,rec_ready,1,1,0x4b +button,1,functions,undo,1,1,0x4c +button,1,automation,snapshot,1,1,0x4d +button,1,automation,touch,1,1,0x4e +button,1,functions,redo,1,1,0x4f +button,1,functions,marker,1,1,0x50 +button,1,functions,enter,1,1,0x51 +button,1,functions,cancel,1,0,0x52 +button,1,functions,mixer,1,0,0x53 +button,1,transport,frm_left,1,1,0x54 +button,1,transport,frm_right,1,1,0x55 +button,1,transport,loop,1,1,0x46 +button,1,transport,punch_in,1,1,0x2c +button,1,transport,punch_out,1,1,0x2b +button,1,transport,home,1,1,0x2a +button,1,transport,end,1,1,0x29 + +# transport buttons +button,1,transport,"rewind",1,1,0x5b +button,1,transport,"ffwd",1,1,0x5c +button,1,transport,"stop",1,1,0x5d +button,1,transport,"play",1,1,0x5e +button,1,transport,"record",1,1,0x1f +button,1,cursor,"cursor_up",1,0,0x60 +button,1,cursor,"cursor_down",1,0,0x61 +button,1,cursor,"cursor_left",1,0,0x62 +button,1,cursor,"cursor_right",1,0,0x63 +button,1,,"zoom",1,1,0x64 +button,1,,"scrub",1,1,0x65 +button,1,user,"user_a",1,0,0x66 +button,1,user,"user_b",1,0,0x67 + +button,7,strip,"fader_touch",1,0,0x68 +button,1,master,"fader_touch",1,0,0x6f +button,1,master,mute,1,0,0x17 +button,1,,clicking,1,1,0x33 + +button,1,,"smpte",0,1,0x71 +button,1,,"beats",0,1,0x72 +button,1,,"solo",0,1,0x73 +button,1,,"relay_click",0,1,0x76 diff --git a/libs/surfaces/mackie/scripts/controls.rb b/libs/surfaces/mackie/scripts/controls.rb new file mode 100644 index 0000000000..b56fd6010d --- /dev/null +++ b/libs/surfaces/mackie/scripts/controls.rb @@ -0,0 +1,208 @@ +#! /usr/bin/ruby +# Copyright (C) 2006,2007 John Anderson + +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +require 'faster_csv' +require 'mackie.rb' + +class Control + attr_accessor :id, :led, :group, :name, :ordinal, :switch + + def initialize( obj, group ) + @id = obj.id + @name = obj.name + @ordinal = obj.ordinal + @switch = obj.switch + @group = group + end + + def ordinal_name + end +end + +class Fader < Control + def self.midi_zero_byte + 0xe0 + end + + def self.mask_for_id( bytes ) + bytes[0] & 0b00001111 + end +end + +class Button < Control + def self.midi_zero_byte + 0x90 + end + + def self.mask_for_id( bytes ) + bytes[1] + end +end + +class Led < Control +end + +class LedRing < Led +end + +class Pot < Control + def self.midi_zero_byte + 0xb0 + end + + def self.mask_for_id( bytes ) + bytes[1] & 0b00011111 + end + + def led=( rhs ) + @led = LedRing.new( rhs, group ) + end +end + +class Group < Array + attr_accessor :name, :controls + + def initialize( name ) + @name = name + end + + def add_control( control ) + @controls ||= Array.new + @controls << control + end +end + +class Strip < Group + + attr_accessor :ordinal + def initialize( name, ordinal ) + super( name ) + @ordinal = ordinal + end + + def name + @name == 'master' ? @name : "#{@name}_#{@ordinal}" + end + + def is_master + name == 'master' + end + +end + +types = { 0xe0 => Fader, 0x90 => Button, 0xb0 => Pot } + +# number of controls, name, switch, led, id +# anything that doesn't have the correct number +# of columns will be ignored +# actually, 'switch' means it generates data +# whereas 'led' means it receives data + +class Row + attr_accessor :count, :name, :switch, :led, :start_id, :type, :group + attr_accessor :id, :ordinal_name, :ordinal_group, :ordinal + + def initialize( hash ) + @count = hash['count'].to_i + @name = hash['name'] + @switch = hash['switch'].to_b + @led = hash['led'].to_b + @start_id = hash['id'].hex + @type = hash['type'] + @group = hash['group'] + + @hash = hash + end + + def each_ordinal( &block ) + for i in 0...count + @ordinal = i + 1 + @ordinal_name = count > 1 ? "#{name}_#{ordinal}" : name + @ordinal_group = count > 1 ? "#{group}_#{ordinal}" : group + @id = start_id + i + + @hash['ordinal_name'] = @ordinal_name + @hash['ordinal_group'] = @ordinal_group + + yield( self ) + end + self + end + + def to_hash + @hash + end +end + +class Surface + attr_reader :groups, :controls_by_id, :types, :midis, :controls, :name + + def initialize( name = 'none' ) + @name = name + @types = Hash.new + @groups = Hash.new + @controls = Array.new + @controls_by_id = Hash.new + @midis = Hash.new + end + + def add_or_create_group( name, ordinal = nil ) + if name.nil? + @groups['none'] = Group.new('none') + else + group = name =~ /strip/ || name == 'master' ? Strip.new( name, ordinal ) : Group.new( name ) + @groups[group.name] ||= group + end + end + + def parse( control_data ) + FasterCSV.parse( control_data, :headers => true ) do |csv_row| + next if csv_row.entries.size < 5 || csv_row[0] =~ /^\s*#/ || csv_row['id'].nil? + row = Row.new( csv_row ) + + row.each_ordinal do |row| + group = add_or_create_group( row.group, row.ordinal ) + if row.switch + # for controls + control = eval "#{row.type.capitalize}.new( row, group )" + + # for controls with leds + control.led = Led.new( row, group ) if row.led + else + # for LED-only entries + if row.led + control = Led.new( row, group ) + control.led = control + end + end + + # add the new control to the various lookups + @controls_by_id[row.id] = control + @controls << control + group << control + + # add incoming midi bytes + if row.switch + types[control.class.midi_zero_byte] = control.class + midis[control.class.midi_zero_byte] ||= Hash.new + midis[control.class.midi_zero_byte][row.id] = control + end + end + end + self + end +end diff --git a/libs/surfaces/mackie/scripts/dump.rb b/libs/surfaces/mackie/scripts/dump.rb new file mode 100755 index 0000000000..f1e341fb34 --- /dev/null +++ b/libs/surfaces/mackie/scripts/dump.rb @@ -0,0 +1,11 @@ +#! /usr/bin/ruby + +while !File.exist? ARGV[0] + sleep 0.010 +end + +file = File.open ARGV[0], 'r' + +while bytes = file.sysread( 3 ) + puts "%02x %02x %02x" % [ bytes[0], bytes[1], bytes[2] ] +end diff --git a/libs/surfaces/mackie/scripts/generate-button-handlers-cc.erb b/libs/surfaces/mackie/scripts/generate-button-handlers-cc.erb new file mode 100644 index 0000000000..62bc65d0c3 --- /dev/null +++ b/libs/surfaces/mackie/scripts/generate-button-handlers-cc.erb @@ -0,0 +1,59 @@ +<%# + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +-%> +<%- +require 'controls.rb' + +sf = Surface.new +sf.parse( File.open "mackie-controls.csv" ) +buttons = sf.controls.find_all{|x| x.class == Button && x.group.class != Strip} +-%> +/* + Generated by scripts/generate-button-handlers.erb +*/ +#include "mackie_button_handler.h" +#include "controls.h" + +#include <iostream> + +using namespace std; +using namespace Mackie; + +LedState MackieButtonHandler::default_button_press( Button & button ) +{ + cout << "press: " << button << endl; + return on; +} +LedState MackieButtonHandler::default_button_release( Button & button ) +{ + cout << "release: " << button << endl; + return off; +} + +<%- +buttons.each do |button| +%> +LedState MackieButtonHandler::<%=button.name%>_press( Button & button ) +{ + return default_button_press( button ); +} + +LedState MackieButtonHandler::<%=button.name%>_release( Button & button ) +{ + return default_button_release( button ); +} +<% end %> diff --git a/libs/surfaces/mackie/scripts/generate-button-handlers-h.erb b/libs/surfaces/mackie/scripts/generate-button-handlers-h.erb new file mode 100644 index 0000000000..605b6c29dc --- /dev/null +++ b/libs/surfaces/mackie/scripts/generate-button-handlers-h.erb @@ -0,0 +1,54 @@ +<%# + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +-%> +<%- +require 'controls.rb' + +sf = Surface.new +sf.parse( File.open "mackie-controls.csv" ) +buttons = sf.controls.find_all{|x| x.class == Button && x.group.class != Strip} +-%> +#ifndef mackie_button_handler_h +#define mackie_button_handler_h +/* + Generated by scripts/generate-button-handlers.erb +*/ + +#include "types.h" + +namespace Mackie +{ + +class MackieButtonHandler +{ +public: + virtual ~MackieButtonHandler() {} + + virtual LedState default_button_press( Button & button ); + virtual LedState default_button_release( Button & button ); + + virtual void update_led( Button & button, LedState ls ) = 0; + +<%- buttons.each do |button| %> + virtual LedState <%=button.name%>_press( Button & ); + virtual LedState <%=button.name%>_release( Button & ); +<% end %> +}; + +} + +#endif diff --git a/libs/surfaces/mackie/scripts/generate-surface.rb b/libs/surfaces/mackie/scripts/generate-surface.rb new file mode 100755 index 0000000000..c6a028804a --- /dev/null +++ b/libs/surfaces/mackie/scripts/generate-surface.rb @@ -0,0 +1,26 @@ +#! /usr/bin/ruby + +require 'erb' + +require File.dirname(__FILE__) + '/controls.rb' + +cc_template = '' +File.open( File.dirname(__FILE__) + "/surface-cc-template.erb", "r" ) { |f| cc_template = f.read } + +h_template = '' +File.open( File.dirname(__FILE__) + "/surface-h-template.erb", "r" ) { |f| h_template = f.read } + +sf = Surface.new( ARGV[0] ) +control_data = '' +File.open( File.dirname(__FILE__) + "/#{sf.name.downcase}-controls.csv", "r") { |f| control_data = f.read } +sf.parse control_data + +@result = "" +erb = ERB.new( cc_template , 0, "%<>-", "@result" ) +erb.result +File.open( "#{sf.name.downcase}_surface.cc", "w" ) { |f| f.write @result } + +erb = ERB.new( h_template , 0, "%<>-", "@result" ) +erb.result +File.open( "#{sf.name.downcase}_surface.h", "w" ) { |f| f.write @result } + diff --git a/libs/surfaces/mackie/scripts/host.rb b/libs/surfaces/mackie/scripts/host.rb new file mode 100755 index 0000000000..8972cba137 --- /dev/null +++ b/libs/surfaces/mackie/scripts/host.rb @@ -0,0 +1,133 @@ +#! /usr/bin/ruby +# Copyright (C) 2006,2007 John Anderson + +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +require 'controls.rb' +require 'mackie.rb' + +while !File.exist? ARGV[0] + sleep 0.010 +end + +#mapping_csv = ARGV[1] || "mackie-controls.csv" +mapping_csv = ARGV[1] +puts "mapping_csv: #{mapping_csv}" +puts "" + +file = File.open ARGV[0], 'r+' +mck = Mackie.new( file ) + +# send device query +response = mck.sysex( "\x00" ) +puts "response: #{response.to_hex}" + +# decode host connection query +status = response[0] +if status != 1 + puts "expected 01, got " + response.to_hex.inspect + exit(1) +end +serial = response[1..7] +challenge = response[8..11] +puts <<EOF +serial: #{serial.to_hex.inspect} +challenge: #{challenge.to_hex.inspect} +EOF + +# send host connection reply +response = mck.sysex( "\x02" + serial.pack('C*') + challenge.pack('C*') ) + +# decode host connection confirmation +status = response[0] +if status != 3 + puts "expected 03, got " + response.to_hex.inspect + exit(1) +end + +serial = response[1..7] +puts <<EOF +serial: #{serial.to_hex.inspect} +EOF + +# faders to minimum. bcf2000 doesn't respond +#file.write( hdr + "\x61\xf7" ) + +# all leds off. bcf2000 doesn't respond +#file.write( hdr + "\x62\xf7" ) + +# get version. comes back as ASCII bytes +version = mck.sysex( "\x13\x00" ) +puts "version: #{version.map{|x| x.chr}}" + +# write a welcome message. bcf2000 responds with exact +# string but doesn't display anything +# 0 offset, +#~ file.write hdr + "\x12\x3fLCDE\xf7" +#~ file.flush +#~ answer = read_sysex file +#~ puts "answer: #{answer[hdr.length..-1].map{|x| x.chr}}" + +# write to BBT display +#~ file.write hdr + "\x10LCDE\xf7" +#~ file.flush +#~ bbt = [] +#~ while ( nc = file.read( 1 ) )[0] != 0xf7 + #~ bbt << nc[0] +#~ end +#~ puts "bbt: #{bbt[hdr.length..-1].map{|x| x.chr}}" + +# write 7-segment display +#~ file.write hdr + "\x11LCDE\xf7" +#~ file.flush + +# go offline. bcf2000 doesn't respond +#~ file.write( hdr + "\x0f\x7f\xf7" ) +#~ file.flush + +sf = Surface.new +control_data = "" +File.open( mapping_csv ) { |f| control_data = f.read } +sf.parse( control_data ) + +# send all faders to 0, but bounce them first +# otherwise the bcf gets confused +sf.midis[0xe0].values.find_all{|x| x.class == Fader}.each do |x| + bytes = Array.new + bytes[0] = 0xe0 + x.ordinal - 1 + bytes[1] = 0x1 + bytes[2] = 0x1 + file.write bytes.pack( 'C*' ) + bytes[0] = 0xe0 + x.ordinal - 1 + bytes[1] = 0x0 + bytes[2] = 0x0 + file.write bytes.pack( 'C*' ) +end +file.flush + +# respond to control movements +while bytes = mck.file.read( 3 ) + print "received: %02.x %02.x %02.x" % [ bytes[0], bytes[1], bytes[2] ] + midi_type = bytes[0] & 0b11110000 + + control_id = sf.types[midi_type].mask_for_id( bytes ) + control = sf.midis[midi_type][control_id] + + print " Control Type: %-7s, " % sf.types[midi_type] + print "id: %4i" % control_id + print ", control: %15s" % ( control ? control.name : "nil control" ) + print ", %15s" % ( control ? control.group.name : "nil group" ) + print "\n" +end diff --git a/libs/surfaces/mackie/scripts/mackie-controls.csv b/libs/surfaces/mackie/scripts/mackie-controls.csv new file mode 100644 index 0000000000..5dbb6297f8 --- /dev/null +++ b/libs/surfaces/mackie/scripts/mackie-controls.csv @@ -0,0 +1,93 @@ +type,count,group,name,switch,led,id +# faders +fader,8,strip,gain,1,0,0x00 +fader,1,master,gain,1,0,0x08 + +# pots +pot,8,strip,vpot,1,1,0x10 +pot,1,,jog,1,0,0x3c +pot,1,,external,1,0,0x2e + +# strip buttons +button,8,strip,recenable,1,1,0x0 +button,8,strip,solo,1,1,0x08 +button,8,strip,mute,1,1,0x10 +button,8,strip,select,1,1,0x18 +button,8,strip,vselect,1,0,0x20 + +# overlay buttons +button,1,assignment,io,1,1,0x28 +button,1,assignment,sends,1,1,0x29 +button,1,assignment,pan,1,1,0x2a +button,1,assignment,plugin,1,1,0x2b +button,1,assignment,eq,1,1,0x2c +button,1,assignment,dyn,1,1,0x2d +button,1,bank,left,1,0,0x2e +button,1,bank,right,1,0,0x2f +button,1,bank,channel_left,1,0,0x30 +button,1,bank,channel_right,1,0,0x31 +button,1,,flip,1,1,0x32 +button,1,,edit,1,1,0x33 + +button,1,display,name_value,1,0,0x34 +button,1,display,smpte_beats,1,0,0x35 +button,1,,F1,1,0,0x36 +button,1,,F2,1,0,0x37 +button,1,,F3,1,0,0x38 +button,1,,F4,1,0,0x39 +button,1,,F5,1,0,0x3a +button,1,,F6,1,0,0x3b +button,1,,F7,1,0,0x3c +button,1,,F8,1,0,0x3d +button,1,,F9,1,0,0x3e +button,1,,F10,1,0,0x3f +button,1,,F11,1,0,0x40 +button,1,,F12,1,0,0x41 +button,1,,F13,1,0,0x42 +button,1,,F14,1,0,0x43 +button,1,,F15,1,0,0x44 +button,1,,F16,1,0,0x45 +button,1,modifiers,shift,1,0,0x46 +button,1,modifiers,option,1,0,0x47 +button,1,modifiers,control,1,0,0x48 +button,1,modifiers,cmd_alt,1,0,0x49 +button,1,automation,on,1,1,0x4a +button,1,automation,rec_ready,1,1,0x4b +button,1,functions,undo,1,1,0x4c +button,1,automation,snapshot,1,1,0x4d +button,1,automation,touch,1,1,0x4e +button,1,functions,redo,1,1,0x4f +button,1,functions,marker,1,1,0x50 +button,1,functions,enter,1,1,0x51 +button,1,functions,cancel,1,0,0x52 +button,1,functions,mixer,1,0,0x53 +button,1,transport,frm_left,1,1,0x54 +button,1,transport,frm_right,1,1,0x55 +button,1,transport,loop,1,1,0x56 +button,1,transport,punch_in,1,1,0x57 +button,1,transport,punch_out,1,1,0x58 +button,1,transport,home,1,1,0x59 +button,1,transport,end,1,1,0x5a + +# transport buttons +button,1,transport,"rewind",1,1,0x5b +button,1,transport,"ffwd",1,1,0x5c +button,1,transport,"stop",1,1,0x5d +button,1,transport,"play",1,1,0x5e +button,1,transport,"record",1,1,0x5f +button,1,cursor,"cursor_up",1,0,0x60 +button,1,cursor,"cursor_down",1,0,0x61 +button,1,cursor,"cursor_left",1,0,0x62 +button,1,cursor,"cursor_right",1,0,0x63 +button,1,,"zoom",1,1,0x64 +button,1,,"scrub",1,1,0x65 +button,1,user,"user_a",1,0,0x66 +button,1,user,"user_b",1,0,0x67 + +button,8,strip,"fader_touch",1,0,0x68 +button,1,master,"fader_touch",1,0,0x70 + +button,1,,"smpte",0,1,0x71 +button,1,,"beats",0,1,0x72 +button,1,,"solo",0,1,0x73 +button,1,,"relay_click",0,1,0x76 diff --git a/libs/surfaces/mackie/scripts/mackie.rb b/libs/surfaces/mackie/scripts/mackie.rb new file mode 100644 index 0000000000..4c4080ad10 --- /dev/null +++ b/libs/surfaces/mackie/scripts/mackie.rb @@ -0,0 +1,119 @@ +class String + def to_bytes + arr = [] + each_byte{|x| arr << x} + arr + end +end + +class Array + def to_hex + map{|x| "%2.0x" % x} + end + + alias as_hex to_hex +end + +class String + def to_b + to_i != 0 || %w{true t yes y}.include?( self.downcase ) + end +end + +class Fixnum + def to_hex + "%02x" % self + end +end + +class Mackie + attr_accessor :file + + def initialize( file ) + @file = file + end + + # send and receive a sysex message + # after wrapping in the header and the eox byte + def sysex( msg ) + puts "Mackie write: #{msg.unpack('C*').to_hex.inspect}" + write_sysex( msg ) + response = read_sysex + puts "Mackie response: #{response.to_hex.inspect}" + response[5..-1] + end + + # returns an array of bytes + def read_sysex + buf = [] + while ( nc = @file.read( 1 ) )[0] != 0xf7 + buf << nc[0] + end + buf + end + + # send and flush a sysex message + # after wrapping in the header and the eox byte + def write_sysex( msg ) + @file.write( hdrlc + msg + "\xf7" ) + @file.flush + end + + def write( msg ) + @file.write msg + @file.flush + end + + def translate_seven_segment( char ) + case char + when 0x40..0x60 + char - 0x40 + when 0x21..0x3f + char + else + 0x00 + end + end + + # display the msg (which can be only 2 characters) + # append the number of stops. Options are '..', '. ', '. ', ' ' + def two_char( msg, stops = ' ' ) + two = Array.new + two << translate_seven_segment( msg.upcase[0] ) + two << translate_seven_segment( msg.upcase[1] ) + + two[0] += 0x40 if stops[0] == '.'[0] + two[1] += 0x40 if stops[1] == '.'[0] + + midi_msg = [0xb0, 0x4a, two[1], 0x4b, two[0] ] + write midi_msg.pack( 'C*' ) + end + + # send and receive the device initialisation + def init + response = sysex( "\x00" ) + + # decode host connection query + status = response[0] + raise( "expected 01, got " + response.inspect ) if status != 1 + + serial = response[1..7] + challenge = response[8..11] + + # send host connection reply + reply = "\x02" + serial.pack('C*') + challenge.pack('C*') + response = sysex reply + + # decode host connection confirmation + status = response[0] + raise ( "expected 03, got " + response.inspect ) if status != 3 + end + + def hdrlc + "\xf0\x00\x00\x66\x10" + end + + def hdrlcxt + "\xf0\x00\x00\x66\x11" + end +end diff --git a/libs/surfaces/mackie/scripts/parse.rb b/libs/surfaces/mackie/scripts/parse.rb new file mode 100644 index 0000000000..3a225a5756 --- /dev/null +++ b/libs/surfaces/mackie/scripts/parse.rb @@ -0,0 +1,61 @@ +#! /usr/bin/ruby +# Copyright (C) 2006,2007 John Anderson + +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +require "rexml/document" +file = File.new( ARGV[0] ) +doc = REXML::Document.new file + +# fetch the node containing the controls +controls = XPath.first( doc, 'Session/ControlProtocols/Protocol[@name="Generic MIDI"]/controls' ) + +channel = 1 + +# A Control is a button or slider. It has an internal ID +# an incoming MIDI message, and an outgoing midi message +class Control + +end + +# Strips have solo,rec,mute,pan,fader +# Strips have midi input +# Strips have midi output +# Strips have an XML representation, or something like that +class Strip + def initialize( node ) + @solo = node.elements['solo'] + @mute = node.elements['mute'] + @rec = node.elements['recenable'] + @fader = node.elements['IO/gaincontrol'] + @panner = node.elements['IO/Panner/StreamPanner/panner'] + end +end + +# This knows how to extract a set of controls from a Route + +doc.elements.each( 'Session/Routes/Route' ) do |node| + strip = Strip.new( node ) + + controls.add_element( 'mute', + 'id' => mute.attribute('id').value, + 'event' => "0xb0", + 'channel' => channel.to_s, + 'additional' => "0x41" + ) + +end + +pp controls.elements diff --git a/libs/surfaces/mackie/scripts/signals.rb b/libs/surfaces/mackie/scripts/signals.rb new file mode 100644 index 0000000000..8182e562a3 --- /dev/null +++ b/libs/surfaces/mackie/scripts/signals.rb @@ -0,0 +1,137 @@ +#~ /usr/bin/ruby +# Copyright (C) 2006,2007 John Anderson + +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +require 'erb' + +signals = %w{ +solo_changed +mute_changed +record_enable_changed +gain_changed +name_changed +panner_changed +} + +@signal_calls = { 'panner_changed' => 'panner()[0]->Changed' } + +def connection_call( x ) + if @signal_calls.include? x + @signal_calls[x] + else + x + end +end + +signals.each do |x| + puts <<EOF +void MackieControlProtocol::notify_#{x}( void *, ARDOUR::Route * route ) +{ + try + { + strip_from_route( route ).#{x.gsub( /_changed/, '' )}(); + } + catch( exception & e ) + { + cout << e.what() << endl; + } +} + +EOF +end + +class_def = <<EOF +#ifndef route_signal_h +#define route_signal_h + +#include <sigc++/sigc++.h> + +class MackieControlProtocol; + +namespace ARDOUR { + class Route; +} + +namespace Mackie +{ + +class Strip; + +/** + This class is intended to easily create and destroy the set of + connections from a route to a control surface strip. Instanting + it will connect the signals, and destructing it will disconnect + the signals. +*/ +class RouteSignal +{ +public: + RouteSignal( ARDOUR::Route & route, MackieControlProtocol & mcp, Strip & strip ) + : _route( route ), _mcp( mcp ), _strip( strip ) + { + connect(); + } + + ~RouteSignal() + { + disconnect(); + } + +private: + ARDOUR::Route & _route; + MackieControlProtocol & _mcp; + Strip & _strip; + +<% signals.each do |x| -%> + sigc::connection _<%= x %>_connection; +<% end -%> +}; + +} + +#endif +EOF + +erb = ERB.new( class_def, 0, ">-" ) +erb.run + +impl_def = <<EOF +#include "route_signal.h" + +#include <ardour/route.h> +#include <ardour/panner.h> + +#include "mackie_control_protocol.h" + +using namespace Mackie; + +void RouteSignal::connect() +{ +<% signals.each do |x| -%> + _<%=x%>_connection = _route.<%=connection_call(x)%>.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_<%=x%> ), &_route ) ); +<% end -%> +} + +void RouteSignal::disconnect() +{ +<% signals.each do |x| -%> + _<%= x %>_connection.disconnect(); +<% end -%> +} +EOF + +erb = ERB.new( impl_def, 0, ">-" ) +erb.run diff --git a/libs/surfaces/mackie/scripts/simple_host.rb b/libs/surfaces/mackie/scripts/simple_host.rb new file mode 100644 index 0000000000..a5c07f2abb --- /dev/null +++ b/libs/surfaces/mackie/scripts/simple_host.rb @@ -0,0 +1,137 @@ +#! /usr/bin/ruby +# Copyright (C) 2006,2007 John Anderson + +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +require 'mackie' + +buttons = {} +pots = {} + +while !File.exist? ARGV[0] + sleep 0.010 +end + +file = File.open( ARGV[0], 'r+' ) +mck = Mackie.new( file ) + +# faders to minimum. bcf2000 doesn't respond +mck.write_sysex "\x61" + +# all leds off. bcf2000 doesn't respond +mck.write_sysex "\x62" + +# get version. comes back as ASCII bytes +version = mck.sysex "\x13\x00" +puts "version: #{version.map{|x| x.chr}}" + +# respond to control movements +while bytes = file.read( 3 ) + puts "received: %02.x %02.x %02.x" % [ bytes[0], bytes[1], bytes[2] ] + output = nil + case bytes[0] & 0b11110000 + when 0xe0 + # fader moved, so respond if move is OK + output = bytes + when 0x90 + # button pressed + case bytes[1] + when 0x68..0x6f + # do nothing - touch detection + puts "touch detect: %02.x" % bytes[2] + else + # treat all buttons as toggles + button_id = bytes[1] + + # only toggle on release. Not working. All buttons send press + # and then release signals + if bytes[2] == 0 + if buttons.include?( button_id ) + # toggle button state + puts "button id #{buttons[button_id]} to #{!buttons[button_id]}" + buttons[button_id] = !buttons[button_id] + else + # create a new button as on + puts "adding button id #{button_id}" + buttons[button_id] = true + end + bytes[2] = buttons[button_id] ? 0x7f : 0 + output = bytes + end + end + when 0xb0 + # pots, jog wheel, external + case bytes[1] + when 0x10..0x17 + #pot turned + pot_id = bytes[1] & 0b00000111 + direction = bytes[2] & 0b01000000 + delta = bytes[2] & 0b00111111 + sign = direction == 0 ? 1 : -1 + + if pots.include? pot_id + current_led_pos = pots[pot_id] + else + current_led_pos = pots[pot_id] = 6 + end + new_led_pos = current_led_pos + sign + new_led_pos = case + when new_led_pos <= 0 + 0 + when new_led_pos >= 11 + 11 + else + new_led_pos + end + + pots[pot_id] = new_led_pos + + puts "pot #{pot_id} turned #{sign} #{direction == 0 ? 'clockwise' : 'widdershins'}: %02.x to #{new_led_pos}" % delta + + output = bytes + output[1] += 0x20 + output[2] = 0b01000000 + #~ modes: + #~ 0 - single dot + #~ 1 - boost/cut + #~ 2 - wrap + #~ 3 - spread + mode = pot_id < 4 ? pot_id : 0 + output[2] |= ( mode << 4 ) + output[2] += ( new_led_pos ) & 0b00001111 + when 0x2e + # external controller + when 0x3c + # jog wheel + end + else + puts "don't know what this means" + end + + # output bytes + if output + #sleep 0.1 + puts "sending: %02.x %02.x %02.x" % [ output[0], output[1], output[2] ] + begin + res = file.write output + puts "res: #{res}" + file.flush + rescue => e + puts "oops #{e}" + file.close + file = File.open ARGV[0], 'r+' + end + end +end diff --git a/libs/surfaces/mackie/scripts/surface-cc-template.erb b/libs/surfaces/mackie/scripts/surface-cc-template.erb new file mode 100644 index 0000000000..3b29be3249 --- /dev/null +++ b/libs/surfaces/mackie/scripts/surface-cc-template.erb @@ -0,0 +1,95 @@ +<%# + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +-%> +/* + Generated by scripts/generate-surface.rb +*/ + +#include "<%= sf.name.downcase %>_surface.h" + +#include "controls.h" +#include "mackie_button_handler.h" + +using namespace Mackie; + +void Mackie::<%= sf.name %>Surface::init_controls() +{ + // intialise groups and strips + Group * group = 0; + + // make sure there are enough strips + strips.resize( <%= sf.groups.values.find_all{|x| x.name =~ /strip/}.size %> ); + +% sf.groups.values.each do |group| + <%- if group.class == Strip -%> + <%- if group.name == 'master' -%> + group = new MasterStrip ( "<%=group.name%>", 0 ); + <%- else -%> + group = new <%= group.class.name %> ( "<%=group.name%>", <%=group.ordinal - 1%> ); + <%- end -%> + <%- else -%> + group = new <%= group.class.name %> ( "<%=group.name%>" ); + <%- end -%> + groups["<%=group.name%>"] = group; + <%- if group.class == Strip -%> + strips[<%=group.ordinal - 1%>] = dynamic_cast<Strip*>( group ); + <%- end -%> + +% end + + // initialise controls + Control * control = 0; + +% sf.controls.each do |control| + group = groups["<%=control.group.name%>"]; + control = new <%= control.class.name %> ( <%= control.id %>, <%= control.ordinal %>, "<%=control.name%>", *group ); + <%=control.class.name.downcase%>s[0x<%=control.id.to_hex %>] = control; + controls.push_back( control ); + <%- if control.group.class != Strip -%> + controls_by_name["<%= control.name %>"] = control; + <%- end -%> + group->add( *control ); + +% end +} + +void Mackie::<%= sf.name %>Surface::handle_button( MackieButtonHandler & mbh, ButtonState bs, Button & button ) +{ + if ( bs != press && bs != release ) + { + mbh.update_led( button, none ); + return; + } + + LedState ls; + switch ( button.id() ) + { +<%- +buttons = sf.controls.find_all{|x| x.class == Button && x.group.class != Strip} +buttons.each do |button| +%> + case 0x<%= button.id.to_hex %>: // <%= button.name %> + switch ( bs ) { + case press: ls = mbh.<%= button.name %>_press( button ); break; + case release: ls = mbh.<%= button.name %>_release( button ); break; + case neither: break; + } + break; +<% end %> + } + mbh.update_led( button, ls ); +} diff --git a/libs/surfaces/mackie/scripts/surface-h-template.erb b/libs/surfaces/mackie/scripts/surface-h-template.erb new file mode 100644 index 0000000000..2f54f66c47 --- /dev/null +++ b/libs/surfaces/mackie/scripts/surface-h-template.erb @@ -0,0 +1,27 @@ +#ifndef mackie_surface_<%= sf.name.downcase %>_h +#define mackie_surface_<%= sf.name.downcase %>_h +/* + Generated by scripts/generate-surface.rb +*/ + +#include "surface.h" + +namespace Mackie +{ + +class MackieButtonHandler; + +class <%= sf.name %>Surface : public Surface +{ +public: + <%= sf.name %>Surface( uint32_t max_strips ) : Surface( max_strips ) + { + } + + virtual void handle_button( MackieButtonHandler & mbh, ButtonState bs, Button & button ); + virtual void init_controls(); +}; + +} + +#endif diff --git a/libs/surfaces/mackie/scripts/test_controls.rb b/libs/surfaces/mackie/scripts/test_controls.rb new file mode 100755 index 0000000000..782b0d427c --- /dev/null +++ b/libs/surfaces/mackie/scripts/test_controls.rb @@ -0,0 +1,9 @@ +#! /usr/bin/ruby + +require 'controls.rb' +require 'pp' + +sf = Surface.new +sf.parse +sf.types.each{|k,v| puts "%02.x #{v}" % k} + diff --git a/libs/surfaces/mackie/scripts/transform.rb b/libs/surfaces/mackie/scripts/transform.rb new file mode 100644 index 0000000000..e0221e188b --- /dev/null +++ b/libs/surfaces/mackie/scripts/transform.rb @@ -0,0 +1,26 @@ +class ElementHandler + + def apply( anElement ) + anElement.each {|e| handle(e)} if anElement + end + + def handle( aNode ) + if aNode.kind_of? REXML::Text + handleTextNode(aNode) + elsif aNode.kind_of? REXML::Element + handle_element aNode + else + return #ignore comments and processing instructions + end + end + + def handle_element( anElement ) + handler_method = "handle_" + anElement.name.tr("-","_") + if self.respond_to? handler_method + self.send(handler_method, anElement) + else + default_handler(anElement) + end + end + +end diff --git a/libs/surfaces/mackie/scripts/write.rb b/libs/surfaces/mackie/scripts/write.rb new file mode 100644 index 0000000000..20b72477e8 --- /dev/null +++ b/libs/surfaces/mackie/scripts/write.rb @@ -0,0 +1,10 @@ +#! /usr/bin/ruby + +require 'mackie.rb' + +@file = File.open '/dev/snd/midiC2D0', 'r+' + +@led_8_on = [ 0x90, 0x18, 0x7f ] +@hci = [ 0, 0xf7 ] +@version_req = [ 0x13, 0, 0xf7 ] + diff --git a/libs/surfaces/mackie/surface.cc b/libs/surfaces/mackie/surface.cc new file mode 100644 index 0000000000..01be2c60c2 --- /dev/null +++ b/libs/surfaces/mackie/surface.cc @@ -0,0 +1,142 @@ +#include "surface.h" + +#include <sstream> +#include <iomanip> +#include <iostream> + +using namespace std; +using namespace Mackie; + +Surface::Surface( uint32_t max_strips, uint32_t unit_strips ) +: _max_strips( max_strips ), _unit_strips( unit_strips ) +{ +} + +void Surface::init() +{ + init_controls(); + init_strips( _max_strips, _unit_strips ); +} + +Surface::~Surface() +{ + // delete groups + for( Groups::iterator it = groups.begin(); it != groups.end(); ++it ) + { + delete it->second; + } + + // delete controls + for( Controls::iterator it = controls.begin(); it != controls.end(); ++it ) + { + delete *it; + } +} + +// Mackie-specific, because of multiple devices on separate ports +// add the strips from 9..max_strips +// unit_strips is the number of strips for additional units. +void Surface::init_strips( uint32_t max_strips, uint32_t unit_strips ) +{ + if ( strips.size() < max_strips ) + { + strips.resize( max_strips ); + for ( uint32_t i = strips.size(); i < max_strips; ++i ) + { + // because I can't find itoa + ostringstream os; + os << "strip_" << i + 1; + string name = os.str(); + + // shallow copy existing strip + // which works because the controls + // have the same ids across units + Strip * strip = new Strip( *strips[i % unit_strips] ); + + // update the relevant values + strip->index( i ); + strip->name( name ); + + // add to data structures + groups[name] = strip; + strips[i] = strip; + } + } +} + +ostream & Mackie::operator << ( ostream & os, const Mackie::Control & control ) +{ + os << typeid( control ).name(); + os << " { "; + os << "name: " << control.name(); + os << ", "; + os << "id: " << "0x" << setw(2) << setfill('0') << hex << control.id() << setfill(' '); + os << ", "; + os << "ordinal: " << dec << control.ordinal(); + os << ", "; + os << "group: " << control.group().name(); + os << " }"; + + return os; +} + +/** + TODO could optimise this to use enum, but it's only + called during the protocol class instantiation. + + generated using + + irb -r controls.rb + sf=Surface.new + sf.parse + controls = sf.groups.find{|x| x[0] =~ /strip/}.each{|x| puts x[1]} + controls[1].each {|x| puts "\telse if ( control.name() == \"#{x.name}\" )\n\t{\n\t\t_#{x.name} = reinterpret_cast<#{x.class.name}*>(&control);\n\t}\n"} +*/ +void Strip::add( Control & control ) +{ + Group::add( control ); + if ( control.name() == "gain" ) + { + _gain = reinterpret_cast<Fader*>(&control); + } + else if ( control.name() == "vpot" ) + { + _vpot = reinterpret_cast<Pot*>(&control); + } + else if ( control.name() == "recenable" ) + { + _recenable = reinterpret_cast<Button*>(&control); + } + else if ( control.name() == "solo" ) + { + _solo = reinterpret_cast<Button*>(&control); + } + else if ( control.name() == "mute" ) + { + _mute = reinterpret_cast<Button*>(&control); + } + else if ( control.name() == "select" ) + { + _select = reinterpret_cast<Button*>(&control); + } + else if ( control.name() == "vselect" ) + { + _vselect = reinterpret_cast<Button*>(&control); + } + else if ( control.name() == "fader_touch" ) + { + _fader_touch = reinterpret_cast<Button*>(&control); + } + else if ( control.type() == Control::type_led || control.type() == Control::type_led_ring ) + { + // do nothing + cout << "Strip::add not adding " << control << endl; + } + else + { + ostringstream os; + os << "Strip::add: unknown control type " << control; + throw MackieControlException( os.str() ); + } +} + diff --git a/libs/surfaces/mackie/surface.h b/libs/surfaces/mackie/surface.h new file mode 100644 index 0000000000..0ccde75537 --- /dev/null +++ b/libs/surfaces/mackie/surface.h @@ -0,0 +1,94 @@ +#ifndef mackie_surface_h +#define mackie_surface_h + +#include "controls.h" +#include "types.h" +#include <stdint.h> + +namespace Mackie +{ + +class MackieButtonHandler; + +/** + This represents an entire control surface, made up of Groups, + Strips and Controls. There are several collections for + ease of addressing in different ways, but only one collection + has definitive ownership. + + It handles mapping button ids to press_ and release_ calls. + + There are various emulations of the Mackie around, so specific + emulations will inherit from this to change button mapping, or + have 7 fader channels instead of 8, or whatever. + + Currently there are BcfSurface and MackieSurface. + + TODO maybe make Group inherit from Control, for ease of ownership. +*/ +class Surface +{ +public: + /** + A Surface can be made up of multiple units. eg one Mackie MCU plus + one or more Mackie MCU extenders. + + \param max_strips is the number of strips for the entire surface. + \param unit_strips is the number of strips per unit. + */ + Surface( uint32_t max_strips, uint32_t unit_strips = 8 ); + virtual ~Surface(); + + /// Calls the virtual initialisation methods. This *must* be called after + /// construction, because c++ is too dumb to call virtual methods from + /// inside a constructor + void init(); + + typedef std::vector<Control*> Controls; + + /// This collection has ownership of all the controls + Controls controls; + + /** + These are alternative addressing schemes + They use maps because the indices aren't always + 0-based. + */ + std::map<int,Control*> faders; + std::map<int,Control*> pots; + std::map<int,Control*> buttons; + std::map<int,Control*> leds; + + /// no strip controls in here because they usually + /// have the same names. + std::map<std::string,Control*> controls_by_name; + + /// The collection of all numbered strips. No master + /// strip in here. + typedef std::vector<Strip*> Strips; + Strips strips; + + /// This collection owns the groups + typedef std::map<std::string,Group*> Groups; + Groups groups; + + uint32_t max_strips() const + { + return _max_strips; + } + + /// map button ids to calls to press_ and release_ in mbh + virtual void handle_button( MackieButtonHandler & mbh, ButtonState bs, Button & button ) = 0; + +protected: + virtual void init_controls() = 0; + virtual void init_strips( uint32_t max_strips, uint32_t unit_strips ); + +private: + uint32_t _max_strips; + uint32_t _unit_strips; +}; + +} + +#endif diff --git a/libs/surfaces/mackie/surface_port.cc b/libs/surfaces/mackie/surface_port.cc new file mode 100644 index 0000000000..8aa1be7fe9 --- /dev/null +++ b/libs/surfaces/mackie/surface_port.cc @@ -0,0 +1,178 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#include "surface_port.h" + +#include "mackie_control_exception.h" +#include "controls.h" + +#include <midi++/types.h> +#include <midi++/port.h> +#include <sigc++/sigc++.h> +#include <boost/shared_array.hpp> + +#include "i18n.h" + +#include <sstream> + +#include <cstring> +#include <cerrno> + +using namespace std; +using namespace Mackie; + +SurfacePort::SurfacePort( MIDI::Port & port, int number ) +: _port( port ), _number( number ), _active( false ) +{ +} + +SurfacePort::~SurfacePort() +{ + //cout << "~SurfacePort::SurfacePort()" << endl; + // make sure another thread isn't reading or writing as we close the port + Glib::RecMutex::Lock lock( _rwlock ); + _active = false; + //cout << "~SurfacePort::SurfacePort() finished" << endl; +} + +// wrapper for one day when strerror_r is working properly +string fetch_errmsg( int error_number ) +{ + char * msg = strerror( error_number ); + return msg; +} + +MidiByteArray SurfacePort::read() +{ + const int max_buf_size = 512; + MIDI::byte buf[max_buf_size]; + MidiByteArray retval; + + // check active. Mainly so that the destructor + // doesn't destroy the mutex while it's still locked + if ( !active() ) return retval; + + // return nothing read if the lock isn't acquired + Glib::RecMutex::Lock lock( _rwlock, Glib::TRY_LOCK ); + + if ( !lock.locked() ) + { + //cout << "SurfacePort::read not locked" << endl; + return retval; + } + + // check active again - destructor sequence + if ( !active() ) return retval; + + // read port and copy to return value + int nread = port().read( buf, sizeof (buf), 0 ); + + if (nread >= 0) { + retval.copy( nread, buf ); + if ((size_t) nread == sizeof (buf)) + { + retval << read(); + } + } + else + { + if ( errno != EAGAIN ) + { + ostringstream os; + os << "Surface: error reading from port: " << port().name(); + os << ": " << errno << fetch_errmsg( errno ); + + cout << os.str() << endl; + inactive_event(); + throw MackieControlException( os.str() ); + } + } + return retval; +} + +void SurfacePort::write( const MidiByteArray & mba ) +{ + //if ( mba[0] == 0xf0 ) cout << "SurfacePort::write: " << mba << endl; + //cout << "SurfacePort::write: " << mba << endl; + + // check active before and after lock - to make sure + // that the destructor doesn't destroy the mutex while + // it's still in use + if ( !active() ) return; + Glib::RecMutex::Lock lock( _rwlock ); + if ( !active() ) return; + + int count = port().write( mba.bytes().get(), mba.size(), 0 ); + if ( count != (int)mba.size() ) + { + if ( errno != EAGAIN ) + { + ostringstream os; + os << "Surface: couldn't write to port " << port().name(); + os << ": " << errno << fetch_errmsg( errno ); + + cout << os.str(); + inactive_event(); + throw MackieControlException( os.str() ); + } + } + //if ( mba[0] == 0xf0 ) cout << "SurfacePort::write " << count << endl; +} + +void SurfacePort::write_sysex( const MidiByteArray & mba ) +{ + MidiByteArray buf; + buf << sysex_hdr() << mba << MIDI::eox; + write( buf ); +} + +void SurfacePort::write_sysex( MIDI::byte msg ) +{ + MidiByteArray buf; + buf << sysex_hdr() << msg << MIDI::eox; + write( buf ); +} + +// This should be moved to midi++ at some point +ostream & operator << ( ostream & os, const MIDI::Port & port ) +{ + os << "device: " << port.device(); + os << "; "; + os << "name: " << port.name(); + os << "; "; + os << "type: " << port.type(); + os << "; "; + os << "mode: " << port.mode(); + os << "; "; + os << "ok: " << port.ok(); + os << "; "; + os << "number: " << port.number(); + os << "; "; + return os; +} + +ostream & Mackie::operator << ( ostream & os, const SurfacePort & port ) +{ + os << "{ "; + os << "device: " << port.port().device(); + os << "; "; + os << "name: " << port.port().name(); + os << "; "; + os << "number: " << port.number(); + os << " }"; + return os; +} diff --git a/libs/surfaces/mackie/surface_port.h b/libs/surfaces/mackie/surface_port.h new file mode 100644 index 0000000000..87419f1bcd --- /dev/null +++ b/libs/surfaces/mackie/surface_port.h @@ -0,0 +1,100 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#ifndef surface_port_h +#define surface_port_h + +#include <sigc++/signal.h> +#include <glibmm/thread.h> + +#include "midi_byte_array.h" +#include "types.h" + +namespace MIDI { + class Port; +} + +namespace Mackie +{ + +/** + Make a relationship between a midi port and a Mackie device. +*/ +class SurfacePort : public sigc::trackable +{ +public: + SurfacePort( MIDI::Port & port, int number ); + virtual ~SurfacePort(); + + // when this is successful, active() should return true + virtual void open() = 0; + + // subclasses should call this before doing their own close + virtual void close() = 0; + + /// read bytes from the port. They'll either end up in the + /// parser, or if that's not active they'll be returned + MidiByteArray read(); + + /// an easier way to output bytes via midi + void write( const MidiByteArray & ); + + /// write a sysex message + void write_sysex( const MidiByteArray & mba ); + void write_sysex( MIDI::byte msg ); + + // return the correct sysex header for this port + virtual const MidiByteArray & sysex_hdr() const = 0; + + MIDI::Port & port() { return _port; } + const MIDI::Port & port() const { return _port; } + + // all control notofications are sent from here + sigc::signal<void, SurfacePort &, Control &, const ControlState &> control_event; + + // emitted just before the port goes into initialisation + // where it tries to establish that its device is connected + sigc::signal<void> init_event; + + // emitted when the port completes initialisation successfully + sigc::signal<void> active_event; + + // emitted when the port goes inactive (ie a read or write failed) + sigc::signal<void> inactive_event; + + // the port number - master is 0, extenders are 1,2,3,4 + virtual int number() const { return _number; } + + // number of strips handled by this port. Usually 8. + virtual int strips() const = 0; + + virtual bool active() const { return _active; } + virtual void active( bool yn ) { _active = yn; } + +private: + MIDI::Port & _port; + int _number; + bool _active; + + Glib::RecMutex _rwlock; +}; + +std::ostream & operator << ( std::ostream & , const SurfacePort & port ); + +} + +#endif diff --git a/libs/surfaces/mackie/test.cc b/libs/surfaces/mackie/test.cc new file mode 100644 index 0000000000..351058523f --- /dev/null +++ b/libs/surfaces/mackie/test.cc @@ -0,0 +1,25 @@ +#include <iostream> +#include <string> +#include <sstream> +#include <vector> +#include <algorithm> +#include <cstdarg> +#include <iomanip> + +#include "midi_byte_array.h" + +using namespace std; + +namespace MIDI { + typedef unsigned char byte; + byte sysex = 0xf0; + byte eox = 0xf7; +} + +int main() +{ + MidiByteArray bytes( 4, 0xf0, 0x01, 0x03, 0x7f ); + cout << bytes << endl; + return 0; +} + diff --git a/libs/surfaces/mackie/types.cc b/libs/surfaces/mackie/types.cc new file mode 100644 index 0000000000..d2818d7340 --- /dev/null +++ b/libs/surfaces/mackie/types.cc @@ -0,0 +1,9 @@ +#include "types.h" + +namespace Mackie +{ + LedState on( LedState::on ); + LedState off( LedState::off ); + LedState flashing( LedState::flashing ); + LedState none( LedState::none ); +} diff --git a/libs/surfaces/mackie/types.h b/libs/surfaces/mackie/types.h new file mode 100644 index 0000000000..2b47e15640 --- /dev/null +++ b/libs/surfaces/mackie/types.h @@ -0,0 +1,93 @@ +/* + Copyright (C) 2006,2007 John Anderson + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#ifndef mackie_types_h +#define mackie_types_h + +namespace Mackie +{ + +/** + This started off as an enum, but it got really annoying + typing ? on : off +*/ +class LedState +{ +public: + enum state_t { none, off, flashing, on }; + LedState() : _state( none ) {} + LedState( bool yn ): _state( yn ? on : off ) {} + LedState( state_t state ): _state( state ) {} + + bool operator == ( const LedState & other ) const + { + return state() == other.state(); + } + + bool operator != ( const LedState & other ) const + { + return state() != other.state(); + } + + state_t state() const { return _state; } + +private: + state_t _state; +}; + +extern LedState on; +extern LedState off; +extern LedState flashing; +extern LedState none; + +enum ButtonState { neither = -1, release = 0, press = 1 }; + +/** + Contains the state for a control, with some convenience + constructors +*/ +struct ControlState +{ + ControlState(): pos(0.0), delta(0.0), button_state(neither) {} + + ControlState( LedState ls ): pos(0.0), delta(0.0), led_state(ls), button_state(neither) {} + + // Note that this sets both pos and delta to the flt value + ControlState( LedState ls, float flt ): pos(flt), delta(flt), ticks(0), led_state(ls), button_state(neither) {} + ControlState( float flt ): pos(flt), delta(flt), ticks(0), led_state(none), button_state(neither) {} + ControlState( float flt, int tcks ): pos(flt), delta(flt), ticks(tcks), led_state(none), button_state(neither) {} + ControlState( ButtonState bs ): pos(0.0), delta(0.0), ticks(0), led_state(none), button_state(bs) {} + + float pos; + float delta; + int ticks; + LedState led_state; + ButtonState button_state; +}; + +class Control; +class Fader; +class Button; +class Strip; +class Group; +class Pot; +class Led; +class LedRing; + +} + +#endif |