patchmaster 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|