diff options
author | David Robillard <d@drobilla.net> | 2007-03-18 06:07:08 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2007-03-18 06:07:08 +0000 |
commit | 99904735e066804358f1d0bd138a84f1e9ecda91 (patch) | |
tree | 71a924cf1660b5b00231275bd481bbd27094dd9b /libs/surfaces/mackie/scripts | |
parent | eb270e70a12c410cdd98585ad25bb6d8e384a4f5 (diff) |
Merged with trunk R1612.
git-svn-id: svn://localhost/ardour2/branches/midi@1614 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/surfaces/mackie/scripts')
18 files changed, 1333 insertions, 0 deletions
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 ] + |