patchmaster 0.0.0

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.
@@ -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