Class: PM::DSL

Inherits:
Object
  • Object
show all
Includes:
PM
Defined in:
lib/patchmaster/dsl.rb

Overview

Implements a DSL for describing a PatchMaster setup.

Constant Summary

Constants included from PM

ACTIVE_SENSE, CC_BALANCE, CC_BALANCE_LSB, CC_BANK_SELECT, CC_BANK_SELECT_LSB, CC_BREATH_CONTROLLER, CC_BREATH_CONTROLLER_LSB, CC_CHORUS_DEPTH, CC_DATA_DECREMENT, CC_DATA_ENTRY, CC_DATA_ENTRY_LSB, CC_DATA_INCREMENT, CC_DETUNE_DEPTH, CC_EXPRESSION_CONTROLLER, CC_EXPRESSION_CONTROLLER_LSB, CC_EXT_EFFECTS_DEPTH, CC_FOOT_CONTROLLER, CC_FOOT_CONTROLLER_LSB, CC_GEN_PURPOSE_1, CC_GEN_PURPOSE_1_LSB, CC_GEN_PURPOSE_2, CC_GEN_PURPOSE_2_LSB, CC_GEN_PURPOSE_3, CC_GEN_PURPOSE_3_LSB, CC_GEN_PURPOSE_4, CC_GEN_PURPOSE_4_LSB, CC_GEN_PURPOSE_5, CC_GEN_PURPOSE_6, CC_GEN_PURPOSE_7, CC_GEN_PURPOSE_8, CC_HOLD_2, CC_MOD_WHEEL, CC_MOD_WHEEL_LSB, CC_NREG_PARAM_LSB, CC_NREG_PARAM_MSB, CC_PAN, CC_PAN_LSB, CC_PHASER_DEPTH, CC_PORTAMENTO, CC_PORTAMENTO_TIME, CC_PORTAMENTO_TIME_LSB, CC_REG_PARAM_LSB, CC_REG_PARAM_MSB, CC_SOFT_PEDAL, CC_SUSTAIN, CC_SUSTENUTO, CC_TREMELO_DEPTH, CC_VOLUME, CC_VOLUME_LSB, CHANNEL_PRESSURE, CLOCK, CM_ALL_NOTES_OFF, CM_LOCAL_CONTROL, CM_MONO_MODE_ON, CM_OMNI_MODE_OFF, CM_OMNI_MODE_ON, CM_POLY_MODE_ON, CM_RESET_ALL_CONTROLLERS, CONTINUE, CONTROLLER, CONTROLLER_NAMES, EOX, GM_DRUM_NOTE_LOWEST, GM_DRUM_NOTE_NAMES, GM_PATCH_NAMES, META_COPYRIGHT, META_CUE, META_EVENT, META_INSTRUMENT, META_LYRIC, META_MARKER, META_MIDI_CHAN_PREFIX, META_PATCH_SIG, META_SEQ_NAME, META_SEQ_NUM, META_SEQ_SPECIF, META_SET_TEMPO, META_SMPTE, META_TEXT, META_TIME_SIG, META_TRACK_END, MIDI_CHANNELS, NOTES_PER_CHANNEL, NOTE_OFF, NOTE_ON, PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE, SONG_POINTER, SONG_SELECT, START, STOP, SYSEX, SYSTEM_RESET, TUNE_REQUEST

Instance Method Summary collapse

Constructor Details

#initializeDSL

Returns a new instance of DSL.



11
12
13
14
# File 'lib/patchmaster/dsl.rb', line 11

def initialize
  @pm = PatchMaster.instance
  init
end

Instance Method Details

#alias_input(new_sym, old_sym) ⇒ Object



187
188
189
# File 'lib/patchmaster/dsl.rb', line 187

def alias_input(new_sym, old_sym)
  @inputs[new_sym] = @inputs[old_sym]
end

#alias_output(new_sym, old_sym) ⇒ Object



191
192
193
# File 'lib/patchmaster/dsl.rb', line 191

def alias_output(new_sym, old_sym)
  @outputs[new_sym] = @outputs[old_sym]
end

#code_key(key_or_sym, &block) ⇒ Object



