refactor Push2 GUI into modular "layouts" that encapsulate a given screen, its drawing and event handling

This commit is contained in:
Paul Davis 2016-07-10 10:49:39 -04:00
parent 86578ea0cc
commit 14d80ec5f8
7 changed files with 1113 additions and 818 deletions

View file

@ -213,7 +213,7 @@ Push2::button_play ()
return; return;
} }
if (modifier_state & ModShift) { if (_modifier_state & ModShift) {
goto_start (session->transport_rolling()); goto_start (session->transport_rolling());
return; return;
} }
@ -258,13 +258,13 @@ Push2::button_page_left ()
void void
Push2::button_right () Push2::button_right ()
{ {
switch_bank (max (0, bank_start + 8)); _current_layout->button_right ();
} }
void void
Push2::button_left () Push2::button_left ()
{ {
switch_bank (max (0, bank_start - 8)); _current_layout->button_left ();
} }
void void
@ -331,45 +331,19 @@ Push2::button_clip ()
void void
Push2::button_upper (uint32_t n) Push2::button_upper (uint32_t n)
{ {
if (!stripable[n]) { _current_layout->button_upper (n);
return;
}
if (modifier_state & ModShift) {
boost::shared_ptr<AutomationControl> sc = stripable[n]->rec_enable_control ();
if (sc) {
sc->set_value (!sc->get_value(), PBD::Controllable::UseGroup);
}
} else {
boost::shared_ptr<SoloControl> sc = stripable[n]->solo_control ();
if (sc) {
sc->set_value (!sc->self_soloed(), PBD::Controllable::UseGroup);
}
}
} }
void void
Push2::button_lower (uint32_t n) Push2::button_lower (uint32_t n)
{ {
if (!stripable[n]) { _current_layout->button_lower (n);
return;
}
if (modifier_state & ModSelect) {
SetStripableSelection (stripable[n]);
} else {
boost::shared_ptr<MuteControl> mc = stripable[n]->mute_control ();
if (mc) {
mc->set_value (!mc->muted_by_self(), PBD::Controllable::UseGroup);
}
}
} }
void void
Push2::button_undo () Push2::button_undo ()
{ {
if (modifier_state & ModShift) { if (_modifier_state & ModShift) {
ControlProtocol::Redo (); ControlProtocol::Redo ();
} else { } else {
ControlProtocol::Undo (); ControlProtocol::Undo ();
@ -379,56 +353,56 @@ Push2::button_undo ()
void void
Push2::button_fwd32t () Push2::button_fwd32t ()
{ {
const int n = (modifier_state & ModShift) ? 8 : 0; const int n = (_modifier_state & ModShift) ? 8 : 0;
goto_nth_marker (0+n); goto_nth_marker (0+n);
} }
void void
Push2::button_fwd32 () Push2::button_fwd32 ()
{ {
const int n = (modifier_state & ModShift) ? 8 : 0; const int n = (_modifier_state & ModShift) ? 8 : 0;
goto_nth_marker (1+n); goto_nth_marker (1+n);
} }
void void
Push2::button_fwd16t () Push2::button_fwd16t ()
{ {
const int n = (modifier_state & ModShift) ? 8 : 0; const int n = (_modifier_state & ModShift) ? 8 : 0;
goto_nth_marker (2+n); goto_nth_marker (2+n);
} }
void void
Push2::button_fwd16 () Push2::button_fwd16 ()
{ {
const int n = (modifier_state & ModShift) ? 8 : 0; const int n = (_modifier_state & ModShift) ? 8 : 0;
goto_nth_marker (3+n); goto_nth_marker (3+n);
} }
void void
Push2::button_fwd8t () Push2::button_fwd8t ()
{ {
const int n = (modifier_state & ModShift) ? 8 : 0; const int n = (_modifier_state & ModShift) ? 8 : 0;
goto_nth_marker (4+n); goto_nth_marker (4+n);
} }
void void
Push2::button_fwd8 () Push2::button_fwd8 ()
{ {
const int n = (modifier_state & ModShift) ? 8 : 0; const int n = (_modifier_state & ModShift) ? 8 : 0;
goto_nth_marker (5+n); goto_nth_marker (5+n);
} }
void void
Push2::button_fwd4t () Push2::button_fwd4t ()
{ {
const int n = (modifier_state & ModShift) ? 8 : 0; const int n = (_modifier_state & ModShift) ? 8 : 0;
goto_nth_marker (6+n); goto_nth_marker (6+n);
} }
void void
Push2::button_fwd4 () Push2::button_fwd4 ()
{ {
const int n = (modifier_state & ModShift) ? 8 : 0; const int n = (_modifier_state & ModShift) ? 8 : 0;
goto_nth_marker (7+n); goto_nth_marker (7+n);
} }
@ -466,93 +440,30 @@ Push2::button_shift_long_press ()
void void
Push2::button_select_press () Push2::button_select_press ()
{ {
start_select (); cerr << "start select\n";
_modifier_state = ModifierState (_modifier_state | ModSelect);
Button* b = id_button_map[Select];
b->set_color (Push2::LED::White);
b->set_state (Push2::LED::Blinking16th);
write (b->state_msg());
_current_layout->button_select_press ();
} }
void void
Push2::button_select_release () Push2::button_select_release ()
{ {
if (!(modifier_state & ModSelect)) { if (_modifier_state & ModSelect) {
/* somebody else used us as a modifier */ cerr << "end select\n";
return; _modifier_state = ModifierState (_modifier_state & ~(ModSelect));
Button* b = id_button_map[Select];
b->timeout_connection.disconnect ();
b->set_color (Push2::LED::White);
b->set_state (Push2::LED::OneShot24th);
write (b->state_msg());
} }
end_select (); _current_layout->button_select_release ();
int selected = -1;
for (int n = 0; n < 8; ++n) {
if (stripable[n]) {
if (stripable[n]->presentation_info().selected()) {
selected = n;
break;
}
}
}
if (selected < 0) {
/* no visible track selected, select first (if any) */
if (stripable[0]) {
SetStripableSelection (stripable[0]);
}
} else {
if (modifier_state & ModShift) {
std::cerr << "select prev\n";
/* select prev */
if (selected == 0) {
/* current selected is leftmost ... cancel selection,
switch banks by one, and select leftmost
*/
if (bank_start != 0) {
ClearStripableSelection ();
switch_bank (bank_start-1);
if (stripable[0]) {
SetStripableSelection (stripable[0]);
}
}
} else {
/* select prev, if any */
int n = selected - 1;
while (n >= 0 && !stripable[n]) {
--n;
}
if (n >= 0) {
SetStripableSelection (stripable[n]);
}
}
} else {
std::cerr << "select next\n";
/* select next */
if (selected == 7) {
/* current selected is rightmost ... cancel selection,
switch banks by one, and select righmost
*/
ToggleStripableSelection (stripable[selected]);
switch_bank (bank_start+1);
if (stripable[7]) {
SetStripableSelection (stripable[7]);
}
} else {
/* select next, if any */
int n = selected + 1;
while (n < 8 && !stripable[n]) {
++n;
}
if (n != 8) {
SetStripableSelection (stripable[n]);
}
}
}
}
} }
void void
@ -622,9 +533,9 @@ Push2::button_layout_press ()
void void
Push2::button_scale_press () Push2::button_scale_press ()
{ {
if (current_menu != scale_menu) { if (_current_layout != scale_layout) {
show_scale_menu (); _current_layout = scale_layout;
} else { } else {
cancel_menu (); _current_layout = mix_layout;
} }
} }

View file

@ -0,0 +1,38 @@
/*
Copyright (C) 2016 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 "push2.h"
using namespace ARDOUR;
using namespace ArdourSurface;
Push2Layout::Push2Layout (Push2& p, Session& s)
: p2 (p)
, session (s)
{
}
Push2Layout::~Push2Layout ()
{
}
bool
Push2Layout::mapped () const
{
return p2.current_layout() == this;
}

570
libs/surfaces/push2/mix.cc Normal file
View file

@ -0,0 +1,570 @@
/*
Copyright (C) 2016 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 <pangomm/layout.h>
#include "pbd/compose.h"
#include "pbd/convert.h"
#include "pbd/debug.h"
#include "pbd/failed_constructor.h"
#include "pbd/file_utils.h"
#include "pbd/search_path.h"
#include "pbd/enumwriter.h"
#include "midi++/parser.h"
#include "timecode/time.h"
#include "timecode/bbt_time.h"
#include "ardour/async_midi_port.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "ardour/filesystem_paths.h"
#include "ardour/midiport_manager.h"
#include "ardour/midi_track.h"
#include "ardour/midi_port.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "push2.h"
#include "i18n.h"
using namespace ARDOUR;
using namespace std;
using namespace PBD;
using namespace Glib;
using namespace ArdourSurface;
MixLayout::MixLayout (Push2& p, Session& s, Cairo::RefPtr<Cairo::Context> context)
: Push2Layout (p, s)
, bank_start (0)
{
tc_clock_layout = Pango::Layout::create (context);
bbt_clock_layout = Pango::Layout::create (context);
Pango::FontDescription fd ("Sans Bold 24");
tc_clock_layout->set_font_description (fd);
bbt_clock_layout->set_font_description (fd);
Pango::FontDescription fd2 ("Sans 10");
for (int n = 0; n < 8; ++n) {
upper_layout[n] = Pango::Layout::create (context);
upper_layout[n]->set_font_description (fd2);
upper_layout[n]->set_text ("solo");
lower_layout[n] = Pango::Layout::create (context);
lower_layout[n]->set_font_description (fd2);
lower_layout[n]->set_text ("mute");
}
Pango::FontDescription fd3 ("Sans Bold 10");
for (int n = 0; n < 8; ++n) {
mid_layout[n] = Pango::Layout::create (context);
mid_layout[n]->set_font_description (fd3);
}
switch_bank (0);
}
MixLayout::~MixLayout ()
{
}
bool
MixLayout::redraw (Cairo::RefPtr<Cairo::Context> context) const
{
framepos_t audible = session.audible_frame();
Timecode::Time TC;
bool negative = false;
string tc_clock_text;
string bbt_clock_text;
if (audible < 0) {
audible = -audible;
negative = true;
}
session.timecode_time (audible, TC);
TC.negative = TC.negative || negative;
tc_clock_text = Timecode::timecode_format_time(TC);
Timecode::BBT_Time bbt = session.tempo_map().bbt_at_frame (audible);
char buf[16];
#define BBT_BAR_CHAR "|"
if (negative) {
snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
bbt.bars, bbt.beats, bbt.ticks);
} else {
snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
bbt.bars, bbt.beats, bbt.ticks);
}
bbt_clock_text = buf;
bool dirty = false;
if (tc_clock_text != tc_clock_layout->get_text()) {
dirty = true;
tc_clock_layout->set_text (tc_clock_text);
}
if (bbt_clock_text != tc_clock_layout->get_text()) {
dirty = true;
bbt_clock_layout->set_text (bbt_clock_text);
}
string mid_text;
for (int n = 0; n < 8; ++n) {
if (stripable[n]) {
mid_text = short_version (stripable[n]->name(), 10);
if (mid_text != mid_layout[n]->get_text()) {
mid_layout[n]->set_text (mid_text);
dirty = true;
}
}
}
if (!dirty) {
return false;
}
context->set_source_rgb (0.764, 0.882, 0.882);
context->rectangle (0, 0, 960, 160);
context->fill ();
/* clocks */
context->set_source_rgb (0.23, 0.0, 0.349);
context->move_to (650, 25);
tc_clock_layout->update_from_cairo_context (context);
tc_clock_layout->show_in_cairo_context (context);
context->move_to (650, 60);
bbt_clock_layout->update_from_cairo_context (context);
bbt_clock_layout->show_in_cairo_context (context);
for (int n = 0; n < 8; ++n) {
context->move_to (10 + (n*120), 2);
upper_layout[n]->update_from_cairo_context (context);
upper_layout[n]->show_in_cairo_context (context);
}
for (int n = 0; n < 8; ++n) {
context->move_to (10 + (n*120), 140);
lower_layout[n]->update_from_cairo_context (context);
lower_layout[n]->show_in_cairo_context (context);
}
for (int n = 0; n < 8; ++n) {
if (stripable[n] && stripable[n]->presentation_info().selected()) {
context->rectangle (10 + (n*120) - 5, 115, 120, 22);
context->set_source_rgb (1.0, 0.737, 0.172);
context->fill();
}
context->set_source_rgb (0.0, 0.0, 0.0);
context->move_to (10 + (n*120), 120);
mid_layout[n]->update_from_cairo_context (context);
mid_layout[n]->show_in_cairo_context (context);
}
return true;
}
void
MixLayout::button_upper (uint32_t n)
{
if (!stripable[n]) {
return;
}
if (p2.modifier_state() & Push2::ModShift) {
boost::shared_ptr<AutomationControl> sc = stripable[n]->rec_enable_control ();
if (sc) {
sc->set_value (!sc->get_value(), PBD::Controllable::UseGroup);
}
} else {
boost::shared_ptr<SoloControl> sc = stripable[n]->solo_control ();
if (sc) {
sc->set_value (!sc->self_soloed(), PBD::Controllable::UseGroup);
}
}
}
void
MixLayout::button_lower (uint32_t n)
{
if (!stripable[n]) {
return;
}
if (p2.modifier_state() & Push2::ModSelect) {
ControlProtocol::SetStripableSelection (stripable[n]);
} else {
boost::shared_ptr<MuteControl> mc = stripable[n]->mute_control ();
if (mc) {
mc->set_value (!mc->muted_by_self(), PBD::Controllable::UseGroup);
}
}
}
void
MixLayout::strip_vpot (int n, int delta)
{
if (stripable[n]) {
boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
if (ac) {
ac->set_value (ac->get_value() + ((2.0/64.0) * delta), PBD::Controllable::UseGroup);
}
}
}
void
MixLayout::strip_vpot_touch (int n, bool touching)
{
if (stripable[n]) {
boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
if (ac) {
if (touching) {
ac->start_touch (session.audible_frame());
} else {
ac->stop_touch (true, session.audible_frame());
}
}
}
}
void
MixLayout::stripable_property_change (PropertyChange const& what_changed, int which)
{
if (what_changed.contains (Properties::selected)) {
if (!stripable[which]) {
return;
}
/* cancel string, which will cause a redraw on the next update
* cycle. The redraw will reflect selected status
*/
mid_layout[which]->set_text (string());
}
}
void
MixLayout::solo_change (int n)
{
Push2::ButtonID bid;
switch (n) {
case 0:
bid = Push2::Upper1;
break;
case 1:
bid = Push2::Upper2;
break;
case 2:
bid = Push2::Upper3;
break;
case 3:
bid = Push2::Upper4;
break;
case 4:
bid = Push2::Upper5;
break;
case 5:
bid = Push2::Upper6;
break;
case 6:
bid = Push2::Upper7;
break;
case 7:
bid = Push2::Upper8;
break;
default:
return;
}
boost::shared_ptr<SoloControl> ac = stripable[n]->solo_control ();
if (!ac) {
return;
}
Push2::Button* b = p2.button_by_id (bid);
if (ac->soloed()) {
b->set_color (Push2::LED::Green);
} else {
b->set_color (Push2::LED::Black);
}
if (ac->soloed_by_others_upstream() || ac->soloed_by_others_downstream()) {
b->set_state (Push2::LED::Blinking4th);
} else {
b->set_state (Push2::LED::OneShot24th);
}
p2.write (b->state_msg());
}
void
MixLayout::mute_change (int n)
{
Push2::ButtonID bid;
if (!stripable[n]) {
return;
}
cerr << "Mute changed on " << n << ' ' << stripable[n]->name() << endl;
switch (n) {
case 0:
bid = Push2::Lower1;
break;
case 1:
bid = Push2::Lower2;
break;
case 2:
bid = Push2::Lower3;
break;
case 3:
bid = Push2::Lower4;
break;
case 4:
bid = Push2::Lower5;
break;
case 5:
bid = Push2::Lower6;
break;
case 6:
bid = Push2::Lower7;
break;
case 7:
bid = Push2::Lower8;
break;
default:
return;
}
boost::shared_ptr<MuteControl> mc = stripable[n]->mute_control ();
if (!mc) {
return;
}
Push2::Button* b = p2.button_by_id (bid);
if (Config->get_show_solo_mutes() && !Config->get_solo_control_is_listen_control ()) {
if (mc->muted_by_self ()) {
/* full mute */
b->set_color (Push2::LED::Blue);
b->set_state (Push2::LED::OneShot24th);
cerr << "FULL MUTE1\n";
} else if (mc->muted_by_others_soloing () || mc->muted_by_masters ()) {
/* this will reflect both solo mutes AND master mutes */
b->set_color (Push2::LED::Blue);
b->set_state (Push2::LED::Blinking4th);
cerr << "OTHER MUTE1\n";
} else {
/* no mute at all */
b->set_color (Push2::LED::Black);
b->set_state (Push2::LED::OneShot24th);
cerr << "NO MUTE1\n";
}
} else {
if (mc->muted_by_self()) {
/* full mute */
b->set_color (Push2::LED::Blue);
b->set_state (Push2::LED::OneShot24th);
cerr << "FULL MUTE2\n";
} else if (mc->muted_by_masters ()) {
/* this shows only master mutes, not mute-by-others-soloing */
b->set_color (Push2::LED::Blue);
b->set_state (Push2::LED::Blinking4th);
cerr << "OTHER MUTE1\n";
} else {
/* no mute at all */
b->set_color (Push2::LED::Black);
b->set_state (Push2::LED::OneShot24th);
cerr << "NO MUTE2\n";
}
}
p2.write (b->state_msg());
}
void
MixLayout::switch_bank (uint32_t base)
{
stripable_connections.drop_connections ();
/* try to get the first stripable for the requested bank */
stripable[0] = session.get_remote_nth_stripable (base, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
if (!stripable[0]) {
return;
}
/* at least one stripable in this bank */
bank_start = base;
stripable[1] = session.get_remote_nth_stripable (base+1, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[2] = session.get_remote_nth_stripable (base+2, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[3] = session.get_remote_nth_stripable (base+3, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[4] = session.get_remote_nth_stripable (base+4, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[5] = session.get_remote_nth_stripable (base+5, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[6] = session.get_remote_nth_stripable (base+6, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[7] = session.get_remote_nth_stripable (base+7, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
for (int n = 0; n < 8; ++n) {
if (!stripable[n]) {
continue;
}
/* stripable goes away? refill the bank, starting at the same point */
stripable[n]->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::switch_bank, this, bank_start), &p2);
boost::shared_ptr<AutomationControl> sc = stripable[n]->solo_control();
if (sc) {
sc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::solo_change, this, n), &p2);
}
boost::shared_ptr<AutomationControl> mc = stripable[n]->mute_control();
if (mc) {
mc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::mute_change, this, n), &p2);
}
stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::stripable_property_change, this, _1, n), &p2);
solo_change (n);
mute_change (n);
}
}
void
MixLayout::button_right ()
{
switch_bank (max (0, bank_start + 8));
}
void
MixLayout::button_left ()
{
switch_bank (max (0, bank_start - 8));
}
void
MixLayout::button_select_press ()
{
}
void
MixLayout::button_select_release ()
{
if (!(p2.modifier_state() & Push2::ModSelect)) {
/* somebody else used us as a modifier */
return;
}
int selected = -1;
for (int n = 0; n < 8; ++n) {
if (stripable[n]) {
if (stripable[n]->presentation_info().selected()) {
selected = n;
break;
}
}
}
if (selected < 0) {
/* no visible track selected, select first (if any) */
if (stripable[0]) {
ControlProtocol::SetStripableSelection (stripable[0]);
}
} else {
if (p2.modifier_state() & Push2::ModShift) {
std::cerr << "select prev\n";
/* select prev */
if (selected == 0) {
/* current selected is leftmost ... cancel selection,
switch banks by one, and select leftmost
*/
if (bank_start != 0) {
ControlProtocol::ClearStripableSelection ();
switch_bank (bank_start-1);
if (stripable[0]) {
ControlProtocol::SetStripableSelection (stripable[0]);
}
}
} else {
/* select prev, if any */
int n = selected - 1;
while (n >= 0 && !stripable[n]) {
--n;
}
if (n >= 0) {
ControlProtocol::SetStripableSelection (stripable[n]);
}
}
} else {
std::cerr << "select next\n";
/* select next */
if (selected == 7) {
/* current selected is rightmost ... cancel selection,
switch banks by one, and select righmost
*/
ControlProtocol::ToggleStripableSelection (stripable[selected]);
switch_bank (bank_start+1);
if (stripable[7]) {
ControlProtocol::SetStripableSelection (stripable[7]);
}
} else {
/* select next, if any */
int n = selected + 1;
while (n < 8 && !stripable[n]) {
++n;
}
if (n != 8) {
ControlProtocol::SetStripableSelection (stripable[n]);
}
}
}
}
}

View file

@ -1,25 +1,21 @@
/* /*
Copyright (C) 2016 Paul Davis Copyright (C) 2016 Paul Davis
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/ */
#include <cairomm/context.h>
#include <cairomm/surface.h>
#include <pangomm/layout.h>
#include "pbd/compose.h" #include "pbd/compose.h"
#include "pbd/convert.h" #include "pbd/convert.h"
#include "pbd/debug.h" #include "pbd/debug.h"
@ -46,14 +42,14 @@
#include "gui.h" #include "gui.h"
#include "menu.h" #include "menu.h"
#include "i18n.h"
using namespace ARDOUR; using namespace ARDOUR;
using namespace std; using namespace std;
using namespace PBD; using namespace PBD;
using namespace Glib; using namespace Glib;
using namespace ArdourSurface; using namespace ArdourSurface;
#include "i18n.h"
#include "pbd/abstract_ui.cc" // instantiate template #include "pbd/abstract_ui.cc" // instantiate template
const int Push2::cols = 960; const int Push2::cols = 960;
@ -122,9 +118,10 @@ Push2::Push2 (ARDOUR::Session& s)
, handle (0) , handle (0)
, device_buffer (0) , device_buffer (0)
, frame_buffer (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, cols, rows)) , frame_buffer (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, cols, rows))
, modifier_state (None) , _modifier_state (None)
, splash_start (0) , splash_start (0)
, bank_start (0) , _current_layout (0)
, drawn_layout (0)
, connection_state (ConnectionState (0)) , connection_state (ConnectionState (0))
, gui (0) , gui (0)
, _mode (MusicalMode::IonianMajor) , _mode (MusicalMode::IonianMajor)
@ -133,42 +130,26 @@ Push2::Push2 (ARDOUR::Session& s)
, _in_key (true) , _in_key (true)
, octave_shift (0) , octave_shift (0)
, percussion (false) , percussion (false)
, current_menu (0)
, drawn_menu (0)
{ {
context = Cairo::Context::create (frame_buffer); context = Cairo::Context::create (frame_buffer);
tc_clock_layout = Pango::Layout::create (context);
bbt_clock_layout = Pango::Layout::create (context);
Pango::FontDescription fd ("Sans Bold 24");
tc_clock_layout->set_font_description (fd);
bbt_clock_layout->set_font_description (fd);
Pango::FontDescription fd2 ("Sans 10");
for (int n = 0; n < 8; ++n) {
upper_layout[n] = Pango::Layout::create (context);
upper_layout[n]->set_font_description (fd2);
upper_layout[n]->set_text ("solo");
lower_layout[n] = Pango::Layout::create (context);
lower_layout[n]->set_font_description (fd2);
lower_layout[n]->set_text ("mute");
}
Pango::FontDescription fd3 ("Sans Bold 10");
for (int n = 0; n < 8; ++n) {
mid_layout[n] = Pango::Layout::create (context);
mid_layout[n]->set_font_description (fd3);
}
build_pad_table (); build_pad_table ();
build_maps (); build_maps ();
build_scale_menu ();
/* master cannot be removed, so no need to connect to going-away signal */
master = session->master_out ();
if (open ()) { if (open ()) {
throw failed_constructor (); throw failed_constructor ();
} }
StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_selection_change, this, _1), this); ControlProtocol::StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_selection_change, this, _1), this);
/* catch current selection, if any */
{
StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected()));
stripable_selection_change (sp);
}
/* catch arrival and departure of Push2 itself */ /* catch arrival and departure of Push2 itself */
ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&Push2::port_registration_handler, this), this); ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&Push2::port_registration_handler, this), this);
@ -281,6 +262,10 @@ Push2::open ()
connect_to_parser (); connect_to_parser ();
mix_layout = new MixLayout (*this, *session, context);
scale_layout = new ScaleLayout (*this, *session, context);
_current_layout = mix_layout;
return 0; return 0;
} }
@ -317,7 +302,6 @@ Push2::close ()
vblank_connection.disconnect (); vblank_connection.disconnect ();
periodic_connection.disconnect (); periodic_connection.disconnect ();
session_connections.drop_connections (); session_connections.drop_connections ();
stripable_connections.drop_connections ();
if (handle) { if (handle) {
libusb_release_interface (handle, 0x00); libusb_release_interface (handle, 0x00);
@ -325,10 +309,6 @@ Push2::close ()
handle = 0; handle = 0;
} }
for (int n = 0; n < 8; ++n) {
stripable[n].reset ();
}
delete [] device_frame_buffer; delete [] device_frame_buffer;
device_frame_buffer = 0; device_frame_buffer = 0;
@ -509,133 +489,34 @@ Push2::blit_to_device_frame_buffer ()
bool bool
Push2::redraw () Push2::redraw ()
{ {
string tc_clock_text;
string bbt_clock_text;
if (splash_start) { if (splash_start) {
if (get_microseconds() - splash_start > 4000000) {
/* display splash for 3 seconds */
if (get_microseconds() - splash_start > 3000000) {
splash_start = 0; splash_start = 0;
} else { } else {
return false; return false;
} }
} }
if (current_menu) { Glib::Threads::Mutex::Lock lm (layout_lock, Glib::Threads::TRY_LOCK);
if (current_menu->dirty() || drawn_menu != current_menu) {
/* fill background */
context->set_source_rgb (0.764, 0.882, 0.882);
context->rectangle (0, 0, 960, 160);
context->fill ();
/* now menu */
current_menu->redraw (context);
drawn_menu = current_menu;
return true;
}
return false;
} else {
drawn_menu = 0;
}
if (session) { if (!lm.locked()) {
framepos_t audible = session->audible_frame(); /* can't get layout, no re-render needed */
Timecode::Time TC;
bool negative = false;
if (audible < 0) {
audible = -audible;
negative = true;
}
session->timecode_time (audible, TC);
TC.negative = TC.negative || negative;
tc_clock_text = Timecode::timecode_format_time(TC);
Timecode::BBT_Time bbt = session->tempo_map().bbt_at_frame (audible);
char buf[16];
#define BBT_BAR_CHAR "|"
if (negative) {
snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
bbt.bars, bbt.beats, bbt.ticks);
} else {
snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
bbt.bars, bbt.beats, bbt.ticks);
}
bbt_clock_text = buf;
}
bool dirty = false;
if (tc_clock_text != tc_clock_layout->get_text()) {
dirty = true;
tc_clock_layout->set_text (tc_clock_text);
}
if (bbt_clock_text != tc_clock_layout->get_text()) {
dirty = true;
bbt_clock_layout->set_text (bbt_clock_text);
}
string mid_text;
for (int n = 0; n < 8; ++n) {
if (stripable[n]) {
mid_text = short_version (stripable[n]->name(), 10);
if (mid_text != mid_layout[n]->get_text()) {
mid_layout[n]->set_text (mid_text);
dirty = true;
}
}
}
if (!dirty) {
return false; return false;
} }
context->set_source_rgb (0.764, 0.882, 0.882); bool render_needed = false;
context->rectangle (0, 0, 960, 160);
context->fill ();
context->set_source_rgb (0.23, 0.0, 0.349);
context->move_to (650, 25);
tc_clock_layout->update_from_cairo_context (context);
tc_clock_layout->show_in_cairo_context (context);
context->move_to (650, 60);
bbt_clock_layout->update_from_cairo_context (context);
bbt_clock_layout->show_in_cairo_context (context);
for (int n = 0; n < 8; ++n) { if (drawn_layout != _current_layout) {
context->move_to (10 + (n*120), 2); render_needed = true;
upper_layout[n]->update_from_cairo_context (context);
upper_layout[n]->show_in_cairo_context (context);
} }
for (int n = 0; n < 8; ++n) { bool dirty = _current_layout->redraw (context);
context->move_to (10 + (n*120), 140); drawn_layout = _current_layout;
lower_layout[n]->update_from_cairo_context (context);
lower_layout[n]->show_in_cairo_context (context);
}
for (int n = 0; n < 8; ++n) { return dirty || render_needed;
if (stripable[n] && stripable[n]->presentation_info().selected()) {
context->rectangle (10 + (n*120) - 5, 115, 120, 22);
context->set_source_rgb (1.0, 0.737, 0.172);
context->fill();
}
context->set_source_rgb (0.0, 0.0, 0.0);
context->move_to (10 + (n*120), 120);
mid_layout[n]->update_from_cairo_context (context);
mid_layout[n]->show_in_cairo_context (context);
}
/* render clock */
/* render foo */
/* render bar */
return true;
} }
bool bool
@ -709,14 +590,8 @@ Push2::set_active (bool yn)
init_buttons (true); init_buttons (true);
init_touch_strip (); init_touch_strip ();
set_pad_scale (_scale_root, _root_octave, _mode, _in_key); set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
switch_bank (0);
splash (); splash ();
/* catch current selection, if any */
{
StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected()));
stripable_selection_change (sp);
}
} else { } else {
@ -861,33 +736,33 @@ Push2::handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
switch (ev->controller_number) { switch (ev->controller_number) {
case 71: case 71:
strip_vpot (0, delta); _current_layout->strip_vpot (0, delta);
break; break;
case 72: case 72:
strip_vpot (1, delta); _current_layout->strip_vpot (1, delta);
break; break;
case 73: case 73:
strip_vpot (2, delta); _current_layout->strip_vpot (2, delta);
break; break;
case 74: case 74:
strip_vpot (3, delta); _current_layout->strip_vpot (3, delta);
break; break;
case 75: case 75:
strip_vpot (4, delta); _current_layout->strip_vpot (4, delta);
break; break;
case 76: case 76:
strip_vpot (5, delta); _current_layout->strip_vpot (5, delta);
break; break;
case 77: case 77:
strip_vpot (6, delta); _current_layout->strip_vpot (6, delta);
break; break;
case 78: case 78:
strip_vpot (7, delta); _current_layout->strip_vpot (7, delta);
break; break;
/* left side pair */ /* left side pair */
case 14: case 14:
strip_vpot (8, delta); other_vpot (8, delta);
break; break;
case 15: case 15:
other_vpot (1, delta); other_vpot (1, delta);
@ -913,28 +788,28 @@ Push2::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* e
switch (ev->note_number) { switch (ev->note_number) {
case 0: case 0:
strip_vpot_touch (0, ev->velocity > 64); _current_layout->strip_vpot_touch (0, ev->velocity > 64);
break; break;
case 1: case 1:
strip_vpot_touch (1, ev->velocity > 64); _current_layout->strip_vpot_touch (1, ev->velocity > 64);
break; break;
case 2: case 2:
strip_vpot_touch (2, ev->velocity > 64); _current_layout->strip_vpot_touch (2, ev->velocity > 64);
break; break;
case 3: case 3:
strip_vpot_touch (3, ev->velocity > 64); _current_layout->strip_vpot_touch (3, ev->velocity > 64);
break; break;
case 4: case 4:
strip_vpot_touch (4, ev->velocity > 64); _current_layout->strip_vpot_touch (4, ev->velocity > 64);
break; break;
case 5: case 5:
strip_vpot_touch (5, ev->velocity > 64); _current_layout->strip_vpot_touch (5, ev->velocity > 64);
break; break;
case 6: case 6:
strip_vpot_touch (6, ev->velocity > 64); _current_layout->strip_vpot_touch (6, ev->velocity > 64);
break; break;
case 7: case 7:
strip_vpot_touch (7, ev->velocity > 64); _current_layout->strip_vpot_touch (7, ev->velocity > 64);
break; break;
/* left side */ /* left side */
@ -1233,317 +1108,6 @@ Push2::set_state (const XMLNode & node, int version)
return retval; return retval;
} }
void
Push2::switch_bank (uint32_t base)
{
if (!session) {
return;
}
stripable_connections.drop_connections ();
/* try to get the first stripable for the requested bank */
stripable[0] = session->get_remote_nth_stripable (base, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
if (!stripable[0]) {
return;
}
/* at least one stripable in this bank */
bank_start = base;
stripable[1] = session->get_remote_nth_stripable (base+1, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[2] = session->get_remote_nth_stripable (base+2, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[3] = session->get_remote_nth_stripable (base+3, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[4] = session->get_remote_nth_stripable (base+4, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[5] = session->get_remote_nth_stripable (base+5, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[6] = session->get_remote_nth_stripable (base+6, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
stripable[7] = session->get_remote_nth_stripable (base+7, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
for (int n = 0; n < 8; ++n) {
if (!stripable[n]) {
continue;
}
/* stripable goes away? refill the bank, starting at the same point */
stripable[n]->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::switch_bank, this, bank_start), this);
boost::shared_ptr<AutomationControl> sc = stripable[n]->solo_control();
if (sc) {
sc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::solo_change, this, n), this);
}
boost::shared_ptr<AutomationControl> mc = stripable[n]->mute_control();
if (mc) {
mc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::mute_change, this, n), this);
}
stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_property_change, this, _1, n), this);
solo_change (n);
mute_change (n);
}
/* master cannot be removed, so no need to connect to going-away signal */
master = session->master_out ();
}
void
Push2::stripable_property_change (PropertyChange const& what_changed, int which)
{
if (what_changed.contains (Properties::selected)) {
if (!stripable[which]) {
return;
}
/* cancel string, which will cause a redraw on the next update
* cycle. The redraw will reflect selected status
*/
mid_layout[which]->set_text (string());
}
}
void
Push2::stripable_selection_change (StripableNotificationListPtr selected)
{
boost::shared_ptr<MidiPort> pad_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in)->shadow_port();
boost::shared_ptr<MidiTrack> current_midi_track = current_pad_target.lock();
boost::shared_ptr<MidiTrack> new_pad_target;
/* See if there's a MIDI track selected */
for (StripableNotificationList::iterator si = selected->begin(); si != selected->end(); ++si) {
new_pad_target = boost::dynamic_pointer_cast<MidiTrack> ((*si).lock());
if (new_pad_target) {
break;
}
}
if (new_pad_target) {
cerr << "new midi pad target " << new_pad_target->name() << endl;
} else {
cerr << "no midi pad target\n";
}
if (current_midi_track == new_pad_target) {
/* nothing to do */
return;
}
if (!new_pad_target) {
/* leave existing connection alone */
return;
}
/* disconnect from pad port, if appropriate */
if (current_midi_track && pad_port) {
cerr << "Disconnect pads from " << current_midi_track->name() << endl;
current_midi_track->input()->disconnect (current_midi_track->input()->nth(0), pad_port->name(), this);
}
/* now connect the pad port to this (newly) selected midi
* track, if indeed there is one.
*/
if (new_pad_target && pad_port) {
cerr << "Reconnect pads to " << new_pad_target->name() << endl;
new_pad_target->input()->connect (new_pad_target->input()->nth (0), pad_port->name(), this);
current_pad_target = new_pad_target;
} else {
current_pad_target.reset ();
}
}
void
Push2::solo_change (int n)
{
ButtonID bid;
switch (n) {
case 0:
bid = Upper1;
break;
case 1:
bid = Upper2;
break;
case 2:
bid = Upper3;
break;
case 3:
bid = Upper4;
break;
case 4:
bid = Upper5;
break;
case 5:
bid = Upper6;
break;
case 6:
bid = Upper7;
break;
case 7:
bid = Upper8;
break;
default:
return;
}
boost::shared_ptr<SoloControl> ac = stripable[n]->solo_control ();
if (!ac) {
return;
}
Button* b = id_button_map[bid];
if (ac->soloed()) {
b->set_color (LED::Green);
} else {
b->set_color (LED::Black);
}
if (ac->soloed_by_others_upstream() || ac->soloed_by_others_downstream()) {
b->set_state (LED::Blinking4th);
} else {
b->set_state (LED::OneShot24th);
}
write (b->state_msg());
}
void
Push2::mute_change (int n)
{
ButtonID bid;
if (!stripable[n]) {
return;
}
cerr << "Mute changed on " << n << ' ' << stripable[n]->name() << endl;
switch (n) {
case 0:
bid = Lower1;
break;
case 1:
bid = Lower2;
break;
case 2:
bid = Lower3;
break;
case 3:
bid = Lower4;
break;
case 4:
bid = Lower5;
break;
case 5:
bid = Lower6;
break;
case 6:
bid = Lower7;
break;
case 7:
bid = Lower8;
break;
default:
return;
}
boost::shared_ptr<MuteControl> mc = stripable[n]->mute_control ();
if (!mc) {
return;
}
Button* b = id_button_map[bid];
if (Config->get_show_solo_mutes() && !Config->get_solo_control_is_listen_control ()) {
if (mc->muted_by_self ()) {
/* full mute */
b->set_color (LED::Blue);
b->set_state (LED::OneShot24th);
cerr << "FULL MUTE1\n";
} else if (mc->muted_by_others_soloing () || mc->muted_by_masters ()) {
/* this will reflect both solo mutes AND master mutes */
b->set_color (LED::Blue);
b->set_state (LED::Blinking4th);
cerr << "OTHER MUTE1\n";
} else {
/* no mute at all */
b->set_color (LED::Black);
b->set_state (LED::OneShot24th);
cerr << "NO MUTE1\n";
}
} else {
if (mc->muted_by_self()) {
/* full mute */
b->set_color (LED::Blue);
b->set_state (LED::OneShot24th);
cerr << "FULL MUTE2\n";
} else if (mc->muted_by_masters ()) {
/* this shows only master mutes, not mute-by-others-soloing */
b->set_color (LED::Blue);
b->set_state (LED::Blinking4th);
cerr << "OTHER MUTE1\n";
} else {
/* no mute at all */
b->set_color (LED::Black);
b->set_state (LED::OneShot24th);
cerr << "NO MUTE2\n";
}
}
write (b->state_msg());
}
void
Push2::strip_vpot (int n, int delta)
{
if (current_menu) {
current_menu->step_active (n, delta);
return;
}
if (stripable[n]) {
boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
if (ac) {
ac->set_value (ac->get_value() + ((2.0/64.0) * delta), PBD::Controllable::UseGroup);
}
}
}
void
Push2::strip_vpot_touch (int n, bool touching)
{
if (current_menu) {
return;
}
if (stripable[n]) {
boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
if (ac) {
if (touching) {
ac->start_touch (session->audible_frame());
} else {
ac->stop_touch (true, session->audible_frame());
}
}
}
}
void void
Push2::other_vpot (int n, int delta) Push2::other_vpot (int n, int delta)
{ {
@ -1590,7 +1154,7 @@ void
Push2::start_shift () Push2::start_shift ()
{ {
cerr << "start shift\n"; cerr << "start shift\n";
modifier_state = ModifierState (modifier_state | ModShift); _modifier_state = ModifierState (_modifier_state | ModShift);
Button* b = id_button_map[Shift]; Button* b = id_button_map[Shift];
b->set_color (LED::White); b->set_color (LED::White);
b->set_state (LED::Blinking16th); b->set_state (LED::Blinking16th);
@ -1600,9 +1164,9 @@ Push2::start_shift ()
void void
Push2::end_shift () Push2::end_shift ()
{ {
if (modifier_state & ModShift) { if (_modifier_state & ModShift) {
cerr << "end shift\n"; cerr << "end shift\n";
modifier_state = ModifierState (modifier_state & ~(ModShift)); _modifier_state = ModifierState (_modifier_state & ~(ModShift));
Button* b = id_button_map[Shift]; Button* b = id_button_map[Shift];
b->timeout_connection.disconnect (); b->timeout_connection.disconnect ();
b->set_color (LED::White); b->set_color (LED::White);
@ -1611,31 +1175,6 @@ Push2::end_shift ()
} }
} }
void
Push2::start_select ()
{
cerr << "start select\n";
modifier_state = ModifierState (modifier_state | ModSelect);
Button* b = id_button_map[Select];
b->set_color (LED::White);
b->set_state (LED::Blinking16th);
write (b->state_msg());
}
void
Push2::end_select ()
{
if (modifier_state & ModSelect) {
cerr << "end select\n";
modifier_state = ModifierState (modifier_state & ~(ModSelect));
Button* b = id_button_map[Select];
b->timeout_connection.disconnect ();
b->set_color (LED::White);
b->set_state (LED::OneShot24th);
write (b->state_msg());
}
}
void void
Push2::splash () Push2::splash ()
{ {
@ -2005,72 +1544,69 @@ Push2::set_percussive_mode (bool yn)
PadChange (); /* EMIT SIGNAL */ PadChange (); /* EMIT SIGNAL */
} }
void Push2Layout*
Push2::set_menu (Push2Menu* m) Push2::current_layout () const
{ {
current_menu = m; Glib::Threads::Mutex::Lock lm (layout_lock);
drawn_menu = 0; return _current_layout;
} }
void void
Push2::build_scale_menu () Push2::stripable_selection_change (StripableNotificationListPtr selected)
{ {
vector<string> v; boost::shared_ptr<MidiPort> pad_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in)->shadow_port();
boost::shared_ptr<MidiTrack> current_midi_track = current_pad_target.lock();
boost::shared_ptr<MidiTrack> new_pad_target;
scale_menu = new Push2Menu (context); /* See if there's a MIDI track selected */
v.push_back ("Dorian"); for (StripableNotificationList::iterator si = selected->begin(); si != selected->end(); ++si) {
v.push_back ("IonianMajor");
v.push_back ("Minor");
v.push_back ("HarmonicMinor");
v.push_back ("MelodicMinorAscending");
v.push_back ("MelodicMinorDescending");
v.push_back ("Phrygian");
v.push_back ("Lydian");
v.push_back ("Mixolydian");
v.push_back ("Aeolian");
v.push_back ("Locrian");
v.push_back ("PentatonicMajor");
v.push_back ("PentatonicMinor");
v.push_back ("Chromatic");
v.push_back ("BluesScale");
v.push_back ("NeapolitanMinor");
v.push_back ("NeapolitanMajor");
v.push_back ("Oriental");
v.push_back ("DoubleHarmonic");
v.push_back ("Enigmatic");
v.push_back ("Hirajoshi");
v.push_back ("HungarianMinor");
v.push_back ("HungarianMajor");
v.push_back ("Kumoi");
v.push_back ("Iwato");
v.push_back ("Hindu");
v.push_back ("Spanish8Tone");
v.push_back ("Pelog");
v.push_back ("HungarianGypsy");
v.push_back ("Overtone");
v.push_back ("LeadingWholeTone");
v.push_back ("Arabian");
v.push_back ("Balinese");
v.push_back ("Gypsy");
v.push_back ("Mohammedan");
v.push_back ("Javanese");
v.push_back ("Persian");
v.push_back ("Algeria");
scale_menu->fill_column (0, v); new_pad_target = boost::dynamic_pointer_cast<MidiTrack> ((*si).lock());
v.clear (); if (new_pad_target) {
break;
}
}
if (new_pad_target) {
cerr << "new midi pad target " << new_pad_target->name() << endl;
} else {
cerr << "no midi pad target\n";
}
if (current_midi_track == new_pad_target) {
/* nothing to do */
return;
}
if (!new_pad_target) {
/* leave existing connection alone */
return;
}
/* disconnect from pad port, if appropriate */
if (current_midi_track && pad_port) {
cerr << "Disconnect pads from " << current_midi_track->name() << endl;
current_midi_track->input()->disconnect (current_midi_track->input()->nth(0), pad_port->name(), this);
}
/* now connect the pad port to this (newly) selected midi
* track, if indeed there is one.
*/
if (new_pad_target && pad_port) {
cerr << "Reconnect pads to " << new_pad_target->name() << endl;
new_pad_target->input()->connect (new_pad_target->input()->nth (0), pad_port->name(), this);
current_pad_target = new_pad_target;
} else {
current_pad_target.reset ();
}
} }
void Push2::Button*
Push2::show_scale_menu () Push2::button_by_id (ButtonID bid)
{ {
set_menu (scale_menu); return id_button_map[bid];
}
void
Push2::cancel_menu ()
{
set_menu (0);
} }

View file

@ -72,72 +72,12 @@ public:
class P2GUI; class P2GUI;
class Push2Menu; class Push2Menu;
class Push2Layout;
class Push2 : public ARDOUR::ControlProtocol class Push2 : public ARDOUR::ControlProtocol
, public AbstractUI<Push2Request> , public AbstractUI<Push2Request>
{ {
public: public:
Push2 (ARDOUR::Session&);
~Push2 ();
static bool probe ();
static void* request_factory (uint32_t);
std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles ();
bool has_editor () const { return true; }
void* get_gui () const;
void tear_down_gui ();
int set_active (bool yn);
XMLNode& get_state();
int set_state (const XMLNode & node, int version);
PBD::Signal0<void> ConnectionChange;
boost::shared_ptr<ARDOUR::Port> input_port();
boost::shared_ptr<ARDOUR::Port> output_port();
int pad_note (int row, int col) const;
PBD::Signal0<void> PadChange;
void set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey);
MusicalMode::Type mode() const { return _mode; }
int scale_root() const { return _scale_root; }
int root_octave() const { return _root_octave; }
bool in_key() const { return _in_key; }
static const int cols;
static const int rows;
private:
libusb_device_handle *handle;
uint8_t frame_header[16];
uint16_t* device_frame_buffer;
int device_buffer;
Cairo::RefPtr<Cairo::ImageSurface> frame_buffer;
sigc::connection vblank_connection;
sigc::connection periodic_connection;
enum ModifierState {
None = 0,
ModShift = 0x1,
ModSelect = 0x2,
};
ModifierState modifier_state;
static const int pixels_per_row;
void do_request (Push2Request*);
int stop ();
int open ();
int close ();
bool redraw ();
int blit_to_device_frame_buffer ();
bool vblank ();
enum ButtonID { enum ButtonID {
TapTempo, TapTempo,
Metronome, Metronome,
@ -341,6 +281,76 @@ class Push2 : public ARDOUR::ControlProtocol
: Button (bb, ex, press, release, long_press) {} : Button (bb, ex, press, release, long_press) {}
}; };
public:
Push2 (ARDOUR::Session&);
~Push2 ();
static bool probe ();
static void* request_factory (uint32_t);
std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles ();
bool has_editor () const { return true; }
void* get_gui () const;
void tear_down_gui ();
int set_active (bool yn);
XMLNode& get_state();
int set_state (const XMLNode & node, int version);
PBD::Signal0<void> ConnectionChange;
boost::shared_ptr<ARDOUR::Port> input_port();
boost::shared_ptr<ARDOUR::Port> output_port();
int pad_note (int row, int col) const;
PBD::Signal0<void> PadChange;
void set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey);
MusicalMode::Type mode() const { return _mode; }
int scale_root() const { return _scale_root; }
int root_octave() const { return _root_octave; }
bool in_key() const { return _in_key; }
Push2Layout* current_layout() const;
enum ModifierState {
None = 0,
ModShift = 0x1,
ModSelect = 0x2,
};
ModifierState modifier_state() const { return _modifier_state; }
Button* button_by_id (ButtonID);
void write (const MidiByteArray&);
static const int cols;
static const int rows;
private:
libusb_device_handle *handle;
uint8_t frame_header[16];
uint16_t* device_frame_buffer;
int device_buffer;
Cairo::RefPtr<Cairo::ImageSurface> frame_buffer;
sigc::connection vblank_connection;
sigc::connection periodic_connection;
ModifierState _modifier_state;
static const int pixels_per_row;
void do_request (Push2Request*);
int stop ();
int open ();
int close ();
bool redraw ();
int blit_to_device_frame_buffer ();
bool vblank ();
void relax () {} void relax () {}
/* map of Buttons by CC */ /* map of Buttons by CC */
@ -394,7 +404,6 @@ class Push2 : public ARDOUR::ControlProtocol
void handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes*); void handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes*);
void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count); void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count);
void write (const MidiByteArray&);
bool midi_input_handler (Glib::IOCondition ioc, MIDI::Port* port); bool midi_input_handler (Glib::IOCondition ioc, MIDI::Port* port);
bool periodic (); bool periodic ();
@ -422,24 +431,6 @@ class Push2 : public ARDOUR::ControlProtocol
void button_new (); void button_new ();
void button_browse (); void button_browse ();
void button_clip (); void button_clip ();
void button_upper (uint32_t n);
void button_lower (uint32_t n);
void button_upper_1 () { button_upper (0); }
void button_upper_2 () { button_upper (1); }
void button_upper_3 () { button_upper (2); }
void button_upper_4 () { button_upper (3); }
void button_upper_5 () { button_upper (4); }
void button_upper_6 () { button_upper (5); }
void button_upper_7 () { button_upper (6); }
void button_upper_8 () { button_upper (7); }
void button_lower_1 () { button_lower (0); }
void button_lower_2 () { button_lower (1); }
void button_lower_3 () { button_lower (2); }
void button_lower_4 () { button_lower (3); }
void button_lower_5 () { button_lower (4); }
void button_lower_6 () { button_lower (5); }
void button_lower_7 () { button_lower (6); }
void button_lower_8 () { button_lower (7); }
void button_undo (); void button_undo ();
void button_fwd32t (); void button_fwd32t ();
void button_fwd32 (); void button_fwd32 ();
@ -464,49 +455,57 @@ class Push2 : public ARDOUR::ControlProtocol
void button_layout_press (); void button_layout_press ();
void button_scale_press (); void button_scale_press ();
void button_upper (uint32_t n);
void button_lower (uint32_t n);
void button_upper_1 () { button_upper (0); }
void button_upper_2 () { button_upper (1); }
void button_upper_3 () { button_upper (2); }
void button_upper_4 () { button_upper (3); }
void button_upper_5 () { button_upper (4); }
void button_upper_6 () { button_upper (5); }
void button_upper_7 () { button_upper (6); }
void button_upper_8 () { button_upper (7); }
void button_lower_1 () { button_lower (0); }
void button_lower_2 () { button_lower (1); }
void button_lower_3 () { button_lower (2); }
void button_lower_4 () { button_lower (3); }
void button_lower_5 () { button_lower (4); }
void button_lower_6 () { button_lower (5); }
void button_lower_7 () { button_lower (6); }
void button_lower_8 () { button_lower (7); }
void start_shift (); void start_shift ();
void end_shift (); void end_shift ();
void start_select ();
void end_select ();
/* encoders */ /* non-strip encoders */
void strip_vpot (int, int);
void other_vpot (int, int); void other_vpot (int, int);
void strip_vpot_touch (int, bool);
void other_vpot_touch (int, bool); void other_vpot_touch (int, bool);
/* widgets */ /* special Stripables */
boost::shared_ptr<ARDOUR::Stripable> master;
boost::shared_ptr<ARDOUR::Stripable> monitor;
/* Cairo graphics context */
Cairo::RefPtr<Cairo::Context> context; Cairo::RefPtr<Cairo::Context> context;
Glib::RefPtr<Pango::Layout> tc_clock_layout;
Glib::RefPtr<Pango::Layout> bbt_clock_layout;
Glib::RefPtr<Pango::Layout> upper_layout[8];
Glib::RefPtr<Pango::Layout> mid_layout[8];
Glib::RefPtr<Pango::Layout> lower_layout[8];
void splash (); void splash ();
ARDOUR::microseconds_t splash_start; ARDOUR::microseconds_t splash_start;
/* stripables */ /* Layouts */
int32_t bank_start; mutable Glib::Threads::Mutex layout_lock;
PBD::ScopedConnectionList stripable_connections; Push2Layout* _current_layout;
boost::shared_ptr<ARDOUR::Stripable> stripable[8]; Push2Layout* drawn_layout;
boost::shared_ptr<ARDOUR::Stripable> master; Push2Layout* mix_layout;
boost::shared_ptr<ARDOUR::Stripable> monitor; Push2Layout* scale_layout;
void solo_change (int);
void mute_change (int);
void stripable_property_change (PBD::PropertyChange const& what_changed, int which);
void switch_bank (uint32_t base);
bool pad_filter (ARDOUR::MidiBuffer& in, ARDOUR::MidiBuffer& out) const; bool pad_filter (ARDOUR::MidiBuffer& in, ARDOUR::MidiBuffer& out) const;
boost::weak_ptr<ARDOUR::MidiTrack> current_pad_target; boost::weak_ptr<ARDOUR::MidiTrack> current_pad_target;
PBD::ScopedConnection selection_connection;
void stripable_selection_change (ARDOUR::StripableNotificationListPtr);
PBD::ScopedConnection port_reg_connection; PBD::ScopedConnection port_reg_connection;
void port_registration_handler (); void port_registration_handler ();
@ -527,6 +526,9 @@ class Push2 : public ARDOUR::ControlProtocol
/* pad mapping */ /* pad mapping */
PBD::ScopedConnection selection_connection;
void stripable_selection_change (ARDOUR::StripableNotificationListPtr);
std::map<int,int> pad_map; std::map<int,int> pad_map;
void build_pad_table(); void build_pad_table();
@ -539,16 +541,92 @@ class Push2 : public ARDOUR::ControlProtocol
bool percussion; bool percussion;
void set_percussive_mode (bool); void set_percussive_mode (bool);
};
/* menus */ class Push2Layout
Push2Menu* current_menu; {
Push2Menu* drawn_menu; public:
Push2Layout (Push2& p, ARDOUR::Session& s);
virtual ~Push2Layout ();
bool mapped() const;
virtual bool redraw (Cairo::RefPtr<Cairo::Context>) const = 0;
virtual void button_upper (uint32_t n) {}
virtual void button_lower (uint32_t n) {}
virtual void button_up () {}
virtual void button_down () {}
virtual void button_right () {}
virtual void button_left () {}
virtual void button_select_press () {}
virtual void button_select_release () {}
virtual void strip_vpot (int, int) = 0;
virtual void strip_vpot_touch (int, bool) = 0;
protected:
Push2& p2;
ARDOUR::Session& session;
};
class MixLayout : public Push2Layout
{
public:
MixLayout (Push2& p, ARDOUR::Session&, Cairo::RefPtr<Cairo::Context>);
~MixLayout ();
bool redraw (Cairo::RefPtr<Cairo::Context>) const;
void button_upper (uint32_t n);
void button_lower (uint32_t n);
void button_left ();
void button_right ();
void button_select_press ();
void button_select_release ();
void strip_vpot (int, int);
void strip_vpot_touch (int, bool);
private:
Glib::RefPtr<Pango::Layout> tc_clock_layout;
Glib::RefPtr<Pango::Layout> bbt_clock_layout;
Glib::RefPtr<Pango::Layout> upper_layout[8];
Glib::RefPtr<Pango::Layout> mid_layout[8];
Glib::RefPtr<Pango::Layout> lower_layout[8];
/* stripables */
int32_t bank_start;
PBD::ScopedConnectionList stripable_connections;
boost::shared_ptr<ARDOUR::Stripable> stripable[8];
void solo_change (int);
void mute_change (int);
void stripable_property_change (PBD::PropertyChange const& what_changed, int which);
void switch_bank (uint32_t base);
};
class ScaleLayout : public Push2Layout
{
public:
ScaleLayout (Push2& p, ARDOUR::Session&, Cairo::RefPtr<Cairo::Context>);
~ScaleLayout ();
bool redraw (Cairo::RefPtr<Cairo::Context>) const;
void button_upper (uint32_t n);
void button_lower (uint32_t n);
void strip_vpot (int, int);
void strip_vpot_touch (int, bool);
private:
Push2Menu* scale_menu; Push2Menu* scale_menu;
void build_scale_menu (Cairo::RefPtr<Cairo::Context>);
void build_scale_menu ();
void set_menu (Push2Menu*);
void show_scale_menu ();
void cancel_menu ();
}; };
} /* namespace */ } /* namespace */

