Jog Wheel Fun:

- When ffwd/rew is pressed, wheel controls speed
- Zoom button allows jog wheel to zoom
- Scrub button cycles jog from scrub to shuttle to whatever it was before



git-svn-id: svn://localhost/ardour2/branches/2.0-ongoing@2155 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
John Anderson 2007-07-19 22:07:10 +00:00
parent 47add43cd0
commit 4c12c98e33
19 changed files with 1687 additions and 1262 deletions

View file

@ -35,6 +35,7 @@ surface.cc
mackie_control_protocol.cc
bcf_surface.cc
mackie_surface.cc
mackie_jog_wheel.cc
""")
mackie.Append(CCFLAGS="-D_REENTRANT -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE")

View file

@ -1,3 +1,4 @@
* update manual with jog wheel states
* alsa/sequencer ports unstable
* crash when mmc port set to mcu
* remappable buttons

File diff suppressed because it is too large Load diff

View file

@ -267,9 +267,13 @@ public:
}
virtual type_t type() const { return type_button; };
bool pressed() const { return _pressed; }
Button & pressed( bool rhs ) { _pressed = rhs; return *this; }
private:
Led _led;
bool _pressed;
};
class LedRing : public Led

View file

@ -99,6 +99,7 @@ MackieControlProtocol::MackieControlProtocol (Session& session)
, _polling( true )
, pfd( 0 )
, nfds( 0 )
, _jog_wheel( *this )
{
#ifdef DEBUG
cout << "MackieControlProtocol::MackieControlProtocol" << endl;
@ -907,27 +908,17 @@ void MackieControlProtocol::handle_control_event( SurfacePort & port, Control &
{
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() );
_jog_wheel.jog_event( port, control, state );
// turn off the led ring, for bcf emulation mode
port.write( builder.build_led_ring( dynamic_cast<Pot &>( control ), off ) );
if ( mcu_port().emulation() == MackiePort::bcf2000 )
{
port.write( builder.build_led_ring( dynamic_cast<Pot &>( control ), off ) );
}
}
else
{
cout << "external controller" << state.ticks << endl;
cout << "external controller" << state.ticks * state.sign << endl;
}
}
break;
@ -1186,12 +1177,16 @@ LedState MackieControlProtocol::record_release( Button & button )
LedState MackieControlProtocol::rewind_press( Button & button )
{
session->request_transport_speed( -4.0 );
_jog_wheel.push( JogWheel::speed );
_jog_wheel.transport_direction( -1 );
session->request_transport_speed( -_jog_wheel.transport_speed() );
return on;
}
LedState MackieControlProtocol::rewind_release( Button & button )
{
_jog_wheel.pop();
_jog_wheel.transport_direction( 0 );
if ( _transport_previously_rolling )
session->request_transport_speed( 1.0 );
else
@ -1201,12 +1196,16 @@ LedState MackieControlProtocol::rewind_release( Button & button )
LedState MackieControlProtocol::ffwd_press( Button & button )
{
session->request_transport_speed( 4.0 );
_jog_wheel.push( JogWheel::speed );
_jog_wheel.transport_direction( 1 );
session->request_transport_speed( _jog_wheel.transport_speed() );
return on;
}
LedState MackieControlProtocol::ffwd_release( Button & button )
{
_jog_wheel.pop();
_jog_wheel.transport_direction( 0 );
if ( _transport_previously_rolling )
session->request_transport_speed( 1.0 );
else
@ -1506,3 +1505,50 @@ LedState MackieControlProtocol::marker_release( Button & button )
{
return off;
}
void jog_wheel_state_display( JogWheel::State state, MackiePort & port )
{
switch( state )
{
case JogWheel::zoom: port.write( builder.two_char_display( "Zm" ) ); break;
case JogWheel::scroll: port.write( builder.two_char_display( "Sc" ) ); break;
case JogWheel::scrub: port.write( builder.two_char_display( "Sb" ) ); break;
case JogWheel::shuttle: port.write( builder.two_char_display( "Sh" ) ); break;
case JogWheel::speed: port.write( builder.two_char_display( "Sp" ) ); break;
case JogWheel::select: port.write( builder.two_char_display( "Se" ) ); break;
}
}
Mackie::LedState MackieControlProtocol::zoom_press( Mackie::Button & )
{
_jog_wheel.zoom_state_toggle();
update_global_button( "scrub", _jog_wheel.jog_wheel_state() == JogWheel::scrub );
jog_wheel_state_display( _jog_wheel.jog_wheel_state(), mcu_port() );
return _jog_wheel.jog_wheel_state() == JogWheel::zoom;
}
Mackie::LedState MackieControlProtocol::zoom_release( Mackie::Button & )
{
return _jog_wheel.jog_wheel_state() == JogWheel::zoom;
}
Mackie::LedState MackieControlProtocol::scrub_press( Mackie::Button & )
{
_jog_wheel.scrub_state_cycle();
update_global_button( "zoom", _jog_wheel.jog_wheel_state() == JogWheel::zoom );
jog_wheel_state_display( _jog_wheel.jog_wheel_state(), mcu_port() );
return
_jog_wheel.jog_wheel_state() == JogWheel::scrub
||
_jog_wheel.jog_wheel_state() == JogWheel::shuttle
;
}
Mackie::LedState MackieControlProtocol::scrub_release( Mackie::Button & )
{
return
_jog_wheel.jog_wheel_state() == JogWheel::scrub
||
_jog_wheel.jog_wheel_state() == JogWheel::shuttle
;
}

View file

@ -35,6 +35,7 @@
#include "route_signal.h"
#include "mackie_button_handler.h"
#include "mackie_port.h"
#include "mackie_jog_wheel.h"
#include "timer.h"
namespace MIDI {
@ -184,6 +185,19 @@ class MackieControlProtocol
virtual Mackie::LedState marker_press( Mackie::Button & );
virtual Mackie::LedState marker_release( Mackie::Button & );
// jog wheel states
virtual Mackie::LedState zoom_press( Mackie::Button & );
virtual Mackie::LedState zoom_release( Mackie::Button & );
virtual Mackie::LedState scrub_press( Mackie::Button & );
virtual Mackie::LedState scrub_release( Mackie::Button & );
/// This is the main MCU port, ie not an extender port
/// Only for use by JogWheel
const Mackie::MackiePort & mcu_port() const;
Mackie::MackiePort & mcu_port();
ARDOUR::Session & get_session() { return *session; }
protected:
// create instances of MackiePort, depending on what's found in ardour.rc
void create_ports();
@ -222,10 +236,6 @@ class MackieControlProtocol
// 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;
@ -318,6 +328,8 @@ class MackieControlProtocol
// timer for two quick marker left presses
Mackie::Timer _frm_left_last;
Mackie::JogWheel _jog_wheel;
};
#endif // ardour_mackie_control_protocol_h

View file

@ -55,8 +55,6 @@ void * MackieControlProtocol::monitor_work()
{
if ( poll_ports() )
{
cout << "--------------------------------------" << endl;
cout << "MackieControlProtocol::read_ports _ports: " << _ports.size() << ", nfds: " << nfds << endl;
try { read_ports(); }
catch ( exception & e ) {
cout << "MackieControlProtocol::poll_ports caught exception: " << e.what() << endl;
@ -66,6 +64,9 @@ void * MackieControlProtocol::monitor_work()
}
// poll for automation data from the routes
poll_automation();
// check if we need to stop scrubbing
_jog_wheel.check_scrubbing();
}
catch ( exception & e )
{

View file

@ -0,0 +1,218 @@
#include "mackie_jog_wheel.h"
#include "mackie_control_protocol.h"
#include "surface_port.h"
#include "controls.h"
#include <algorithm>
using namespace Mackie;
JogWheel::JogWheel( MackieControlProtocol & mcp )
: _mcp( mcp )
, _transport_speed( 4.0 )
, _transport_direction( 0 )
, _shuttle_speed( 0.0 )
{
}
JogWheel::State JogWheel::jog_wheel_state() const
{
if ( !_jog_wheel_states.empty() )
return _jog_wheel_states.top();
else
return scroll;
}
void JogWheel::zoom_event( SurfacePort & port, Control & control, const ControlState & state )
{
}
void JogWheel::scrub_event( SurfacePort & port, Control & control, const ControlState & state )
{
}
void JogWheel::speed_event( SurfacePort & port, Control & control, const ControlState & state )
{
}
void JogWheel::scroll_event( SurfacePort & port, Control & control, const ControlState & state )
{
}
float scaled_delta( const ControlState & state, float current_speed )
{
return state.sign * ( pow( state.ticks + 1, 2 ) + current_speed ) / 100.0;
}
void JogWheel::jog_event( SurfacePort & port, Control & control, const ControlState & state )
{
// TODO use current snap-to setting?
#if 0
long delta = state.ticks * sign * 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() );
#endif
switch ( jog_wheel_state() )
{
case scroll:
//ScrollTimeline causes crashes
if ( _mcp.mcu_port().emulation() == MackiePort::bcf2000 )
_mcp.ScrollTimeline( state.ticks * state.sign / 100.0 );
else
_mcp.ScrollTimeline( state.ticks * state.sign / 100.0 );
break;
case zoom:
// TODO do a for loop for each, to number of ticks
if ( state.sign > 0 )
for ( unsigned int i = 0; i < state.ticks; ++i ) _mcp.ZoomIn();
else
for ( unsigned int i = 0; i < state.ticks; ++i ) _mcp.ZoomOut();
break;
case speed:
{
// block because we initialize a variable
// locally, _transport_speed is an absolute value...
// fairly arbitrary scaling function
_transport_speed += scaled_delta( state, _mcp.get_session().transport_speed() );
// make sure not weirdness get so the session
if ( _transport_speed < 0 || isnan( _transport_speed ) )
{
_transport_speed = 0.0;
}
// translated current speed to a signed transport velocity
_mcp.get_session().request_transport_speed( transport_speed() * transport_direction() );
break;
}
case scrub:
{
add_scrub_interval( _scrub_timer.restart() );
// copied from tranzport driver
float speed = 0.0;
// This should really be part of the surface object
if ( _mcp.mcu_port().emulation() == MackiePort::bcf2000 )
// 5 clicks per second => speed == 1.0
speed = 50.0 / average_scrub_interval() * state.ticks;
else
// 10 clicks per second => speed == 1.0
speed = 100.0 / average_scrub_interval() * state.ticks;
_mcp.get_session().request_transport_speed( speed * state.sign );
break;
}
case shuttle:
_shuttle_speed = _mcp.get_session().transport_speed();
_shuttle_speed += scaled_delta( state, _mcp.get_session().transport_speed() );
_mcp.get_session().request_transport_speed( _shuttle_speed );
break;
case select:
cout << "JogWheel select not implemented" << endl;
break;
}
}
void JogWheel::check_scrubbing()
{
// if the last elapsed is greater than the average + std deviation, then stop
if ( !_scrub_intervals.empty() && _scrub_timer.elapsed() > average_scrub_interval() + std_dev_scrub_interval() )
{
_mcp.get_session().request_transport_speed( 0.0 );
_scrub_intervals.clear();
}
}
void JogWheel::push( State state )
{
_jog_wheel_states.push( state );
}
void JogWheel::pop()
{
if ( _jog_wheel_states.size() > 0 )
{
_jog_wheel_states.pop();
}
}
void JogWheel::zoom_state_toggle()
{
if ( jog_wheel_state() == zoom )
pop();
else
push( zoom );
}
JogWheel::State JogWheel::scrub_state_cycle()
{
State top = jog_wheel_state();
if ( top == scrub )
{
// stop scrubbing and go to shuttle
pop();
push( shuttle );
_shuttle_speed = 0.0;
}
else if ( top == shuttle )
{
// default to scroll, or the last selected
pop();
}
else
{
// start with scrub
push( scrub );
}
return jog_wheel_state();
}
void JogWheel::add_scrub_interval( unsigned long elapsed )
{
if ( _scrub_intervals.size() > 5 )
{
_scrub_intervals.pop_front();
}
_scrub_intervals.push_back( elapsed );
}
float JogWheel::average_scrub_interval()
{
float sum = 0.0;
for ( std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it )
{
sum += *it;
}
return sum / _scrub_intervals.size();
}
float JogWheel::std_dev_scrub_interval()
{
float average = average_scrub_interval();
// calculate standard deviation
float sum = 0.0;
for ( std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it )
{
sum += pow( *it - average, 2 );
}
return sqrt( sum / _scrub_intervals.size() -1 );
}

View file

@ -0,0 +1,102 @@
#ifndef mackie_jog_wheel
#define mackie_jog_wheel
#include "timer.h"
#include <stack>
#include <deque>
#include <queue>
class MackieControlProtocol;
namespace Mackie
{
class SurfacePort;
class Control;
class ControlState;
/**
A jog wheel can be used to control many things. This
handles all of the states and state transitions.
Mainly it exists to avoid putting a bunch of messy
stuff in MackieControlProtocol.
But it doesn't really know who it is, with stacks, queues and various
boolean state variables.
*/
class JogWheel
{
public:
enum State { scroll, zoom, speed, scrub, shuttle, select };
JogWheel( MackieControlProtocol & mcp );
/// As the wheel turns...
void jog_event( SurfacePort & port, Control & control, const ControlState & state );
// These are for incoming button presses that change the internal state
// but they're not actually used at the moment.
void zoom_event( SurfacePort & port, Control & control, const ControlState & state );
void scrub_event( SurfacePort & port, Control & control, const ControlState & state );
void speed_event( SurfacePort & port, Control & control, const ControlState & state );
void scroll_event( SurfacePort & port, Control & control, const ControlState & state );
/// Return the current jog wheel mode, which defaults to Scroll
State jog_wheel_state() const;
/// The current transport speed for ffwd and rew. Can be
/// set by wheel when they're pressed.
float transport_speed() const { return _transport_speed; }
/// one of -1,0,1
int transport_direction() const { return _transport_direction; }
void transport_direction( int rhs ) { _transport_direction = rhs; }
void push( State state );
void pop();
/// Turn zoom mode on and off
void zoom_state_toggle();
/**
Cycle scrub -> shuttle -> previous
*/
State scrub_state_cycle();
/// Check to see when the last scrub event was
/// And stop scrubbing if it was too long ago.
/// Intended to be called from a periodic timer of
/// some kind.
void check_scrubbing();
protected:
void add_scrub_interval( unsigned long elapsed );
float average_scrub_interval();
float std_dev_scrub_interval();
private:
MackieControlProtocol & _mcp;
// transport speed for ffwd and rew, controller by jog
float _transport_speed;
int _transport_direction;
/// Speed for shuttle
float _shuttle_speed;
/// a stack for keeping track of states
std::stack<State> _jog_wheel_states;
/// So we know how fast to set the transport speed while scrubbing
Timer _scrub_timer;
/// to keep track of what the current scrub rate is
/// so we can calculate a moving average
std::deque<unsigned long> _scrub_intervals;
};
}
#endif

View file

@ -408,9 +408,9 @@ void MackiePort::handle_midi_any (MIDI::Parser & parser, MIDI::byte * raw_bytes,
ControlState state;
// bytes[2] & 0b01000000 (0x40) give sign
int sign = ( bytes[2] & 0x40 ) == 0 ? 1 : -1;
state.sign = ( bytes[2] & 0x40 ) == 0 ? 1 : -1;
// bytes[2] & 0b00111111 (0x3f) gives delta
state.ticks = ( bytes[2] & 0x3f) * sign;
state.ticks = ( bytes[2] & 0x3f);
state.delta = float( state.ticks ) / float( 0x3f );
control_event( *this, control, state );

File diff suppressed because it is too large Load diff

View file

@ -21,12 +21,12 @@ 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,assignment,zoom,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,,scrub,1,1,0x32
button,1,,edit,1,1,0x56
button,1,display,name_value,1,0,0x34
@ -78,8 +78,8 @@ 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,,"dyn",1,1,0x64
button,1,,"flip",1,1,0x65
button,1,user,"user_a",1,0,0x66
button,1,user,"user_b",1,0,0x67

Can't render this file because it has a wrong number of fields in line 2.

View file

@ -99,7 +99,7 @@ while bytes = mck.file.read( 3 )
control = sf.midis[midi_type][control_id]
print " Control Type: %-7s, " % sf.types[midi_type]
print "id: %4i" % control_id
print "id: %4x" % control_id
print ", control: %15s" % ( control ? control.name : "nil control" )
print ", %15s" % ( control ? control.group.name : "nil group" )
print "\n"

View file

@ -52,17 +52,20 @@ void Mackie::<%= sf.name %>Surface::init_controls()
% end
// initialise controls
Control * control = 0;
Fader * fader = 0;
Pot * pot = 0;
Button * button = 0;
Led * led = 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 );
<%= control.class.name.downcase %> = new <%= control.class.name %> ( <%= control.id %>, <%= control.ordinal %>, "<%=control.name%>", *group );
<%=control.class.name.downcase%>s[0x<%=control.id.to_hex %>] = <%= control.class.name.downcase %>;
controls.push_back( <%= control.class.name.downcase %> );
<%- if control.group.class != Strip -%>
controls_by_name["<%= control.name %>"] = control;
controls_by_name["<%= control.name %>"] = <%= control.class.name.downcase %>;
<%- end -%>
group->add( *control );
group->add( *<%= control.class.name.downcase %> );
% end
}

View file

@ -56,10 +56,10 @@ public:
Indexed by raw_id not by id. @see Control for the distinction.
*/
std::map<int,Control*> faders;
std::map<int,Control*> pots;
std::map<int,Control*> buttons;
std::map<int,Control*> leds;
std::map<int,Fader*> faders;
std::map<int,Pot*> pots;
std::map<int,Button*> buttons;
std::map<int,Led*> leds;
/// no strip controls in here because they usually
/// have the same names.

View file

@ -87,7 +87,7 @@ MidiByteArray SurfacePort::read()
retval.copy( nread, buf );
if ((size_t) nread == sizeof (buf))
{
#ifdef DEBUG
#ifdef PORT_DEBUG
cout << "SurfacePort::read recursive" << endl;
#endif
retval << read();
@ -106,7 +106,7 @@ MidiByteArray SurfacePort::read()
throw MackieControlException( os.str() );
}
}
#ifdef DEBUG
#ifdef PORT_DEBUG
cout << "SurfacePort::read: " << retval << endl;
#endif
return retval;
@ -114,7 +114,7 @@ MidiByteArray SurfacePort::read()
void SurfacePort::write( const MidiByteArray & mba )
{
#ifdef DEBUG
#ifdef PORT_DEBUG
//if ( mba[0] == 0xf0 ) cout << "SurfacePort::write: " << mba << endl;
cout << "SurfacePort::write: " << mba << endl;
#endif
@ -140,7 +140,7 @@ void SurfacePort::write( const MidiByteArray & mba )
throw MackieControlException( os.str() );
}
}
#ifdef DEBUG
#ifdef PORT_DEBUG
cout << "SurfacePort::wrote " << count << endl;
#endif
}

View file

@ -44,7 +44,7 @@ public:
if ( shouldStart )
start();
}
/**
Start the timer running. Return the current timestamp, in milliseconds
*/

View file

@ -2,8 +2,28 @@
namespace Mackie
{
LedState on( LedState::on );
LedState off( LedState::off );
LedState flashing( LedState::flashing );
LedState none( LedState::none );
LedState on( LedState::on );
LedState off( LedState::off );
LedState flashing( LedState::flashing );
LedState none( LedState::none );
std::ostream & operator << ( std::ostream & os, const ControlState & cs )
{
os << "ControlState { ";
os << "pos: " << cs.pos;
os << ", ";
os << "sign: " << cs.sign;
os << ", ";
os << "delta: " << cs.delta;
os << ", ";
os << "ticks: " << cs.ticks;
os << ", ";
os << "led_state: " << cs.led_state.state();
os << ", ";
os << "button_state: " << cs.button_state;
os << " }";
return os;
}
}

View file

@ -18,7 +18,7 @@
#ifndef mackie_types_h
#define mackie_types_h
#define DEBUG 1
#include <iostream>
namespace Mackie
{
@ -71,16 +71,27 @@ struct ControlState
// 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( float flt, unsigned 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) {}
/// For faders. Between 0 and 1.
float pos;
/// For pots. Sign. Either -1 or 1;
int sign;
/// For pots. Signed value of total movement. Between 0 and 1
float delta;
int ticks;
/// For pots. Unsigned number of ticks. Usually between 1 and 16.
unsigned int ticks;
LedState led_state;
ButtonState button_state;
};
std::ostream & operator << ( std::ostream &, const ControlState & );
class Control;
class Fader;
class Button;