82
83
84
85
86
# File 'lib/patchmaster/dsl.rb', line 82

def code_key(key_or_sym, &block)
  ck = CodeKey.new(to_binding_key(key_or_sym), CodeChunk.new(block))
  @pm.bind_code(ck)
  @code_keys << ck
end

#connection(in_sym, in_chan, out_sym, out_chan = nil) {|@conn| ... } ⇒ Object Also known as: conn, c

in_chan can be skipped, so “connection :foo, :bar, 1” is the same as “connection :foo, nil, :bar, 1”.

Yields:



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/patchmaster/dsl.rb', line 122

def connection(in_sym, in_chan, out_sym, out_chan=nil)
  input = @inputs[in_sym]
  if in_chan.kind_of? Symbol
    out_chan = out_sym
    out_sym = in_chan
    in_chan = nil
  end
  raise "can't find input instrument #{in_sym}" unless input
  output = @outputs[out_sym]
  raise "can't find outputput instrument #{out_sym}" unless output

  @conn = Connection.new(input, in_chan, output, out_chan)
  @patch << @conn
  yield @conn if block_given?
end

#filter(&block) ⇒ Object Also known as: f



171
172
173
174
# File 'lib/patchmaster/dsl.rb', line 171

def filter(&block)
  @conn.filter = Filter.new(CodeChunk.new(block))
  @filters << @conn.filter
end

#initObject

Initialize state used for reading.



17
18
19
20
21
22
23
24
# File 'lib/patchmaster/dsl.rb', line 17

def init
  @inputs = {}
  @outputs = {}
  @triggers = []
  @filters = []
  @code_keys = []
  @songs = {}                 # key = name, value = song
end

#input(port_num, sym, name = nil) ⇒ Object Also known as: inp



35
36
37
38
39
40
41
42
43
# File 'lib/patchmaster/dsl.rb', line 35

def input(port_num, sym, name=nil)
  raise "input: two inputs can not have the same symbol (:#{sym})" if @inputs[sym]

  input = InputInstrument.new(sym, name, port_num, @pm.use_midi?)
  @inputs[sym] = input
  @pm.inputs << input
rescue => ex
  raise "input: error creating input instrument \"#{name || sym}\" on input port #{port_num}: #{ex}"
end

#load(file) ⇒ Object



26
27
28
29
30
31
32
33
# File 'lib/patchmaster/dsl.rb', line 26

def load(file)
  contents = IO.read(file)
  init
  instance_eval(contents)
  read_code_keys(contents)
  read_triggers(contents)
  read_filters(contents)
end

#message(name, bytes) ⇒ Object



58
59
60
# File 'lib/patchmaster/dsl.rb', line 58

def message(name, bytes)
  @pm.messages[name.downcase] = [name, bytes]
end

#message_key(key_or_sym, name) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/patchmaster/dsl.rb', line 62

def message_key(key_or_sym, name)
  if name.is_a?(Symbol)
      name, key_or_sym = key_or_sym, name
      $stderr.puts "WARNING: the arguments to message_key are now key first, then name."
      $stderr.puts "I will use #{name} as the name and #{key_or_sym} as the key for now."
      $stderr.puts "Please swap them for future compatability."
  end
  if key_or_sym.is_a?(String) && name.is_a?(String)
    if name.length == 1 && key_or_sym.length > 1
      name, key_or_sym = key_or_sym, name
      $stderr.puts "WARNING: the arguments to message_key are now key first, then name."
      $stderr.puts "I will use #{name} as the name and #{key_or_sym} as the key for now."
      $stderr.puts "Please swap them for future compatability."
    elsif name.length == 1 && key_or_sym.length == 1
      raise "message_key: since both name and key are one-character strings, I can't tell which is which. Please make the name longer."
    end
  end
  @pm.bind_message(name, to_binding_key(key_or_sym))
end

#notes(txt) ⇒ Object



102
103
104
# File 'lib/patchmaster/dsl.rb', line 102

def notes(txt)
  @song.notes = txt
end

#output(port_num, sym, name = nil) ⇒ Object Also known as: out, outp



