patchmaster 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/patchmaster +20 -0
- data/lib/patchmaster.rb +15 -0
- data/lib/patchmaster/app/info_window.rb +32 -0
- data/lib/patchmaster/app/info_window_contents.txt +15 -0
- data/lib/patchmaster/app/list_window.rb +26 -0
- data/lib/patchmaster/app/main.rb +161 -0
- data/lib/patchmaster/app/patch_window.rb +61 -0
- data/lib/patchmaster/app/pm_window.rb +41 -0
- data/lib/patchmaster/app/prompt_window.rb +61 -0
- data/lib/patchmaster/connection.rb +90 -0
- data/lib/patchmaster/consts.rb +439 -0
- data/lib/patchmaster/dsl.rb +227 -0
- data/lib/patchmaster/filter.rb +23 -0
- data/lib/patchmaster/io.rb +87 -0
- data/lib/patchmaster/list.rb +121 -0
- data/lib/patchmaster/list_container.rb +36 -0
- data/lib/patchmaster/patch.rb +48 -0
- data/lib/patchmaster/patchmaster.rb +168 -0
- data/lib/patchmaster/predicates.rb +125 -0
- data/lib/patchmaster/song.rb +24 -0
- data/lib/patchmaster/song_list.rb +39 -0
- data/lib/patchmaster/sorted_song_list.rb +15 -0
- data/test/test_helper.rb +58 -0
- metadata +88 -0
@@ -0,0 +1,439 @@
|
|
1
|
+
# MIDI and PatchMaster constants.
|
2
|
+
module PM
|
3
|
+
|
4
|
+
# Define MIDI note names C0 - B10
|
5
|
+
(0..10).each { |oct|
|
6
|
+
{:C => 0, :D => 2, :E => 4, :F => 5, :G => 7, :A => 9, :B => 11}.each { |note,val|
|
7
|
+
base = (oct+1) * 12 + val
|
8
|
+
eval <<EOS
|
9
|
+
#{note}f#{oct} = #{base - 1}
|
10
|
+
#{note}b#{oct} = #{base - 1}
|
11
|
+
#{note}#{oct} = #{base}
|
12
|
+
#{note}s#{oct} = #{base + 1}
|
13
|
+
EOS
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
# Number of MIDI channels
|
18
|
+
MIDI_CHANNELS = 16
|
19
|
+
# Number of note per MIDI channel
|
20
|
+
NOTES_PER_CHANNEL = 128
|
21
|
+
|
22
|
+
#--
|
23
|
+
# Standard MIDI File meta event defs.
|
24
|
+
#++
|
25
|
+
META_EVENT = 0xff
|
26
|
+
META_SEQ_NUM = 0x00
|
27
|
+
META_TEXT = 0x01
|
28
|
+
META_COPYRIGHT = 0x02
|
29
|
+
META_SEQ_NAME = 0x03
|
30
|
+
META_INSTRUMENT = 0x04
|
31
|
+
META_LYRIC = 0x05
|
32
|
+
META_MARKER = 0x06
|
33
|
+
META_CUE = 0x07
|
34
|
+
META_MIDI_CHAN_PREFIX = 0x20
|
35
|
+
META_TRACK_END = 0x2f
|
36
|
+
META_SET_TEMPO = 0x51
|
37
|
+
META_SMPTE = 0x54
|
38
|
+
META_TIME_SIG = 0x58
|
39
|
+
META_PATCH_SIG = 0x59
|
40
|
+
META_SEQ_SPECIF = 0x7f
|
41
|
+
|
42
|
+
#--
|
43
|
+
# Channel messages
|
44
|
+
#++
|
45
|
+
# Note, val
|
46
|
+
NOTE_OFF = 0x80
|
47
|
+
# Note, val
|
48
|
+
NOTE_ON = 0x90
|
49
|
+
# Note, val
|
50
|
+
POLY_PRESSURE = 0xA0
|
51
|
+
# Controller #, val
|
52
|
+
CONTROLLER = 0xB0
|
53
|
+
# Program number
|
54
|
+
PROGRAM_CHANGE = 0xC0
|
55
|
+
# Channel pressure
|
56
|
+
CHANNEL_PRESSURE = 0xD0
|
57
|
+
# LSB, MSB
|
58
|
+
PITCH_BEND = 0xE0
|
59
|
+
|
60
|
+
#--
|
61
|
+
# System common messages
|
62
|
+
#++
|
63
|
+
# System exclusive start
|
64
|
+
SYSEX = 0xF0
|
65
|
+
# Beats from top: LSB/MSB 6 ticks = 1 beat
|
66
|
+
SONG_POINTER = 0xF2
|
67
|
+
# Val = number of song
|
68
|
+
SONG_SELECT = 0xF3
|
69
|
+
# Tune request
|
70
|
+
TUNE_REQUEST = 0xF6
|
71
|
+
# End of system exclusive
|
72
|
+
EOX = 0xF7
|
73
|
+
|
74
|
+
#--
|
75
|
+
# System realtime messages
|
76
|
+
#++
|
77
|
+
# MIDI clock (24 per quarter note)
|
78
|
+
CLOCK = 0xF8
|
79
|
+
# Sequence start
|
80
|
+
START = 0xFA
|
81
|
+
# Sequence continue
|
82
|
+
CONTINUE = 0xFB
|
83
|
+
# Sequence stop
|
84
|
+
STOP = 0xFC
|
85
|
+
# Active sensing (sent every 300 ms when nothing else being sent)
|
86
|
+
ACTIVE_SENSE = 0xFE
|
87
|
+
# System reset
|
88
|
+
SYSTEM_RESET = 0xFF
|
89
|
+
|
90
|
+
# Controller numbers
|
91
|
+
# = 0 - 31 = continuous, LSB
|
92
|
+
# = 32 - 63 = continuous, MSB
|
93
|
+
# = 64 - 97 = switches
|
94
|
+
CC_MOD_WHEEL = 1
|
95
|
+
CC_BREATH_CONTROLLER = 2
|
96
|
+
CC_FOOT_CONTROLLER = 4
|
97
|
+
CC_PORTAMENTO_TIME = 5
|
98
|
+
CC_DATA_ENTRY_MSB = 6
|
99
|
+
CC_VOLUME = 7
|
100
|
+
CC_BALANCE = 8
|
101
|
+
CC_PAN = 10
|
102
|
+
CC_EXPRESSION_CONTROLLER = 11
|
103
|
+
CC_GEN_PURPOSE_1 = 16
|
104
|
+
CC_GEN_PURPOSE_2 = 17
|
105
|
+
CC_GEN_PURPOSE_3 = 18
|
106
|
+
CC_GEN_PURPOSE_4 = 19
|
107
|
+
|
108
|
+
# [32 - 63] are LSB for [0 - 31]
|
109
|
+
CC_DATA_ENTRY_LSB = 38
|
110
|
+
|
111
|
+
#--
|
112
|
+
# Momentaries:
|
113
|
+
#++
|
114
|
+
CC_SUSTAIN = 64
|
115
|
+
CC_PORTAMENTO = 65
|
116
|
+
CC_SUSTENUTO = 66
|
117
|
+
CC_SOFT_PEDAL = 67
|
118
|
+
CC_HOLD_2 = 69
|
119
|
+
CC_GEN_PURPOSE_5 = 50
|
120
|
+
CC_GEN_PURPOSE_6 = 51
|
121
|
+
CC_GEN_PURPOSE_7 = 52
|
122
|
+
CC_GEN_PURPOSE_8 = 53
|
123
|
+
CC_TREMELO_DEPTH = 92
|
124
|
+
CC_CHORUS_DEPTH = 93
|
125
|
+
CC_DETUNE_DEPTH = 94
|
126
|
+
CC_PHASER_DEPTH = 95
|
127
|
+
CC_DATA_INCREMENT = 96
|
128
|
+
CC_DATA_DECREMENT = 97
|
129
|
+
CC_NREG_PARAM_LSB = 98
|
130
|
+
CC_NREG_PARAM_MSB = 99
|
131
|
+
CC_REG_PARAM_LSB = 100
|
132
|
+
CC_REG_PARAM_MSB = 101
|
133
|
+
|
134
|
+
#--
|
135
|
+
# Channel mode message values
|
136
|
+
#++
|
137
|
+
# Val 0 == off, 0x7f == on
|
138
|
+
CM_LOCAL_CONTROL = 0x7A
|
139
|
+
CM_ALL_NOTES_OFF = 0x7B # Val must be 0
|
140
|
+
CM_OMNI_MODE_OFF = 0x7C # Val must be 0
|
141
|
+
CM_OMNI_MODE_ON = 0x7D # Val must be 0
|
142
|
+
CM_MONO_MODE_ON = 0x7E # Val = # chans
|
143
|
+
CM_POLY_MODE_ON = 0x7F # Val must be 0
|
144
|
+
|
145
|
+
# Controller names
|
146
|
+
CONTROLLER_NAMES = [
|
147
|
+
"0",
|
148
|
+
"Modulation",
|
149
|
+
"Breath Control",
|
150
|
+
"3",
|
151
|
+
"Foot Controller",
|
152
|
+
"Portamento Time",
|
153
|
+
"Data Entry",
|
154
|
+
"Volume",
|
155
|
+
"Balance",
|
156
|
+
"9",
|
157
|
+
"Pan",
|
158
|
+
"Expression Control",
|
159
|
+
"12", "13", "14", "15",
|
160
|
+
"General Controller 1",
|
161
|
+
"General Controller 2",
|
162
|
+
"General Controller 3",
|
163
|
+
"General Controller 4",
|
164
|
+
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
|
165
|
+
"30", "31",
|
166
|
+
"32", "33", "34", "35", "36", "37", "38", "39", "40", "41",
|
167
|
+
"42", "43", "44", "45", "46", "47", "48", "49", "50", "51",
|
168
|
+
"52", "53", "54", "55", "56", "57", "58", "59", "60", "61",
|
169
|
+
"62", "63",
|
170
|
+
"Sustain Pedal",
|
171
|
+
"Portamento",
|
172
|
+
"Sostenuto",
|
173
|
+
"Soft Pedal",
|
174
|
+
"68",
|
175
|
+
"Hold 2",
|
176
|
+
"70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
|
177
|
+
"General Controller 5",
|
178
|
+
"Tempo Change",
|
179
|
+
"General Controller 7",
|
180
|
+
"General Controller 8",
|
181
|
+
"84", "85", "86", "87", "88", "89", "90",
|
182
|
+
"External Effects Depth",
|
183
|
+
"Tremolo Depth",
|
184
|
+
"Chorus Depth",
|
185
|
+
"Detune (Celeste) Depth",
|
186
|
+
"Phaser Depth",
|
187
|
+
"Data Increment",
|
188
|
+
"Data Decrement",
|
189
|
+
"Non-Registered Param LSB",
|
190
|
+
"Non-Registered Param MSB",
|
191
|
+
"Registered Param LSB",
|
192
|
+
"Registered Param MSB",
|
193
|
+
"102", "103", "104", "105", "106", "107", "108", "109",
|
194
|
+
"110", "111", "112", "113", "114", "115", "116", "117",
|
195
|
+
"118", "119", "120",
|
196
|
+
"Reset All Controllers",
|
197
|
+
"Local Control",
|
198
|
+
"All Notes Off",
|
199
|
+
"Omni Mode Off",
|
200
|
+
"Omni Mode On",
|
201
|
+
"Mono Mode On",
|
202
|
+
"Poly Mode On"
|
203
|
+
]
|
204
|
+
|
205
|
+
# General MIDI patch names
|
206
|
+
GM_PATCH_NAMES = [
|
207
|
+
#--
|
208
|
+
# Pianos
|
209
|
+
#++
|
210
|
+
"Acoustic Grand Piano",
|
211
|
+
"Bright Acoustic Piano",
|
212
|
+
"Electric Grand Piano",
|
213
|
+
"Honky-tonk Piano",
|
214
|
+
"Electric Piano 1",
|
215
|
+
"Electric Piano 2",
|
216
|
+
"Harpsichord",
|
217
|
+
"Clavichord",
|
218
|
+
#--
|
219
|
+
# Tuned Idiophones
|
220
|
+
#++
|
221
|
+
"Celesta",
|
222
|
+
"Glockenspiel",
|
223
|
+
"Music Box",
|
224
|
+
"Vibraphone",
|
225
|
+
"Marimba",
|
226
|
+
"Xylophone",
|
227
|
+
"Tubular Bells",
|
228
|
+
"Dulcimer",
|
229
|
+
#--
|
230
|
+
# Organs
|
231
|
+
#++
|
232
|
+
"Drawbar Organ",
|
233
|
+
"Percussive Organ",
|
234
|
+
"Rock Organ",
|
235
|
+
"Church Organ",
|
236
|
+
"Reed Organ",
|
237
|
+
"Accordion",
|
238
|
+
"Harmonica",
|
239
|
+
"Tango Accordion",
|
240
|
+
#--
|
241
|
+
# Guitars
|
242
|
+
#++
|
243
|
+
"Acoustic Guitar (nylon)",
|
244
|
+
"Acoustic Guitar (steel)",
|
245
|
+
"Electric Guitar (jazz)",
|
246
|
+
"Electric Guitar (clean)",
|
247
|
+
"Electric Guitar (muted)",
|
248
|
+
"Overdriven Guitar",
|
249
|
+
"Distortion Guitar",
|
250
|
+
"Guitar harmonics",
|
251
|
+
#--
|
252
|
+
# Basses
|
253
|
+
#++
|
254
|
+
"Acoustic Bass",
|
255
|
+
"Electric Bass (finger)",
|
256
|
+
"Electric Bass (pick)",
|
257
|
+
"Fretless Bass",
|
258
|
+
"Slap Bass 1",
|
259
|
+
"Slap Bass 2",
|
260
|
+
"Synth Bass 1",
|
261
|
+
"Synth Bass 2",
|
262
|
+
#--
|
263
|
+
# Strings
|
264
|
+
#++
|
265
|
+
"Violin",
|
266
|
+
"Viola",
|
267
|
+
"Cello",
|
268
|
+
"Contrabass",
|
269
|
+
"Tremolo Strings",
|
270
|
+
"Pizzicato Strings",
|
271
|
+
"Orchestral Harp",
|
272
|
+
"Timpani",
|
273
|
+
#--
|
274
|
+
# Ensemble strings and voices
|
275
|
+
#++
|
276
|
+
"String Ensemble 1",
|
277
|
+
"String Ensemble 2",
|
278
|
+
"SynthStrings 1",
|
279
|
+
"SynthStrings 2",
|
280
|
+
"Choir Aahs",
|
281
|
+
"Voice Oohs",
|
282
|
+
"Synth Voice",
|
283
|
+
"Orchestra Hit",
|
284
|
+
#--
|
285
|
+
# Brass
|
286
|
+
#++
|
287
|
+
"Trumpet",
|
288
|
+
"Trombone",
|
289
|
+
"Tuba",
|
290
|
+
"Muted Trumpet",
|
291
|
+
"French Horn",
|
292
|
+
"Brass Section",
|
293
|
+
"SynthBrass 1",
|
294
|
+
"SynthBrass 2",
|
295
|
+
#--
|
296
|
+
# Reeds
|
297
|
+
#++
|
298
|
+
"Soprano Sax", # 64
|
299
|
+
"Alto Sax",
|
300
|
+
"Tenor Sax",
|
301
|
+
"Baritone Sax",
|
302
|
+
"Oboe",
|
303
|
+
"English Horn",
|
304
|
+
"Bassoon",
|
305
|
+
"Clarinet",
|
306
|
+
#--
|
307
|
+
# Pipes
|
308
|
+
#++
|
309
|
+
"Piccolo",
|
310
|
+
"Flute",
|
311
|
+
"Recorder",
|
312
|
+
"Pan Flute",
|
313
|
+
"Blown Bottle",
|
314
|
+
"Shakuhachi",
|
315
|
+
"Whistle",
|
316
|
+
"Ocarina",
|
317
|
+
#--
|
318
|
+
# Synth Leads
|
319
|
+
#++
|
320
|
+
"Lead 1 (square)",
|
321
|
+
"Lead 2 (sawtooth)",
|
322
|
+
"Lead 3 (calliope)",
|
323
|
+
"Lead 4 (chiff)",
|
324
|
+
"Lead 5 (charang)",
|
325
|
+
"Lead 6 (voice)",
|
326
|
+
"Lead 7 (fifths)",
|
327
|
+
"Lead 8 (bass + lead)",
|
328
|
+
#--
|
329
|
+
# Synth Pads
|
330
|
+
#++
|
331
|
+
"Pad 1 (new age)",
|
332
|
+
"Pad 2 (warm)",
|
333
|
+
"Pad 3 (polysynth)",
|
334
|
+
"Pad 4 (choir)",
|
335
|
+
"Pad 5 (bowed)",
|
336
|
+
"Pad 6 (metallic)",
|
337
|
+
"Pad 7 (halo)",
|
338
|
+
"Pad 8 (sweep)",
|
339
|
+
#--
|
340
|
+
# Effects
|
341
|
+
#++
|
342
|
+
"FX 1 (rain)",
|
343
|
+
"FX 2 (soundtrack)",
|
344
|
+
"FX 3 (crystal)",
|
345
|
+
"FX 4 (atmosphere)",
|
346
|
+
"FX 5 (brightness)",
|
347
|
+
"FX 6 (goblins)",
|
348
|
+
"FX 7 (echoes)",
|
349
|
+
"FX 8 (sci-fi)",
|
350
|
+
#--
|
351
|
+
# Ethnic
|
352
|
+
#++
|
353
|
+
"Sitar",
|
354
|
+
"Banjo",
|
355
|
+
"Shamisen",
|
356
|
+
"Koto",
|
357
|
+
"Kalimba",
|
358
|
+
"Bag pipe",
|
359
|
+
"Fiddle",
|
360
|
+
"Shanai",
|
361
|
+
#--
|
362
|
+
# Percussion
|
363
|
+
#++
|
364
|
+
"Tinkle Bell",
|
365
|
+
"Agogo",
|
366
|
+
"Steel Drums",
|
367
|
+
"Woodblock",
|
368
|
+
"Taiko Drum",
|
369
|
+
"Melodic Tom",
|
370
|
+
"Synth Drum",
|
371
|
+
"Reverse Cymbal",
|
372
|
+
#--
|
373
|
+
# Sound Effects
|
374
|
+
#++
|
375
|
+
"Guitar Fret Noise",
|
376
|
+
"Breath Noise",
|
377
|
+
"Seashore",
|
378
|
+
"Bird Tweet",
|
379
|
+
"Telephone Ring",
|
380
|
+
"Helicopter",
|
381
|
+
"Applause",
|
382
|
+
"Gunshot"
|
383
|
+
]
|
384
|
+
|
385
|
+
# GM drum notes start at 35 (C), so subtrack GM_DRUM_NOTE_LOWEST from your
|
386
|
+
# note number before using this array.
|
387
|
+
GM_DRUM_NOTE_LOWEST = 35
|
388
|
+
# General MIDI drum channel note names.
|
389
|
+
GM_DRUM_NOTE_NAMES = [
|
390
|
+
"Acoustic Bass Drum", # 35, C
|
391
|
+
"Bass Drum 1", # 36, C#
|
392
|
+
"Side Stick", # 37, D
|
393
|
+
"Acoustic Snare", # 38, D#
|
394
|
+
"Hand Clap", # 39, E
|
395
|
+
"Electric Snare", # 40, F
|
396
|
+
"Low Floor Tom", # 41, F#
|
397
|
+
"Closed Hi Hat", # 42, G
|
398
|
+
"High Floor Tom", # 43, G#
|
399
|
+
"Pedal Hi-Hat", # 44, A
|
400
|
+
"Low Tom", # 45, A#
|
401
|
+
"Open Hi-Hat", # 46, B
|
402
|
+
"Low-Mid Tom", # 47, C
|
403
|
+
"Hi Mid Tom", # 48, C#
|
404
|
+
"Crash Cymbal 1", # 49, D
|
405
|
+
"High Tom", # 50, D#
|
406
|
+
"Ride Cymbal 1", # 51, E
|
407
|
+
"Chinese Cymbal", # 52, F
|
408
|
+
"Ride Bell", # 53, F#
|
409
|
+
"Tambourine", # 54, G
|
410
|
+
"Splash Cymbal", # 55, G#
|
411
|
+
"Cowbell", # 56, A
|
412
|
+
"Crash Cymbal 2", # 57, A#
|
413
|
+
"Vibraslap", # 58, B
|
414
|
+
"Ride Cymbal 2", # 59, C
|
415
|
+
"Hi Bongo", # 60, C#
|
416
|
+
"Low Bongo", # 61, D
|
417
|
+
"Mute Hi Conga", # 62, D#
|
418
|
+
"Open Hi Conga", # 63, E
|
419
|
+
"Low Conga", # 64, F
|
420
|
+
"High Timbale", # 65, F#
|
421
|
+
"Low Timbale", # 66, G
|
422
|
+
"High Agogo", # 67, G#
|
423
|
+
"Low Agogo", # 68, A
|
424
|
+
"Cabasa", # 69, A#
|
425
|
+
"Maracas", # 70, B
|
426
|
+
"Short Whistle", # 71, C
|
427
|
+
"Long Whistle", # 72, C#
|
428
|
+
"Short Guiro", # 73, D
|
429
|
+
"Long Guiro", # 74, D#
|
430
|
+
"Claves", # 75, E
|
431
|
+
"Hi Wood Block", # 76, F
|
432
|
+
"Low Wood Block", # 77, F#
|
433
|
+
"Mute Cuica", # 78, G
|
434
|
+
"Open Cuica", # 79, G#
|
435
|
+
"Mute Triangle", # 80, A
|
436
|
+
"Open Triangle" # 81, A#
|
437
|
+
]
|
438
|
+
|
439
|
+
end # PM
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'unimidi'
|
2
|
+
|
3
|
+
module PM
|
4
|
+
|
5
|
+
# Implements a DSL for describing a PatchMaster setup.
|
6
|
+
class DSL
|
7
|
+
|
8
|
+
include PM
|
9
|
+
|
10
|
+
def initialize(no_midi=false)
|
11
|
+
@no_midi = no_midi
|
12
|
+
@pm = PatchMaster.instance
|
13
|
+
end
|
14
|
+
|
15
|
+
def load(file)
|
16
|
+
contents = IO.read(file)
|
17
|
+
@filters = []
|
18
|
+
instance_eval(contents)
|
19
|
+
read_filters(contents)
|
20
|
+
end
|
21
|
+
|
22
|
+
def input(port_num, sym, name=nil)
|
23
|
+
@pm.inputs[sym] = InputDevice.new(name || sym.to_s, port_num, @no_midi)
|
24
|
+
rescue => ex
|
25
|
+
raise "error creating input device \"#{name}\" on input port #{port_num}: #{ex}"
|
26
|
+
end
|
27
|
+
alias_method :in, :input
|
28
|
+
|
29
|
+
def output(port_num, sym, name=nil)
|
30
|
+
@pm.outputs[sym] = OutputDevice.new(name || sym.to_s, port_num, @no_midi)
|
31
|
+
rescue => ex
|
32
|
+
raise "error creating output device \"#{name}\" on output port #{port_num}: #{ex}"
|
33
|
+
end
|
34
|
+
alias_method :out, :output
|
35
|
+
|
36
|
+
def song(name)
|
37
|
+
@song = Song.new(name) # ctor saves into @pm.all_songs
|
38
|
+
yield @song if block_given?
|
39
|
+
end
|
40
|
+
|
41
|
+
def patch(name)
|
42
|
+
@patch = Patch.new(name)
|
43
|
+
@song << @patch
|
44
|
+
yield @patch if block_given?
|
45
|
+
end
|
46
|
+
|
47
|
+
def start_bytes(bytes)
|
48
|
+
@patch.start_bytes = bytes
|
49
|
+
end
|
50
|
+
|
51
|
+
def connection(in_sym, in_chan, out_sym, out_chan)
|
52
|
+
input = @pm.inputs[in_sym]
|
53
|
+
raise "can't find input instrument #{in_sym}" unless input
|
54
|
+
output = @pm.outputs[out_sym]
|
55
|
+
raise "can't find outputput instrument #{out_sym}" unless output
|
56
|
+
|
57
|
+
@conn = Connection.new(input, in_chan, output, out_chan)
|
58
|
+
@patch << @conn
|
59
|
+
yield @conn if block_given?
|
60
|
+
end
|
61
|
+
alias_method :conn, :connection
|
62
|
+
alias_method :c, :connection
|
63
|
+
|
64
|
+
def prog_chg(prog)
|
65
|
+
@conn.pc_prog = prog
|
66
|
+
end
|
67
|
+
alias_method :pc, :prog_chg
|
68
|
+
|
69
|
+
# If +start_or_range+ is a Range, use that. Else either or both params may
|
70
|
+
# be nil.
|
71
|
+
def zone(start_or_range=nil, stop=nil)
|
72
|
+
@conn.zone = if start_or_range.kind_of? Range
|
73
|
+
start_or_range
|
74
|
+
elsif start_or_range == nil && stop == nil
|
75
|
+
nil
|
76
|
+
else
|
77
|
+
((start_or_range || 0) .. (stop || 127))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
alias_method :z, :zone
|
81
|
+
|
82
|
+
def transpose(xpose)
|
83
|
+
@conn.xpose = xpose
|
84
|
+
end
|
85
|
+
alias_method :xpose, :transpose
|
86
|
+
alias_method :x, :transpose
|
87
|
+
|
88
|
+
def filter(&block)
|
89
|
+
@conn.filter = Filter.new(block)
|
90
|
+
@filters << @conn.filter
|
91
|
+
end
|
92
|
+
alias_method :f, :filter
|
93
|
+
|
94
|
+
def song_list(name, song_names)
|
95
|
+
sl = SongList.new(name)
|
96
|
+
@pm.song_lists << sl
|
97
|
+
song_names.each do |sn|
|
98
|
+
song = @pm.all_songs.find(sn)
|
99
|
+
raise "song \"#{sn}\" not found (song list \"#{name}\")" unless song
|
100
|
+
sl << song
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# ****************************************************************
|
105
|
+
|
106
|
+
def save(file)
|
107
|
+
File.open(file, 'w') { |f|
|
108
|
+
save_instruments(f)
|
109
|
+
save_songs(f)
|
110
|
+
save_song_lists(f)
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def save_instruments(f)
|
115
|
+
@pm.inputs.each { |sym, instr|
|
116
|
+
f.puts "input #{instr.port_num}, :#{sym}, #{quoted(instr.name)}"
|
117
|
+
}
|
118
|
+
@pm.outputs.each { |sym, instr|
|
119
|
+
f.puts "output #{instr.port_num}, :#{sym}, #{quoted(instr.name)}"
|
120
|
+
}
|
121
|
+
f.puts
|
122
|
+
end
|
123
|
+
|
124
|
+
def save_songs(f)
|
125
|
+
@pm.all_songs.songs.each do |song|
|
126
|
+
f.puts "song #{quoted(song.name)} do"
|
127
|
+
song.patches.each { |patch| save_patch(f, patch) }
|
128
|
+
f.puts "end"
|
129
|
+
f.puts
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def save_patch(f, patch)
|
134
|
+
f.puts " patch #{quoted(patch.name)} do"
|
135
|
+
f.puts " start_bytes #{patch.start_bytes.inspect}" if patch.start_bytes
|
136
|
+
patch.connections.each { |conn| save_connection(f, conn) }
|
137
|
+
f.puts " end"
|
138
|
+
end
|
139
|
+
|
140
|
+
def save_connection(f, conn)
|
141
|
+
in_sym = @pm.inputs.patch(conn.input)
|
142
|
+
in_chan = conn.input_chan ? conn.input_chan + 1 : 'nil'
|
143
|
+
out_sym = @pm.outputs.patch(conn.output)
|
144
|
+
out_chan = conn.output_chan + 1
|
145
|
+
f.puts " conn :#{in_sym}, #{in_chan}, #{out_sym}, #{out_chan} do"
|
146
|
+
f.puts " prog_chg #{conn.pc_prog}" if conn.pc?
|
147
|
+
f.puts " zone #{conn.note_num_to_name(conn.zone.begin)}, #{conn.note_num_to_name(conn.zone.end)}" if conn.zone
|
148
|
+
f.puts " xpose #{conn.xpose}" if conn.xpose
|
149
|
+
f.puts " filter #{conn.filter.text}" if conn.filter
|
150
|
+
f.puts " end"
|
151
|
+
end
|
152
|
+
|
153
|
+
def save_song_lists(f)
|
154
|
+
@pm.song_lists.each do |sl|
|
155
|
+
next if sl == @pm.all_songs
|
156
|
+
f.puts "song_list #{quoted(sl.name)}, ["
|
157
|
+
@pm.all_songs.songs.each do |song|
|
158
|
+
f.puts " #{quoted(song.name)},"
|
159
|
+
end
|
160
|
+
f.puts "]"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def quoted(str)
|
165
|
+
"\"#{str.gsub('"', "\\\"")}\"" # ' <= un-confuse Emacs font-lock
|
166
|
+
end
|
167
|
+
|
168
|
+
# ****************************************************************
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def input_port(port)
|
173
|
+
if @no_midi
|
174
|
+
MockInputPort.new
|
175
|
+
else
|
176
|
+
UniMIDI::Input.all[port].open
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def output_port(port)
|
181
|
+
if @no_midi
|
182
|
+
MockOutputPort.new
|
183
|
+
else
|
184
|
+
UniMIDI::Output.all[port].open
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Extremely simple filter text reader. Relies on indentation to detect end
|
189
|
+
# of filter block.
|
190
|
+
def read_filters(contents)
|
191
|
+
i = 0
|
192
|
+
in_filter = false
|
193
|
+
filter_indentation = nil
|
194
|
+
filter_end_token = nil
|
195
|
+
contents.each_line do |line|
|
196
|
+
if line =~ /^(\s*)filter\s*(.*)/
|
197
|
+
filter_indentation, text = $1, $2
|
198
|
+
@filters[i].text = text + "\n"
|
199
|
+
in_filter = true
|
200
|
+
filter_end_token = case text
|
201
|
+
when /^{/
|
202
|
+
"}"
|
203
|
+
when /^do\b/
|
204
|
+
"end"
|
205
|
+
when /^lambda\s*({|do)/
|
206
|
+
$1 == "{" ? "}" : "end"
|
207
|
+
else
|
208
|
+
"}|end" # regex
|
209
|
+
end
|
210
|
+
elsif in_filter
|
211
|
+
line =~ /^(\s*)(.*)/
|
212
|
+
indentation, text = $1, $2
|
213
|
+
if indentation.length <= filter_indentation.length
|
214
|
+
if text =~ /^#{filter_end_token}/
|
215
|
+
@filters[i].text << line
|
216
|
+
end
|
217
|
+
i += 1
|
218
|
+
in_filter = false
|
219
|
+
else
|
220
|
+
@filters[i].text << line
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
end
|