summaryrefslogtreecommitdiff
path: root/libs/surfaces/mackie/scripts/controls.rb
blob: 666c34d4af6868449bba77bb717e68ed377e82d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#! /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.

this_dir = File.dirname(__FILE__)

require 'faster_csv'
require "#{this_dir}/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
        # but first print a warning if the id is duplicated
        if @controls_by_id.has_key?( row.id ) && control.group.class != Strip
          duplicated = @controls_by_id[row.id]
          puts "duplicate id #{control.id}:#{control.name} of #{duplicated.id}:#{duplicated.name}"
        end
        
        @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