46
47
48
49
50
51
52
53
54
# File 'lib/patchmaster/dsl.rb', line 46

def output(port_num, sym, name=nil)
  raise "output: two outputs can not have the same symbol (:#{sym})" if @outputs[sym]

  output = OutputInstrument.new(sym, name, port_num, @pm.use_midi?)
  @outputs[sym] = output
  @pm.outputs << output
rescue => ex
  raise "output: error creating output instrument \"#{name || sym}\" on output port #{port_num}: #{ex}"
end

#patch(name) {|@patch| ... } ⇒ Object

Yields:



106
107
108
109
110
# File 'lib/patchmaster/dsl.rb', line 106

def patch(name)
  @patch = Patch.new(name)
  @song << @patch
  yield @patch if block_given?
end

#prog_chg(bank_or_prog, prog = nil) ⇒ Object Also known as: pc

If only bank_or_prog is specified, then it’s a program change. If both, then it’s bank number.



142
143
144
145
146
147
148
149
# File 'lib/patchmaster/dsl.rb', line 142

def prog_chg(bank_or_prog, prog=nil)
  if prog
    @conn.bank = bank_or_prog
    @conn.pc_prog = prog
  else
    @conn.pc_prog = bank_or_prog
  end
end

#save(file) ⇒ Object

****************************************************************



197
198
199
200
201
202
203
204
205
206
207
# File 'lib/patchmaster/dsl.rb', line 197

def save(file)
  File.open(file, 'w') { |f|
    save_instruments(f)
    save_messages(f)
    save_message_keys(f)
    save_code_keys(f)
    save_triggers(f)
    save_songs(f)
    save_song_lists(f)
  }
end

#save_code_keys(f) ⇒ Object



231
232
233
234
235
236
237
238
239
240
# File 'lib/patchmaster/dsl.rb', line 231

def save_code_keys(f)
  @pm.code_bindings.values.each do |code_key|
    str = if code_key.code_chunk.text[0] == '{'
            "code_key(#{to_save_key(code_key.key).inspect}) #{code_key.code_chunk.text}"
          else
            "code_key #{to_save_key(code_key.key).inspect} #{code_key.code_chunk.text}"
          end
    f.puts str
  end
end

#save_connection(f, conn) ⇒ Object



268
269
270
271
272
273
274
275
276
277
# File 'lib/patchmaster/dsl.rb', line 268

def save_connection(f, conn)
  in_chan = conn.input_chan ? conn.input_chan + 1 : 'nil'
  out_chan = conn.output_chan + 1
  f.puts "    conn :#{conn.input.sym}, #{in_chan}, :#{conn.output.sym}, #{out_chan} do"
  f.puts "      prog_chg #{conn.pc_prog}" if conn.pc?
  f.puts "      zone #{conn.note_num_to_name(conn.zone.begin)}, #{conn.note_num_to_name(conn.zone.end)}" if conn.zone
  f.puts "      xpose #{conn.xpose}" if conn.xpose
  f.puts "      filter #{conn.filter.code_chunk.text}" if conn.filter
  f.puts "    end"
end

#save_instruments(f) ⇒ Object



209
210
211
212
213
214
215
216
217
# File 'lib/patchmaster/dsl.rb', line 209

def save_instruments(f)
  @pm.inputs.each do |instr|
    f.puts "input #{instr.port_num}, :#{instr.sym}, #{instr.name.inspect}"
  end
  @pm.outputs.each do |instr|
    f.puts "output #{instr.port_num}, :#{instr.sym}, #{instr.name.inspect}"
  end
  f.puts
end

#save_message_keys(f) ⇒ Object



225
226
227
228
229
# File 'lib/patchmaster/dsl.rb', line 225

def save_message_keys(f)
  @pm.message_bindings.each do |key, message_name|
    f.puts "message_key #{to_save_key(key).inspect}, #{message_name.inspect}"
  end
end

#save_messages(f) ⇒ Object



219
220
221
222
223
# File 'lib/patchmaster/dsl.rb', line 219

