lib/patchmaster/dsl.rb in patchmaster-1.1.2 vs lib/patchmaster/dsl.rb in patchmaster-2.0.0

- old
+ new

@@ -1,6 +1,7 @@ require 'unimidi' +require_relative './code_chunk' module PM # Implements a DSL for describing a PatchMaster setup. class DSL @@ -16,17 +17,19 @@ def init @inputs = {} @outputs = {} @triggers = [] @filters = [] + @code_keys = [] @songs = {} # key = name, value = song end def load(file) contents = IO.read(file) init instance_eval(contents) + read_code_keys(contents) read_triggers(contents) read_filters(contents) end def input(port_num, sym, name=nil) @@ -48,25 +51,46 @@ @pm.outputs << output rescue => ex raise "output: error creating output instrument \"#{name || sym}\" on output port #{port_num}: #{ex}" end alias_method :out, :output + alias_method :outp, :output def message(name, bytes) - @pm.messages[name.downcase] = bytes + @pm.messages[name.downcase] = [name, bytes] end - def message_key(name, key_or_sym) - if @pm.gui - @pm.gui.bind_message(name, key_or_sym) + 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 + 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 + def trigger(instrument_sym, bytes, &block) instrument = @inputs[instrument_sym] raise "trigger: error finding instrument #{instrument_sym}" unless instrument - t = Trigger.new(bytes, block) + t = Trigger.new(bytes, CodeChunk.new(block)) instrument.triggers << t @triggers << t end def song(name) @@ -143,11 +167,11 @@ end alias_method :xpose, :transpose alias_method :x, :transpose def filter(&block) - @conn.filter = Filter.new(block) + @conn.filter = Filter.new(CodeChunk.new(block)) @filters << @conn.filter end alias_method :f, :filter def song_list(name, song_names) @@ -171,47 +195,73 @@ # **************************************************************** 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 def save_instruments(f) @pm.inputs.each do |instr| - f.puts "input #{instr.port_num}, :#{instr.sym}, #{quoted(instr.name)}" + 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}, #{quoted(instr.name)}" + f.puts "output #{instr.port_num}, :#{instr.sym}, #{instr.name.inspect}" end f.puts end + def save_messages(f) + @pm.messages.each do |_, (correct_case_name, msg)| + f.puts "message #{correct_case_name.inspect}, #{msg.inspect}" + end + end + + 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 + + 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 + def save_triggers(f) @pm.inputs.each do |instrument| instrument.triggers.each do |trigger| - str = "trigger :#{instrument.sym}, #{trigger.bytes.inspect} #{trigger.text}" + str = "trigger :#{instrument.sym}, #{trigger.bytes.inspect} #{trigger.code_chunk.text}" f.puts str end end f.puts end def save_songs(f) @pm.all_songs.songs.each do |song| - f.puts "song #{quoted(song.name)} do" + f.puts "song #{song.name.inspect} do" song.patches.each { |patch| save_patch(f, patch) } f.puts "end" f.puts end end def save_patch(f, patch) - f.puts " patch #{quoted(patch.name)} do" + 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 @@ -220,53 +270,72 @@ 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.text}" if conn.filter + f.puts " filter #{conn.filter.code_chunk.text}" if conn.filter f.puts " end" end def save_song_lists(f) @pm.song_lists.each do |sl| next if sl == @pm.all_songs - f.puts "song_list #{quoted(sl.name)}, [" + f.puts "song_list #{sl.name.inspect}, [" @pm.all_songs.songs.each do |song| - f.puts " #{quoted(song.name)}," + f.puts " #{song.name.inspect}," end f.puts "]" end end - def quoted(str) - "\"#{str.gsub('"', "\\\"")}\"" # ' <= un-confuse Emacs font-lock - end - # **************************************************************** private + # Translate symbol like :f1 to the proper function key value. + 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 + + # Translate function key values into symbol strings and other keys into + # double-quoted strings. + def to_save_key(key) + if PM::Main::FUNCTION_KEY_SYMBOLS.value?(key) + PM::Main::FUNCTION_KEY_SYMBOLS.key(key) + else + key + end + end + def read_triggers(contents) read_block_text('trigger', @triggers, contents) end def read_filters(contents) read_block_text('filter', @filters, contents) end + def read_code_keys(contents) + read_block_text('code_key', @code_keys, contents) + end + # Extremely simple block text reader. Relies on indentation to detect end # of code block. 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 - containers[i].text = text + "\n" + chunk = containers[i].code_chunk + chunk.text = text + "\n" in_block = true block_end_token = case text when /^{/ "}" when /^do\b/ @@ -279,18 +348,21 @@ elsif in_block line =~ /^(\s*)(.*)/ indentation, text = $1, $2 if indentation.length <= block_indentation.length if text =~ /^#{block_end_token}/ - containers[i].text << line + chunk.text << line end in_block = false else - containers[i].text << line + chunk.text << line end end end - containers.each { |thing| thing.text.strip! if thing.text } + containers.each do |thing| + text = thing.code_chunk.text + text.strip! if text + end end end end