"merge" (i.e. wholesale import) 2.0-ongoing Mackie code and then fix to compile in...
[ardour.git] / libs / surfaces / mackie / scripts / controls.rb
1 #! /usr/bin/ruby
2 # Copyright (C) 2006,2007 John Anderson
3
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 this_dir = File.dirname(__FILE__)
19
20 require 'faster_csv'
21 require "#{this_dir}/mackie.rb"
22
23 class Control
24   attr_accessor :id, :led, :group, :name, :ordinal, :switch
25   
26   def initialize( obj, group )
27     @id = obj.id
28     @name = obj.name
29     @ordinal = obj.ordinal
30     @switch = obj.switch
31     @group = group
32   end
33   
34   def ordinal_name
35   end
36 end
37
38 class Fader < Control
39   def self.midi_zero_byte
40     0xe0
41   end
42   
43   def self.mask_for_id( bytes )
44     bytes[0] & 0b00001111
45   end
46 end
47
48 class Button < Control
49   def self.midi_zero_byte
50     0x90
51   end
52   
53   def self.mask_for_id( bytes )
54     bytes[1]
55   end
56 end
57
58 class Led < Control
59 end
60
61 class LedRing < Led
62 end
63
64 class Pot < Control
65   def self.midi_zero_byte
66     0xb0
67   end
68   
69   def self.mask_for_id( bytes )
70     bytes[1] & 0b00011111
71   end
72
73   def led=( rhs )
74     @led = LedRing.new( rhs, group )
75   end
76 end
77
78 class Group < Array
79   attr_accessor :name, :controls
80   
81   def initialize( name )
82     @name = name
83   end
84   
85   def add_control( control )
86     @controls ||= Array.new
87     @controls << control
88   end
89 end
90
91 class Strip < Group
92   
93   attr_accessor :ordinal
94   def initialize( name, ordinal )
95     super( name )
96     @ordinal = ordinal
97   end
98   
99   def name
100     @name == 'master' ? @name : "#{@name}_#{@ordinal}"
101   end
102   
103   def is_master
104     name == 'master'
105   end
106   
107 end
108
109 types = { 0xe0 => Fader, 0x90 => Button, 0xb0 => Pot }
110
111 # number of controls, name, switch, led, id
112 # anything that doesn't have the correct number
113 # of columns will be ignored
114 # actually, 'switch' means it generates data
115 # whereas 'led' means it receives data
116
117 class Row
118   attr_accessor :count, :name, :switch, :led, :start_id, :type, :group
119   attr_accessor :id, :ordinal_name, :ordinal_group, :ordinal
120
121   def initialize( hash )
122     @count = hash['count'].to_i
123     @name = hash['name']
124     @switch = hash['switch'].to_b
125     @led = hash['led'].to_b
126     @start_id = hash['id'].hex
127     @type = hash['type']
128     @group = hash['group']
129     
130     @hash = hash
131   end
132   
133   def each_ordinal( &block )
134     for i in 0...count
135       @ordinal = i + 1
136       @ordinal_name = count > 1 ? "#{name}_#{ordinal}" : name
137       @ordinal_group = count > 1 ? "#{group}_#{ordinal}" : group
138       @id = start_id + i
139       
140       @hash['ordinal_name'] = @ordinal_name
141       @hash['ordinal_group'] = @ordinal_group
142       
143       yield( self )
144     end
145     self
146   end
147   
148   def to_hash
149     @hash
150   end
151 end
152
153 class Surface
154   attr_reader :groups, :controls_by_id, :types, :midis, :controls, :name
155   
156   def initialize( name = 'none' )
157     @name = name
158     @types = Hash.new
159     @groups = Hash.new
160     @controls = Array.new
161     @controls_by_id = Hash.new
162     @midis = Hash.new
163   end
164   
165   def add_or_create_group( name, ordinal = nil )
166     if name.nil?
167       @groups['none'] = Group.new('none')
168     else
169       group = name =~ /strip/ || name == 'master' ? Strip.new( name, ordinal ) : Group.new( name )
170       @groups[group.name] ||= group
171     end
172   end
173
174   def parse( control_data )
175     FasterCSV.parse( control_data, :headers => true ) do |csv_row|
176       next if csv_row.entries.size < 5 || csv_row[0] =~ /^\s*#/ || csv_row['id'].nil?
177       row = Row.new( csv_row )
178       
179       row.each_ordinal do |row|
180         group = add_or_create_group( row.group, row.ordinal )
181         if row.switch
182           # for controls
183           control = eval "#{row.type.capitalize}.new( row, group )"
184           
185           # for controls with leds
186           control.led = Led.new( row, group ) if row.led
187         else
188           # for LED-only entries
189           if row.led
190             control = Led.new( row, group )
191             control.led = control
192           end
193         end
194         
195         # add the new control to the various lookups
196         # but first print a warning if the id is duplicated
197         if @controls_by_id.has_key?( row.id ) && control.group.class != Strip
198           duplicated = @controls_by_id[row.id]
199           puts "duplicate id #{control.id}:#{control.name} of #{duplicated.id}:#{duplicated.name}"
200         end
201         
202         @controls_by_id[row.id] = control
203         @controls << control
204         group << control
205         
206         # add incoming midi bytes
207         if row.switch
208           types[control.class.midi_zero_byte] = control.class
209           midis[control.class.midi_zero_byte] ||= Hash.new
210           midis[control.class.midi_zero_byte][row.id] = control
211         end
212       end
213     end
214     self
215   end
216 end