class PM::DSL

Implements a DSL for describing a PatchMaster setup.

Public Class Methods

new() click to toggle source
# File lib/patchmaster/dsl.rb, line 11
def initialize
  @pm = PatchMaster.instance
  init
end

Public Instance Methods

alias_input(new_sym, old_sym) click to toggle source
# 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) click to toggle source
# File lib/patchmaster/dsl.rb, line 191
def alias_output(new_sym, old_sym)
  @outputs[new_sym] = @outputs[old_sym]
end
c(in_sym, in_chan, out_sym, out_chan=nil)
Alias for: connection
code_key(key_or_sym, &block) click to toggle source
# 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
conn(in_sym, in_chan, out_sym, out_chan=nil)
Alias for: connection
connection(in_sym, in_chan, out_sym, out_chan=nil) { |conn| ... } click to toggle source

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

# 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
Also aliased as: conn, c
f(&block)
Alias for: filter
filter(&block) click to toggle source
# File lib/patchmaster/dsl.rb, line 171
def filter(&block)
  @conn.filter = Filter.new(CodeChunk.new(block))
  @filters << @conn.filter
end
Also aliased as: f
init() click to toggle source

Initialize state used for reading.

# File lib/patchmaster/dsl.rb, line 17
def init
  @inputs = {}
  @outputs = {}
  @triggers = []
  @filters = []
  @code_keys = []
  @songs = {}                 # key = name, value = song
end
inp(port_num, sym, name=nil)
Alias for: input
input(port_num, sym, name=nil) click to toggle source
# 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
Also aliased as: inp
load(file) click to toggle source
# 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) click to toggle source
# File lib/patchmaster/dsl.rb, line 58
def message(name, bytes)
  @pm.messages[name.downcase] = [name, bytes]
end
message_key(key_or_sym, name) click to toggle source
# 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) click to toggle source
# File lib/patchmaster/dsl.rb, line 102
def notes(txt)
  @song.notes = txt
end
out(port_num, sym, name=nil)
Alias for: output
outp(port_num, sym, name=nil)
Alias for: output
output(port_num, sym, name=nil) click to toggle source
# 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
Also aliased as: out, outp
patch(name) { |patch| ... } click to toggle source
# File lib/patchmaster/dsl.rb, line 106
def patch(name)
  @patch = Patch.new(name)
  @song << @patch
  yield @patch if block_given?
end
pc(bank_or_prog, prog=nil)
Alias for: prog_chg
prog_chg(bank_or_prog, prog=nil) click to toggle source

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

# 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
Also aliased as: pc
save(file) click to toggle source

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

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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| ... } click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File lib/patchmaster/dsl.rb, line 112
def start_bytes(bytes)
  @patch.start_bytes = bytes
end
stop_bytes(bytes) click to toggle source
# File lib/patchmaster/dsl.rb, line 116
def stop_bytes(bytes)
  @patch.stop_bytes = bytes
end
transpose(xpose) click to toggle source
# File lib/patchmaster/dsl.rb, line 165
def transpose(xpose)
  @conn.xpose = xpose
end
Also aliased as: xpose, x
trigger(instrument_sym, bytes, &block) click to toggle source
# 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
x(xpose)
Alias for: transpose
xpose(xpose)
Alias for: transpose
z(start_or_range=nil, stop=nil)
Alias for: zone
zone(start_or_range=nil, stop=nil) click to toggle source

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

# 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
Also aliased as: z

Private Instance Methods

read_block_text(name, containers, contents) click to toggle source

Extremely simple block text reader. Relies on indentation to detect end of code block.

# File lib/patchmaster/dsl.rb, line 325
def read_block_text(name, containers, contents)
  i = -1
  in_block = false
  block_indentation = nil
  block_end_token = nil
  chunk = nil
  contents.each_line do |line|
    if line =~ /^(\s*)#{name}\s*.*?(({|do|->\s*{|lambda\s*{)(.*))/
      block_indentation, text = $1, $2
      i += 1
      chunk = containers[i].code_chunk
      chunk.text = text + "\n"
      in_block = true
      block_end_token = case text
                           when /^{/
                             "}"
                           when /^do\b/
                             "end"
                           when /^(->|lambda)\s*({|do)/
                             $2 == "{" ? "}" : "end"
                           else
                             "}|end" # regex
                           end
    elsif in_block
      line =~ /^(\s*)(.*)/
      indentation, text = $1, $2
      if indentation.length <= block_indentation.length
        if text =~ /^#{block_end_token}/
          chunk.text << line
        end
        in_block = false
      else
        chunk.text << line
      end
    end
  end
  containers.each do |thing|
    text = thing.code_chunk.text
    text.strip! if text
  end
end
read_code_keys(contents) click to toggle source
# File lib/patchmaster/dsl.rb, line 319
def read_code_keys(contents)
  read_block_text('code_key', @code_keys, contents)
end
read_filters(contents) click to toggle source
# File lib/patchmaster/dsl.rb, line 315
def read_filters(contents)
  read_block_text('filter', @filters, contents)
end
read_triggers(contents) click to toggle source
# File lib/patchmaster/dsl.rb, line 311
def read_triggers(contents)
  read_block_text('trigger', @triggers, contents)
end
to_binding_key(key_or_sym) click to toggle source

Translate symbol like :f1 to the proper function key value.

# File lib/patchmaster/dsl.rb, line 295
def to_binding_key(key_or_sym)
  if key_or_sym.is_a?(Symbol) && PM::Main::FUNCTION_KEY_SYMBOLS[key_or_sym]
    key_or_sym = PM::Main::FUNCTION_KEY_SYMBOLS[key_or_sym]
  end
end
to_save_key(key) click to toggle source

Translate function key values into symbol strings and other keys into double-quoted strings.

# File lib/patchmaster/dsl.rb, line 303
def to_save_key(key)
  if PM::Main::FUNCTION_KEY_SYMBOLS.value?(key)
    PM::Main::FUNCTION_KEY_SYMBOLS.key(key)
  else
    key
  end
end