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