patchmaster 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/patchmaster +5 -2
- data/lib/patchmaster/app/info_window_contents.txt +1 -1
- data/lib/patchmaster/app/main.rb +53 -8
- data/lib/patchmaster/app/trigger_window.rb +2 -2
- data/lib/patchmaster/dsl.rb +32 -30
- data/lib/patchmaster/instrument.rb +26 -11
- data/lib/patchmaster/patchmaster.rb +45 -32
- data/lib/patchmaster/predicates.rb +5 -0
- data/test/test_helper.rb +7 -17
- metadata +5 -5
data/bin/patchmaster
CHANGED
@@ -12,17 +12,20 @@
|
|
12
12
|
require 'optparse'
|
13
13
|
|
14
14
|
use_midi = true
|
15
|
+
use_gui = true
|
15
16
|
OptionParser.new do |opts|
|
16
17
|
opts.banner = "usage: patchmaster [options] [pm_file]"
|
17
18
|
opts.on("-d", "--debug", "Turn on debug mode") { $DEBUG = true }
|
18
19
|
opts.on("-n", "--no-midi", "Turn off MIDI processing") { use_midi = false }
|
20
|
+
opts.on("-t", "--text", "--nw", "--no-window", "No windows") { use_gui = false }
|
19
21
|
end.parse!(ARGV)
|
20
22
|
|
21
23
|
# Must require patchmaster here, after handling options, because Singleton
|
22
|
-
#
|
24
|
+
# initialize code checks $DEBUG.
|
23
25
|
require 'patchmaster'
|
24
26
|
|
25
|
-
app = PM::Main.instance
|
27
|
+
app = use_gui ? PM::Main.instance : PM::PatchMaster.instance
|
28
|
+
app.no_gui! if !use_gui
|
26
29
|
app.no_midi! if !use_midi
|
27
30
|
app.load(ARGV[0]) if ARGV[0]
|
28
31
|
app.run
|
data/lib/patchmaster/app/main.rb
CHANGED
@@ -9,18 +9,21 @@ class Main
|
|
9
9
|
include Singleton
|
10
10
|
include Curses
|
11
11
|
|
12
|
+
FUNCTION_KEY_SYMBOLS = {}
|
13
|
+
12.times do |i|
|
14
|
+
FUNCTION_KEY_SYMBOLS["f#{i+1}".to_sym] = Key::F1 + i
|
15
|
+
FUNCTION_KEY_SYMBOLS["F#{i+1}".to_sym] = Key::F1 + i
|
16
|
+
end
|
17
|
+
|
12
18
|
def initialize
|
13
19
|
@pm = PatchMaster.instance
|
20
|
+
@message_bindings = {}
|
14
21
|
end
|
15
22
|
|
16
23
|
def no_midi!
|
17
24
|
@pm.no_midi!
|
18
25
|
end
|
19
26
|
|
20
|
-
def load(file)
|
21
|
-
@pm.load(file)
|
22
|
-
end
|
23
|
-
|
24
27
|
def run
|
25
28
|
@pm.start
|
26
29
|
begin
|
@@ -49,8 +52,9 @@ class Main
|
|
49
52
|
@pm.goto_song_list(name)
|
50
53
|
when 'e'
|
51
54
|
close_screen
|
52
|
-
@
|
53
|
-
|
55
|
+
file = @loaded_file || PromptWindow.new('Edit', 'Edit file:').gets
|
56
|
+
edit(file)
|
57
|
+
when 'h'
|
54
58
|
help
|
55
59
|
when 27 # "\e" doesn't work here
|
56
60
|
# Twice in a row sends individual note-off commands
|
@@ -60,7 +64,7 @@ class Main
|
|
60
64
|
when 'l'
|
61
65
|
file = PromptWindow.new('Load', 'Load file:').gets
|
62
66
|
begin
|
63
|
-
|
67
|
+
load(file)
|
64
68
|
message("Loaded #{file}")
|
65
69
|
rescue => ex
|
66
70
|
message(ex.to_s)
|
@@ -68,7 +72,7 @@ class Main
|
|
68
72
|
when 's'
|
69
73
|
file = PromptWindow.new('Save', 'Save into file:').gets
|
70
74
|
begin
|
71
|
-
|
75
|
+
save(file)
|
72
76
|
message("Saved #{file}")
|
73
77
|
rescue => ex
|
74
78
|
message(ex.to_s)
|
@@ -88,6 +92,9 @@ class Main
|
|
88
92
|
message(ex.to_s)
|
89
93
|
@pm.debug caller.join("\n")
|
90
94
|
end
|
95
|
+
|
96
|
+
msg_name = @message_bindings[ch]
|
97
|
+
@pm.send_message(msg_name) if msg_name
|
91
98
|
end
|
92
99
|
ensure
|
93
100
|
clear
|
@@ -98,6 +105,10 @@ class Main
|
|
98
105
|
end
|
99
106
|
end
|
100
107
|
|
108
|
+
def bind_message(name, key_sym)
|
109
|
+
@message_bindings[FUNCTION_KEY_SYMBOLS[key_sym]] = name
|
110
|
+
end
|
111
|
+
|
101
112
|
def config_curses
|
102
113
|
init_screen
|
103
114
|
cbreak # unbuffered input
|
@@ -133,6 +144,40 @@ class Main
|
|
133
144
|
|
134
145
|
end
|
135
146
|
|
147
|
+
def load(file)
|
148
|
+
@pm.load(file)
|
149
|
+
@loaded_file = file
|
150
|
+
end
|
151
|
+
|
152
|
+
def save(file)
|
153
|
+
@pm.save(file)
|
154
|
+
@loaded_file = file
|
155
|
+
end
|
156
|
+
|
157
|
+
# Opens the most recently loaded/saved file name in an editor. After
|
158
|
+
# editing, the file is re-loaded.
|
159
|
+
def edit(file)
|
160
|
+
editor_command = find_editor
|
161
|
+
unless editor_command
|
162
|
+
message("Can not find $VISUAL, $EDITOR, vim, or vi on your path")
|
163
|
+
return
|
164
|
+
end
|
165
|
+
|
166
|
+
cmd = "#{editor_command} #{file}"
|
167
|
+
@pm.debug(cmd)
|
168
|
+
system(cmd)
|
169
|
+
load(file)
|
170
|
+
@loaded_file = file
|
171
|
+
end
|
172
|
+
|
173
|
+
# Return the first legit command from $VISUAL, $EDITOR, vim, vi, and
|
174
|
+
# notepad.exe.
|
175
|
+
def find_editor
|
176
|
+
@editor ||= [ENV['VISUAL'], ENV['EDITOR'], 'vim', 'vi', 'notepad.exe'].compact.detect do |cmd|
|
177
|
+
system('which', cmd) || File.exist?(cmd)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
136
181
|
def help
|
137
182
|
message("Help: not yet implemented")
|
138
183
|
end
|
@@ -14,10 +14,10 @@ class TriggerWindow < PmWindow
|
|
14
14
|
super
|
15
15
|
pm = PM::PatchMaster.instance
|
16
16
|
i = 0
|
17
|
-
pm.inputs.each do |
|
17
|
+
pm.inputs.each do |instrument|
|
18
18
|
instrument.triggers.each do |trigger|
|
19
19
|
@win.setpos(i+1, 1)
|
20
|
-
@win.addstr(make_fit(":#{sym} #{trigger.to_s}"))
|
20
|
+
@win.addstr(make_fit(":#{instrument.sym} #{trigger.to_s}"))
|
21
21
|
i += 1
|
22
22
|
end
|
23
23
|
end
|
data/lib/patchmaster/dsl.rb
CHANGED
@@ -14,6 +14,8 @@ class DSL
|
|
14
14
|
|
15
15
|
def load(file)
|
16
16
|
contents = IO.read(file)
|
17
|
+
@inputs = {}
|
18
|
+
@outputs = {}
|
17
19
|
@triggers = []
|
18
20
|
@filters = []
|
19
21
|
@songs = {} # key = name, value = song
|
@@ -23,21 +25,39 @@ class DSL
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def input(port_num, sym, name=nil)
|
26
|
-
|
28
|
+
raise "input: two inputs can not have the same symbol (:#{sym})" if @inputs[sym]
|
29
|
+
|
30
|
+
input = InputInstrument.new(sym, name, port_num, @no_midi)
|
31
|
+
@inputs[sym] = input
|
32
|
+
@pm.inputs << input
|
27
33
|
rescue => ex
|
28
34
|
raise "input: error creating input instrument \"#{name}\" on input port #{port_num}: #{ex}"
|
29
35
|
end
|
30
36
|
alias_method :in, :input
|
31
37
|
|
32
38
|
def output(port_num, sym, name=nil)
|
33
|
-
|
39
|
+
raise "output: two outputs can not have the same symbol (:#{sym})" if @outputs[sym]
|
40
|
+
|
41
|
+
output = OutputInstrument.new(sym, name, port_num, @no_midi)
|
42
|
+
@outputs[sym] = output
|
43
|
+
@pm.outputs << output
|
34
44
|
rescue => ex
|
35
45
|
raise "output: error creating output instrument \"#{name}\" on output port #{port_num}: #{ex}"
|
36
46
|
end
|
37
47
|
alias_method :out, :output
|
38
48
|
|
49
|
+
def message(name, bytes)
|
50
|
+
@pm.messages[name] = bytes
|
51
|
+
end
|
52
|
+
|
53
|
+
def message_key(name, key_sym)
|
54
|
+
if !@pm.no_gui # TODO get rid of double negative
|
55
|
+
PM::Main.instance.bind_message(name, key_sym)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
39
59
|
def trigger(instrument_sym, bytes, &block)
|
40
|
-
instrument = @
|
60
|
+
instrument = @inputs[instrument_sym]
|
41
61
|
raise "trigger: error finding instrument #{instrument_sym}" unless instrument
|
42
62
|
t = Trigger.new(bytes, block)
|
43
63
|
instrument.triggers << t
|
@@ -65,10 +85,10 @@ class DSL
|
|
65
85
|
end
|
66
86
|
|
67
87
|
def connection(in_sym, in_chan, out_sym, out_chan)
|
68
|
-
input = @
|
88
|
+
input = @inputs[in_sym]
|
69
89
|
in_chan = nil if in_chan == :all || in_chan == :any
|
70
90
|
raise "can't find input instrument #{in_sym}" unless input
|
71
|
-
output = @
|
91
|
+
output = @outputs[out_sym]
|
72
92
|
raise "can't find outputput instrument #{out_sym}" unless output
|
73
93
|
|
74
94
|
@conn = Connection.new(input, in_chan, output, out_chan)
|
@@ -130,19 +150,19 @@ class DSL
|
|
130
150
|
end
|
131
151
|
|
132
152
|
def save_instruments(f)
|
133
|
-
@pm.inputs.each do |
|
134
|
-
f.puts "input #{instr.port_num}, :#{sym}, #{quoted(instr.name)}"
|
153
|
+
@pm.inputs.each do |instr|
|
154
|
+
f.puts "input #{instr.port_num}, :#{instr.sym}, #{quoted(instr.name)}"
|
135
155
|
end
|
136
|
-
@pm.outputs.each do |
|
137
|
-
f.puts "output #{instr.port_num}, :#{sym}, #{quoted(instr.name)}"
|
156
|
+
@pm.outputs.each do |instr|
|
157
|
+
f.puts "output #{instr.port_num}, :#{instr.sym}, #{quoted(instr.name)}"
|
138
158
|
end
|
139
159
|
f.puts
|
140
160
|
end
|
141
161
|
|
142
162
|
def save_triggers(f)
|
143
|
-
@pm.inputs.each do |
|
163
|
+
@pm.inputs.each do |instrument|
|
144
164
|
instrument.triggers.each do |trigger|
|
145
|
-
str = "trigger :#{sym}, #{trigger.bytes.inspect} #{trigger.text}"
|
165
|
+
str = "trigger :#{instrument.sym}, #{trigger.bytes.inspect} #{trigger.text}"
|
146
166
|
f.puts str
|
147
167
|
end
|
148
168
|
end
|
@@ -166,11 +186,9 @@ class DSL
|
|
166
186
|
end
|
167
187
|
|
168
188
|
def save_connection(f, conn)
|
169
|
-
in_sym = @pm.inputs.key(conn.input)
|
170
189
|
in_chan = conn.input_chan ? conn.input_chan + 1 : 'nil'
|
171
|
-
out_sym = @pm.outputs.key(conn.output)
|
172
190
|
out_chan = conn.output_chan + 1
|
173
|
-
f.puts " conn :#{
|
191
|
+
f.puts " conn :#{conn.input.sym}, #{in_chan}, :#{conn.output.sym}, #{out_chan} do"
|
174
192
|
f.puts " prog_chg #{conn.pc_prog}" if conn.pc?
|
175
193
|
f.puts " zone #{conn.note_num_to_name(conn.zone.begin)}, #{conn.note_num_to_name(conn.zone.end)}" if conn.zone
|
176
194
|
f.puts " xpose #{conn.xpose}" if conn.xpose
|
@@ -197,22 +215,6 @@ class DSL
|
|
197
215
|
|
198
216
|
private
|
199
217
|
|
200
|
-
def input_port(port)
|
201
|
-
if @no_midi
|
202
|
-
MockInputPort.new
|
203
|
-
else
|
204
|
-
UniMIDI::Input.all[port].open
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
def output_port(port)
|
209
|
-
if @no_midi
|
210
|
-
MockOutputPort.new
|
211
|
-
else
|
212
|
-
UniMIDI::Output.all[port].open
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
218
|
def read_triggers(contents)
|
217
219
|
read_block_text('trigger', @triggers, contents)
|
218
220
|
end
|
@@ -5,10 +5,11 @@ module PM
|
|
5
5
|
|
6
6
|
class Instrument
|
7
7
|
|
8
|
-
attr_reader :name, :port_num, :port
|
8
|
+
attr_reader :sym, :name, :port_num, :port
|
9
9
|
|
10
|
-
def initialize(name, port_num, port)
|
11
|
-
@name, @port_num, @port = name, port_num, port
|
10
|
+
def initialize(sym, name, port_num, port)
|
11
|
+
@sym, @name, @port_num, @port = sym, name, port_num, port
|
12
|
+
@name ||= @port.name if @port
|
12
13
|
end
|
13
14
|
|
14
15
|
end
|
@@ -21,8 +22,8 @@ class InputInstrument < Instrument
|
|
21
22
|
attr_reader :listener
|
22
23
|
|
23
24
|
# If +port+ is nil (the normal case), creates either a real or a mock port
|
24
|
-
def initialize(name, port_num, no_midi=false)
|
25
|
-
super(name, port_num, input_port(port_num, no_midi))
|
25
|
+
def initialize(sym, name, port_num, no_midi=false)
|
26
|
+
super(sym, name, port_num, input_port(port_num, no_midi))
|
26
27
|
@connections = []
|
27
28
|
@triggers = []
|
28
29
|
end
|
@@ -46,7 +47,10 @@ class InputInstrument < Instrument
|
|
46
47
|
def stop
|
47
48
|
PatchMaster.instance.debug("instrument #{name} stop")
|
48
49
|
@port.clear_buffer
|
49
|
-
@listener
|
50
|
+
if @listener
|
51
|
+
@listener.close
|
52
|
+
@listener = nil
|
53
|
+
end
|
50
54
|
end
|
51
55
|
|
52
56
|
# Passes MIDI bytes on to triggers and to each output connection.
|
@@ -59,7 +63,7 @@ class InputInstrument < Instrument
|
|
59
63
|
|
60
64
|
def input_port(port_num, no_midi=false)
|
61
65
|
if no_midi
|
62
|
-
MockInputPort.new
|
66
|
+
MockInputPort.new(port_num)
|
63
67
|
else
|
64
68
|
UniMIDI::Input.all[port_num].open
|
65
69
|
end
|
@@ -69,8 +73,8 @@ end
|
|
69
73
|
|
70
74
|
class OutputInstrument < Instrument
|
71
75
|
|
72
|
-
def initialize(name, port_num, no_midi=false)
|
73
|
-
super(name, port_num, output_port(port_num, no_midi))
|
76
|
+
def initialize(sym, name, port_num, no_midi=false)
|
77
|
+
super(sym, name, port_num, output_port(port_num, no_midi))
|
74
78
|
end
|
75
79
|
|
76
80
|
def midi_out(bytes)
|
@@ -81,7 +85,7 @@ class OutputInstrument < Instrument
|
|
81
85
|
|
82
86
|
def output_port(port_num, no_midi)
|
83
87
|
if no_midi
|
84
|
-
MockOutputPort.new
|
88
|
+
MockOutputPort.new(port_num)
|
85
89
|
else
|
86
90
|
UniMIDI::Output.all[port_num].open
|
87
91
|
end
|
@@ -90,12 +94,16 @@ end
|
|
90
94
|
|
91
95
|
class MockInputPort
|
92
96
|
|
97
|
+
attr_reader :name
|
98
|
+
|
93
99
|
# For MIDIEye::Listener
|
94
100
|
def self.is_compatible?(input)
|
95
101
|
true
|
96
102
|
end
|
97
103
|
|
98
|
-
|
104
|
+
# Constructor param is ignored; it's required by MIDIEye.
|
105
|
+
def initialize(arg)
|
106
|
+
@name = "MockInputPort #{arg}"
|
99
107
|
end
|
100
108
|
|
101
109
|
def gets
|
@@ -115,6 +123,13 @@ class MockInputPort
|
|
115
123
|
end
|
116
124
|
|
117
125
|
class MockOutputPort
|
126
|
+
|
127
|
+
attr_reader :name
|
128
|
+
|
129
|
+
def initialize(port_num)
|
130
|
+
@name = "MockOutputPort #{port_num}"
|
131
|
+
end
|
132
|
+
|
118
133
|
def puts(data)
|
119
134
|
end
|
120
135
|
end
|
@@ -20,7 +20,9 @@ class PatchMaster < SimpleDelegator
|
|
20
20
|
|
21
21
|
include Singleton
|
22
22
|
|
23
|
-
attr_reader :inputs, :outputs, :all_songs, :song_lists
|
23
|
+
attr_reader :inputs, :outputs, :all_songs, :song_lists
|
24
|
+
attr_reader :messages
|
25
|
+
attr_reader :no_midi, :no_gui
|
24
26
|
|
25
27
|
# A Cursor to which we delegate incoming position methods (#song_list,
|
26
28
|
# #song, #patch, #next_song, #prev_patch, etc.)
|
@@ -29,11 +31,12 @@ class PatchMaster < SimpleDelegator
|
|
29
31
|
def initialize
|
30
32
|
@cursor = Cursor.new(self)
|
31
33
|
super(@cursor)
|
34
|
+
@no_midi = false
|
35
|
+
@no_gui = false
|
32
36
|
|
33
37
|
if $DEBUG
|
34
38
|
@debug_file = File.open(DEBUG_FILE, 'a')
|
35
39
|
end
|
36
|
-
@no_midi = false
|
37
40
|
|
38
41
|
init_data
|
39
42
|
end
|
@@ -42,6 +45,10 @@ class PatchMaster < SimpleDelegator
|
|
42
45
|
@no_midi = true
|
43
46
|
end
|
44
47
|
|
48
|
+
def no_gui!
|
49
|
+
@no_gui = true
|
50
|
+
end
|
51
|
+
|
45
52
|
# Loads +file+. Does its best to restore the current song list, song, and
|
46
53
|
# patch after loading.
|
47
54
|
def load(file)
|
@@ -51,7 +58,6 @@ class PatchMaster < SimpleDelegator
|
|
51
58
|
@cursor.mark
|
52
59
|
init_data
|
53
60
|
DSL.new(@no_midi).load(file)
|
54
|
-
@loaded_file = file
|
55
61
|
@cursor.restore
|
56
62
|
|
57
63
|
if restart
|
@@ -65,7 +71,6 @@ class PatchMaster < SimpleDelegator
|
|
65
71
|
|
66
72
|
def save(file)
|
67
73
|
DSL.new(@no_midi).save(file)
|
68
|
-
@loaded_file = file
|
69
74
|
rescue => ex
|
70
75
|
raise("error saving #{file}: #{ex}" + caller.join("\n"))
|
71
76
|
end
|
@@ -73,11 +78,12 @@ class PatchMaster < SimpleDelegator
|
|
73
78
|
# Initializes the cursor and all data.
|
74
79
|
def init_data
|
75
80
|
@cursor.clear
|
76
|
-
@inputs =
|
77
|
-
@outputs =
|
81
|
+
@inputs = []
|
82
|
+
@outputs = []
|
78
83
|
@song_lists = []
|
79
84
|
@all_songs = SortedSongList.new('All Songs')
|
80
85
|
@song_lists << @all_songs
|
86
|
+
@messages = {}
|
81
87
|
end
|
82
88
|
|
83
89
|
# If +init_cursor+ is +true+ (the default), initializes current song list,
|
@@ -86,25 +92,55 @@ class PatchMaster < SimpleDelegator
|
|
86
92
|
@cursor.init if init_cursor
|
87
93
|
@cursor.patch.start if @cursor.patch
|
88
94
|
@running = true
|
89
|
-
@inputs.
|
95
|
+
@inputs.map(&:start)
|
90
96
|
end
|
91
97
|
|
98
|
+
# Stop everything, including input instruments' MIDIEye listener threads.
|
92
99
|
def stop
|
93
100
|
@cursor.patch.stop if @cursor.patch
|
94
|
-
@inputs.
|
101
|
+
@inputs.map(&:stop)
|
95
102
|
@running = false
|
96
103
|
end
|
97
104
|
|
105
|
+
# Run PatchMaster without the GUI. Don't use this when using PM::Main.
|
106
|
+
#
|
107
|
+
# Call #start, wait for inputs' MIDIEye listener threads to finish, then
|
108
|
+
# call #stop. Note that normally nothing stops those threads, so this is
|
109
|
+
# used as a way to make sure the script doesn't quit until killed by
|
110
|
+
# something like SIGINT.
|
111
|
+
def run
|
112
|
+
start(true)
|
113
|
+
@inputs.each { |input| input.listener.join }
|
114
|
+
stop
|
115
|
+
end
|
116
|
+
|
98
117
|
def running?
|
99
118
|
@running
|
100
119
|
end
|
101
120
|
|
121
|
+
# Send the message with the given name to all outputs.
|
122
|
+
def send_message(name)
|
123
|
+
msg = @messages[name]
|
124
|
+
if !msg
|
125
|
+
message("Message \"#{name}\" not found")
|
126
|
+
return
|
127
|
+
end
|
128
|
+
|
129
|
+
debug("Sending message \"#{name}\"")
|
130
|
+
@outputs.each { |out| out.midi_out(msg) }
|
131
|
+
|
132
|
+
# If the user accidentally calls send_message in a filter at the end,
|
133
|
+
# then the filter will return whatever this method returns. Just in
|
134
|
+
# case, return nil instead of whatever the preceding code would return.
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
|
102
138
|
# Sends the +CM_ALL_NOTES_OFF+ controller message to all output
|
103
139
|
# instruments on all 16 MIDI channels. If +individual_notes+ is +true+
|
104
140
|
# send individual +NOTE_OFF+ messages to all notes as well.
|
105
141
|
def panic(individual_notes=false)
|
106
142
|
debug("panic(#{individual_notes})")
|
107
|
-
@outputs.
|
143
|
+
@outputs.each do |out|
|
108
144
|
MIDI_CHANNELS.times do |chan|
|
109
145
|
out.midi_out([CONTROLLER + chan, CM_ALL_NOTES_OFF, 0])
|
110
146
|
if individual_notes
|
@@ -114,29 +150,6 @@ class PatchMaster < SimpleDelegator
|
|
114
150
|
end
|
115
151
|
end
|
116
152
|
|
117
|
-
# Opens the most recently loaded/saved file name in an editor. After
|
118
|
-
# editing, the file is re-loaded.
|
119
|
-
def edit
|
120
|
-
editor_command = find_editor
|
121
|
-
unless editor_command
|
122
|
-
message("Can not find $VISUAL, $EDITOR, vim, or vi on your path")
|
123
|
-
return
|
124
|
-
end
|
125
|
-
|
126
|
-
cmd = "#{editor_command} #{@loaded_file}"
|
127
|
-
debug(cmd)
|
128
|
-
system(cmd)
|
129
|
-
load(@loaded_file)
|
130
|
-
end
|
131
|
-
|
132
|
-
# Return the first legit command from $VISUAL, $EDITOR, vim, vi, and
|
133
|
-
# notepad.exe.
|
134
|
-
def find_editor
|
135
|
-
@editor ||= [ENV['VISUAL'], ENV['EDITOR'], 'vim', 'vi', 'notepad.exe'].compact.detect do |cmd|
|
136
|
-
system('which', cmd) || File.exist?(cmd)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
153
|
# Output +str+ to @debug_file or $stderr.
|
141
154
|
def debug(str)
|
142
155
|
return unless $DEBUG
|
@@ -62,6 +62,11 @@ class Integer
|
|
62
62
|
alias_method :rt?, :realtime?
|
63
63
|
end
|
64
64
|
|
65
|
+
# All the methods here delegate to the first byte in the array, so for
|
66
|
+
# example the following two are equivalent:
|
67
|
+
#
|
68
|
+
# my_array.note_on?
|
69
|
+
# my_array[0].note_on?
|
65
70
|
class Array
|
66
71
|
|
67
72
|
def high_nibble
|
data/test/test_helper.rb
CHANGED
@@ -7,19 +7,15 @@ PM::PatchMaster.instance.no_midi!
|
|
7
7
|
|
8
8
|
module PM
|
9
9
|
|
10
|
-
# To help with testing, we replace MockInputPort#gets and
|
11
|
-
# MockOutputPort#puts with versions that send what we want and save what
|
12
|
-
# received.
|
10
|
+
# To help with testing, we replace PM::MockInputPort#gets and
|
11
|
+
# PM::MockOutputPort#puts with versions that send what we want and save what
|
12
|
+
# is received.
|
13
13
|
class MockInputPort
|
14
14
|
|
15
15
|
attr_accessor :data_to_send
|
16
|
-
|
17
|
-
# For MIDIEye::Listener
|
18
|
-
def self.is_compatible?(input)
|
19
|
-
true
|
20
|
-
end
|
21
16
|
|
22
|
-
def initialize(
|
17
|
+
def initialize(arg)
|
18
|
+
@name = "MockInputPort #{arg}"
|
23
19
|
@t0 = (Time.now.to_f * 1000).to_i
|
24
20
|
end
|
25
21
|
|
@@ -28,20 +24,14 @@ class MockInputPort
|
|
28
24
|
@data_to_send = []
|
29
25
|
[{:data => retval, :timestamp => (Time.now.to_f * 1000).to_i - @t0}]
|
30
26
|
end
|
31
|
-
|
32
|
-
def poll
|
33
|
-
yield gets
|
34
|
-
end
|
35
|
-
|
36
|
-
# add this class to the Listener class' known input types
|
37
|
-
MIDIEye::Listener.input_types << self
|
38
27
|
end
|
39
28
|
|
40
29
|
class MockOutputPort
|
41
30
|
|
42
31
|
attr_accessor :buffer
|
43
32
|
|
44
|
-
def initialize
|
33
|
+
def initialize(port_num)
|
34
|
+
@name = "MockOutputPort #{port_num}"
|
45
35
|
@buffer = []
|
46
36
|
end
|
47
37
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: patchmaster
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-04-
|
12
|
+
date: 2012-04-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
16
|
-
requirement: &
|
15
|
+
name: midi-eye
|
16
|
+
requirement: &2156041300 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2156041300
|
25
25
|
description: ! 'PatchMaster is realtime MIDI performance software that alloweds a
|
26
26
|
musician
|
27
27
|
|