def save_messages(f)
  @pm.messages.each do |_, (correct_case_name, msg)|
    f.puts "message #{correct_case_name.inspect}, #{msg.inspect}"
  end
end

#save_patch(f, patch) ⇒ Object



261
262
263
264
265
266
# File 'lib/patchmaster/dsl.rb', line 261

def save_patch(f, patch)
  f.puts "  patch #{patch.name.inspect} do"
  f.puts "    start_bytes #{patch.start_bytes.inspect}" if patch.start_bytes
  patch.connections.each { |conn| save_connection(f, conn) }
  f.puts "  end"
end

#save_song_lists(f) ⇒ Object



279
280
281
282
283
284
285
286
287
288
# File 'lib/patchmaster/dsl.rb', line 279

def save_song_lists(f)
  @pm.song_lists.each do |sl|
    next if sl == @pm.all_songs
    f.puts "song_list #{sl.name.inspect}, ["
    @pm.all_songs.songs.each do |song|
      f.puts "  #{song.name.inspect},"
    end
    f.puts "]"
  end
end

#save_songs(f) ⇒ Object



252
253
254
255
256
257
258
259
# File 'lib/patchmaster/dsl.rb', line 252

def save_songs(f)
  @pm.all_songs.songs.each do |song|
    f.puts "song #{song.name.inspect} do"
    song.patches.each { |patch| save_patch(f, patch) }
    f.puts "end"
    f.puts
  end
end

#save_triggers(f) ⇒ Object



242
243
244
245
246
247
248
249
250
# File 'lib/patchmaster/dsl.rb', line 242

def save_triggers(f)
  @pm.inputs.each do |instrument|
    instrument.triggers.each do |trigger|
      str = "trigger :#{instrument.sym}, #{trigger.bytes.inspect} #{trigger.code_chunk.text}"
      f.puts str
    end
  end
  f.puts
end

#song(name) {|@song| ... } ⇒ Object

Yields:



96
97
98
99
100
# File 'lib/patchmaster/dsl.rb', line 96

def song(name)
  @song = Song.new(name)      # ctor saves into @pm.all_songs
  @songs[name] = @song
  yield @song if block_given?
end

#song_list(name, song_names) ⇒ Object



177
178
179
180
181
182
183
184
185
# File 'lib/patchmaster/dsl.rb', line 177

def song_list(name, song_names)
  sl = SongList.new(name)
  @pm.song_lists << sl
  song_names.each do |sn|
    song = @songs[sn]
    raise "song \"#{sn}\" not found (song list \"#{name}\")" unless song
    sl << song
  end
end

#start_bytes(bytes) ⇒ Object



112
113
114
# File 'lib/patchmaster/dsl.rb', line 112

def start_bytes(bytes)
  @patch.start_bytes = bytes
end

#stop_bytes(bytes) ⇒ Object



116
117
118
# File 'lib/patchmaster/dsl.rb', line 116

def stop_bytes(bytes)
  @patch.stop_bytes = bytes
end

#transpose(xpose) ⇒ Object Also known as: xpose, x



165
166
167
# File 'lib/patchmaster/dsl.rb', line 165

def transpose(xpose)
  @conn.xpose = xpose
end

#trigger(instrument_sym, bytes, &block) ⇒ Object



88
89
90
91
92
93
94
# File 'lib/patchmaster/dsl.rb', line 88

def trigger(instrument_sym, bytes, &block)
  instrument = @inputs[instrument_sym]
  raise "trigger: error finding instrument #{instrument_sym}" unless instrument
  t = Trigger.new(bytes, CodeChunk.new(block))
  instrument.triggers << t
  @triggers << t
end

#zone(start_or_range = nil, stop = nil) ⇒ Object Also known as: z

If start_or_range is a Range, use that. Else either or both params may be nil.



154
155
156
157
158
159
160
161
162
# File 'lib/patchmaster/dsl.rb', line 154

def zone(start_or_range=nil, stop=nil)
  @conn.zone = if start_or_range.kind_of? Range
                       start_or_range
                     elsif start_or_range == nil && stop == nil
                       nil
                     else
                       ((start_or_range || 0) .. (stop || 127))
                     end
end