View file

@ -0,0 +1,159 @@
/*
Copyright (C) 2016 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 <pangomm/layout.h>
#include "pbd/compose.h"
#include "pbd/convert.h"
#include "pbd/debug.h"
#include "pbd/failed_constructor.h"
#include "pbd/file_utils.h"
#include "pbd/search_path.h"
#include "pbd/enumwriter.h"
#include "midi++/parser.h"
#include "timecode/time.h"
#include "timecode/bbt_time.h"
#include "ardour/async_midi_port.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "ardour/filesystem_paths.h"
#include "ardour/midiport_manager.h"
#include "ardour/midi_track.h"
#include "ardour/midi_port.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
#include "push2.h"
#include "menu.h"
#include "i18n.h"
using namespace ARDOUR;
using namespace std;
using namespace PBD;
using namespace Glib;
using namespace ArdourSurface;
ScaleLayout::ScaleLayout (Push2& p, Session& s, Cairo::RefPtr<Cairo::Context> context)
: Push2Layout (p, s)
{
build_scale_menu (context);
}
ScaleLayout::~ScaleLayout ()
{
}
bool
ScaleLayout::redraw (Cairo::RefPtr<Cairo::Context> context) const
{
bool draw = false;
if (scale_menu->dirty()) {
draw = true;
}
if (!draw) {
return false;
}
context->set_source_rgb (0.764, 0.882, 0.882);
context->rectangle (0, 0, 960, 160);
context->fill ();
scale_menu->redraw (context);
return true;
}
void
ScaleLayout::button_upper (uint32_t n)
{
}
void
ScaleLayout::button_lower (uint32_t n)
{
}
void
ScaleLayout::strip_vpot (int n, int delta)
{
if (n == 0) {
scale_menu->step_active (n, delta);
return;
}
}
void
ScaleLayout::strip_vpot_touch (int, bool)
{
}
void
ScaleLayout::build_scale_menu (Cairo::RefPtr<Cairo::Context> context)
{
vector<string> v;
scale_menu = new Push2Menu (context);
v.push_back ("Dorian");
v.push_back ("IonianMajor");
v.push_back ("Minor");
v.push_back ("HarmonicMinor");
v.push_back ("MelodicMinorAscending");
v.push_back ("MelodicMinorDescending");
v.push_back ("Phrygian");
v.push_back ("Lydian");
v.push_back ("Mixolydian");
v.push_back ("Aeolian");
v.push_back ("Locrian");
v.push_back ("PentatonicMajor");
v.push_back ("PentatonicMinor");
v.push_back ("Chromatic");
v.push_back ("BluesScale");
v.push_back ("NeapolitanMinor");
v.push_back ("NeapolitanMajor");
v.push_back ("Oriental");
v.push_back ("DoubleHarmonic");
v.push_back ("Enigmatic");
v.push_back ("Hirajoshi");
v.push_back ("HungarianMinor");
v.push_back ("HungarianMajor");
v.push_back ("Kumoi");
v.push_back ("Iwato");
v.push_back ("Hindu");
v.push_back ("Spanish8Tone");
v.push_back ("Pelog");
v.push_back ("HungarianGypsy");
v.push_back ("Overtone");
v.push_back ("LeadingWholeTone");
v.push_back ("Arabian");
v.push_back ("Balinese");
v.push_back ("Gypsy");
v.push_back ("Mohammedan");
v.push_back ("Javanese");
v.push_back ("Persian");
v.push_back ("Algeria");
scale_menu->fill_column (0, v);
v.clear ();
}

View file

@ -26,8 +26,11 @@ def build(bld):
midi_byte_array.cc midi_byte_array.cc
leds.cc leds.cc
gui.cc gui.cc
layout.cc
mode.cc mode.cc
menu.cc menu.cc
mix.cc
scale.cc
''' '''
obj.export_includes = ['.'] obj.export_includes = ['.']
obj.defines = [ 'PACKAGE="ardour_push2"' ] obj.defines = [ 'PACKAGE="ardour_push2"' ]