patchmaster 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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