smml 0.0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/Rakefile +2 -0
- data/lib/smml/midi-percussion-map.txt +47 -0
- data/lib/smml/midi-programChange-list.txt +144 -0
- data/lib/smml/msm.rb +2270 -0
- data/lib/smml/version.rb +3 -0
- data/lib/smml.rb +6 -0
- data/smml.gemspec +23 -0
- metadata +81 -0
data/lib/smml/msm.rb
ADDED
@@ -0,0 +1,2270 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# -*- encoding: utf-8 -*-
|
3
|
+
require 'kconv'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
def hint
|
7
|
+
cmd=File.basename($0)
|
8
|
+
puts <<EOF
|
9
|
+
usage: #{cmd} -d \"dddd dr3 dddd r4 drdrdrdr dddd dr3\" -o outfile.mid -t bpm
|
10
|
+
#{cmd} -i infile.txt -o outfile.mid -t bpm
|
11
|
+
|
12
|
+
syntax: ...( will be changed time after time)
|
13
|
+
abcdefg =tone; capital letters are sharps. followed by number as length.
|
14
|
+
+- =octave change
|
15
|
+
r =rest
|
16
|
+
>< =tempo up-down(percent)
|
17
|
+
a4 =4 beats of note 'a'. in length words, integers or flout numbers can be used.
|
18
|
+
a =one beat of note 'a'. default length equals 1 now.
|
19
|
+
A*120 =120 ticks of note 'a #'
|
20
|
+
(v:60) =velocity set to 60 (0-127)
|
21
|
+
&(00 00) =set hex data directly. This can include ...
|
22
|
+
'$delta(240)' for deltaTime data making
|
23
|
+
'$se(F0 41 ..)' system exclusive to system exclusive message
|
24
|
+
(p:11) =ProgramChange channel here, instrument 11
|
25
|
+
(p:organ) =ProgramChange channel here, instrument ?(search word like 'organ' from list if exist)
|
26
|
+
map text must start with instrument number
|
27
|
+
channel number can be used, but not recommended. '(p:0,11)'
|
28
|
+
(key:-4) =transpose -4 except percussionSoundName like '_snare!'
|
29
|
+
[...] =repeat 2 times for first time
|
30
|
+
[...]3 =3 times of inside block []
|
31
|
+
/2:abcd/ =(triplet etc.) notes 'abcd' in 2 beats measure
|
32
|
+
/:abc/ =triplet 'a''b''c' in one beat.
|
33
|
+
/*120:abcd/ = notes 'abcd' in 120 ticks measure. now, default measure is 480 ticks per one beat.
|
34
|
+
/:cd/ ~2e /:~fga/ =(tie) each length : c 0.5 d 0.5+2 e 1+0.25 f 0.25 g 0.25 a 0.25
|
35
|
+
after '~' length needed. if not length 1 is automaticaly inserted.
|
36
|
+
'c~~~' = 'c4'
|
37
|
+
= = same note and length as the previous note. 'c2c2c2c2' = 'c2==='
|
38
|
+
(tempo:120) =tempo set
|
39
|
+
(ch:1) =set this track's channel 1
|
40
|
+
(cc:10,64) =controlChange number10 value 64. see SMF format.
|
41
|
+
(pan:>64) =panpot right 64. ( pan:>0 set center )
|
42
|
+
(bend:100) =pitch bend 100
|
43
|
+
(bendRange:12) =set bend range 12. default is normaly 2.
|
44
|
+
(bendCent:on) =set bend value unit cent (half tone = 100). default is 'off' and value is between -8192 and +8192.
|
45
|
+
'(bendCent:off)(bend:8192)' = '(bendCent:on)(bend:100)'
|
46
|
+
(on:a) =note 'a' sound on only. take no ticks.; the event 'a' is the same as '(on:a)(wait:1)(off:a)'.
|
47
|
+
(wait:1) =set waiting time 1 for next event
|
48
|
+
(off:a) =note 'a' sound off
|
49
|
+
(g:10) =set sound gate-rate 10% (staccato etc.)
|
50
|
+
{64} =tone '64' by absolute tone number. ='(x:64)'
|
51
|
+
{c,e,g} =multi tone. use similar way to tone 'a' etc. = '(on:c)(on:e)(on:g)(wait:1)(off:c)(off:e)(off:g)'
|
52
|
+
:cmaj7, =use chord name. the first letter is tone name 'c'. so using capital one is with sharp.
|
53
|
+
(stroke:4) =chord stroke interval ticks '4'. if '-4' down-up reversed.
|
54
|
+
(V:o,o,110) =preceding modifier velocities. if next notes are 'abc' ,third tone 'c' is with velocity 110. a blank or 'o' mean default value.
|
55
|
+
(G:,,-) =preceding modifier gate rates. if next notes are 'abc' ,third tone 'c' is with gate rate shorter.
|
56
|
+
new preceding modifiers cancel old rest preceding values.
|
57
|
+
^ =accent
|
58
|
+
` =too fast note, play ahead
|
59
|
+
' =too late note, lay back
|
60
|
+
(-)c =c flat
|
61
|
+
(+)c =c sharp; equals 'C'
|
62
|
+
(+2)c,(++)c =c double sharp
|
63
|
+
(0)c =c natural
|
64
|
+
(gm:on)
|
65
|
+
(gs:reset)
|
66
|
+
(xg:on)
|
67
|
+
(syswait:) =when using '(gm:on)' etc., this command is needed for all other tracks to adjust wait-time.
|
68
|
+
||| = track separater
|
69
|
+
/// = page separater
|
70
|
+
(mark:posname) =position name for adjustment of tracks after long rest etc.
|
71
|
+
.DC .DS .toCODA .CODA .FINE =coda mark etc.
|
72
|
+
.SKIP =skip mark on over second time
|
73
|
+
.$ =DS point
|
74
|
+
_snare! =percussion sound ( search word like 'snare' (can use tone number) from percussion list if exist )
|
75
|
+
similarly, _s!=snare, k:bassKick, o:openHighHat, c:closedHighHat, cc:CrachCymbal, h:highTom, l:lowTom as default.
|
76
|
+
map text personaly you set must start with tone number.
|
77
|
+
(loadf:filename.mid,2) =load filename.mid, track 2. Track must be this only and seperated by '|||'.
|
78
|
+
W:=abc =macro definition. One Charactor macro can be used. use prefix '$' for refering.
|
79
|
+
macro W:=abc =macro definition.
|
80
|
+
fn(x):=ab$x =macro with args. in this case, '$fn(10)' is substituded by 'ab10'. similarly,
|
81
|
+
'$fn(:10,20,30)' = 'ab10ab20ab30'.
|
82
|
+
'$fn(4:10,20,30)' = 'ab10(wait:4)ab20(wait:4)ab30'.
|
83
|
+
compile order is : page,track seperate => macro set and replace => repeat check => sound data make
|
84
|
+
; =seperater. same to a new line
|
85
|
+
blank =ignored
|
86
|
+
;; comment =ignored after ';;' of each line
|
87
|
+
;;;;;; =start mark of multi-line comment. end mark is same or longer mark of ';;'. these must start from the top of line.
|
88
|
+
|
89
|
+
basicaly, one sound is a tone command followed by length number. now, tone type commands are :
|
90
|
+
'c', '{64}', '_snare!', '{d,g,-b}', ':cmaj7,'
|
91
|
+
and other commands are with parentheses.
|
92
|
+
EOF
|
93
|
+
end
|
94
|
+
|
95
|
+
1.round(2) rescue (
|
96
|
+
class Float
|
97
|
+
def round n=0
|
98
|
+
c=10**(n+1)
|
99
|
+
f=(((self*c).to_i+5)/10).to_f/(10**n)
|
100
|
+
n>0 ? f : f.to_i
|
101
|
+
end
|
102
|
+
end
|
103
|
+
class Fixnum
|
104
|
+
def round n=0
|
105
|
+
return self if n==0
|
106
|
+
c=10**(n+1)
|
107
|
+
(((self*c).to_i+5)/10).to_f/(10**n)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
)
|
111
|
+
def multilineTrim l,com
|
112
|
+
r=[]
|
113
|
+
on=false
|
114
|
+
mark=""
|
115
|
+
l.each{|i|
|
116
|
+
if on
|
117
|
+
on=false if i=~/^#{mark}/
|
118
|
+
else
|
119
|
+
i=~/^(#{com}#{com}+)/
|
120
|
+
if $&
|
121
|
+
mark=$1
|
122
|
+
on=true
|
123
|
+
else
|
124
|
+
r<<i
|
125
|
+
end
|
126
|
+
end
|
127
|
+
}
|
128
|
+
puts " ? mismatch end mark of multiline comment." if on
|
129
|
+
r
|
130
|
+
end
|
131
|
+
class String
|
132
|
+
def setcmark c
|
133
|
+
@@cmark=c
|
134
|
+
end
|
135
|
+
def cmark
|
136
|
+
@@cmark
|
137
|
+
end
|
138
|
+
def trim ofs="",com=@@cmark
|
139
|
+
lines=self.split("\n")
|
140
|
+
d=multilineTrim(lines,com)
|
141
|
+
d=d.map{|i|i.sub(/(#{com}).*/){}.chomp}*ofs
|
142
|
+
d
|
143
|
+
end
|
144
|
+
def sharp2cmark
|
145
|
+
self.gsub!("#"){@@cmark}
|
146
|
+
end
|
147
|
+
def tracks pspl
|
148
|
+
tracks={}
|
149
|
+
pages=self.split(/#{pspl}+/)
|
150
|
+
pages.each{|p|
|
151
|
+
p.split('|||').each_with_index{|t,i|
|
152
|
+
if tracks[i]
|
153
|
+
tracks[i] << t
|
154
|
+
else
|
155
|
+
tracks[i] = [t]
|
156
|
+
end
|
157
|
+
}
|
158
|
+
}
|
159
|
+
tracks.keys.sort.map{|k|tracks[k]*";"}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
class Array
|
165
|
+
def rotatePlus
|
166
|
+
self[1..-1]+[self.first+12]
|
167
|
+
end
|
168
|
+
def rotateMinus
|
169
|
+
[self.last-12]+self[0..-2]
|
170
|
+
end
|
171
|
+
def orotate n=1
|
172
|
+
r=self
|
173
|
+
if n>0
|
174
|
+
n.times{r=r.rotatePlus}
|
175
|
+
else
|
176
|
+
(-n).times{r=r.rotateMinus}
|
177
|
+
end
|
178
|
+
r
|
179
|
+
end
|
180
|
+
end
|
181
|
+
def unirand n,c,reset=false
|
182
|
+
a=[0,n]
|
183
|
+
while c>a.size
|
184
|
+
t=rand(n-2)+1
|
185
|
+
a<<t if ! a.member?(t)
|
186
|
+
end
|
187
|
+
a.sort_by{rand}
|
188
|
+
end
|
189
|
+
|
190
|
+
class Event
|
191
|
+
attr_accessor :type, :time, :value
|
192
|
+
def initialize ty=:e,*arg
|
193
|
+
@type=ty
|
194
|
+
@pos=0
|
195
|
+
@value=""
|
196
|
+
case @type
|
197
|
+
when :c,:raw
|
198
|
+
@time=0
|
199
|
+
@value=arg[0]
|
200
|
+
when :ahead
|
201
|
+
@time=arg[0]
|
202
|
+
when :o,:off
|
203
|
+
when :mark
|
204
|
+
@mark,@track,@value=arg
|
205
|
+
else
|
206
|
+
@time=arg[0]
|
207
|
+
@value=arg[1]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
def data
|
211
|
+
case @type
|
212
|
+
when :raw
|
213
|
+
rawdata(@value)
|
214
|
+
when :mark
|
215
|
+
"# marktrack(#{@track}_#{@mark}) [#{@value}]\n"
|
216
|
+
when :c,:o,:off
|
217
|
+
@value
|
218
|
+
else
|
219
|
+
varlenHex(@time)+@value
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
# arg=[steps],[values]
|
224
|
+
def mymerge span,*arg
|
225
|
+
r=[]
|
226
|
+
arg.each{|ar|
|
227
|
+
next if ! ar
|
228
|
+
m,steps,vs=ar
|
229
|
+
steps=[steps] if steps.class!=Array
|
230
|
+
steps=steps*(vs.size-1) if steps.size==1
|
231
|
+
r<<[0,m,vs[0]]
|
232
|
+
if vs.size>1
|
233
|
+
stepsum=steps.inject{|s,i|s+i}
|
234
|
+
vsize=vs.size-1
|
235
|
+
if stepsum>span
|
236
|
+
rate=span*1.0/stepsum
|
237
|
+
steps=steps.map{|i|(i*rate).to_i}
|
238
|
+
end
|
239
|
+
n=1
|
240
|
+
r+=vs[1..-1].map{|i|
|
241
|
+
t=steps.shift*n
|
242
|
+
n+=1
|
243
|
+
[t,m,i]
|
244
|
+
}
|
245
|
+
end
|
246
|
+
}
|
247
|
+
n=0
|
248
|
+
all=r.sort_by{|t,m,e|t}
|
249
|
+
rest=span-all[-1][0]
|
250
|
+
all.map{|t,m,e|
|
251
|
+
tt=t-n
|
252
|
+
n=t
|
253
|
+
[tt,m,e]
|
254
|
+
}+[[rest,:rest]]
|
255
|
+
end
|
256
|
+
def rawdata d
|
257
|
+
d.gsub(","){" "}
|
258
|
+
end
|
259
|
+
def trackSizeHex d,cmark="#"
|
260
|
+
d=d.trim("",cmark).split.join
|
261
|
+
i=(d.size+8)/2
|
262
|
+
# p [d,i,i.to_s(16)]
|
263
|
+
#("00000000"+i.to_s(16))[-8..-1]
|
264
|
+
format("%08x",i)+" # size: #{i}"
|
265
|
+
end
|
266
|
+
# 可変長数値表現
|
267
|
+
# 7bitずつに区切り最後以外のbyteは先頭bitを立てる
|
268
|
+
def varlen(v)
|
269
|
+
if v < 0x80
|
270
|
+
return v
|
271
|
+
else
|
272
|
+
v1 = v & 0b01111111
|
273
|
+
v2=(v-v1)>>7
|
274
|
+
v2 =varlen(v2)
|
275
|
+
return [v2,v1]
|
276
|
+
end
|
277
|
+
end
|
278
|
+
def hex2digit d
|
279
|
+
if d.class==String
|
280
|
+
if d=~/^0(x|X)/
|
281
|
+
d=d.to_i(16)
|
282
|
+
else
|
283
|
+
d=d.to_i
|
284
|
+
end
|
285
|
+
end
|
286
|
+
d
|
287
|
+
end
|
288
|
+
def varlenHex(v)
|
289
|
+
v=hex2digit(v)
|
290
|
+
raise if v<0
|
291
|
+
b=[varlen(v.round)]
|
292
|
+
b=b.flatten
|
293
|
+
c=b[0..-2].map{|i| i | 0x80 }
|
294
|
+
r=[c,b[-1]].flatten
|
295
|
+
res=0
|
296
|
+
r.each{|i|
|
297
|
+
res=res*0x100+i
|
298
|
+
}
|
299
|
+
format("%0#{b.size*2}x",res)
|
300
|
+
end
|
301
|
+
def hext2hex d
|
302
|
+
ar=d.split(/[ ,]+/)-[""]
|
303
|
+
hex=ar.map{|i|i.size==2}.uniq==[true]
|
304
|
+
lasth=ar.map{|i|true if i=~/h$/}.uniq==[true]
|
305
|
+
zx=ar.map{|i|true if i=~/^0x/}.uniq==[true]
|
306
|
+
r=ar.map{|i|i[0..1]} if lasth
|
307
|
+
r=ar.map{|i|i[2..-1]} if zx
|
308
|
+
r=ar if hex
|
309
|
+
r
|
310
|
+
end
|
311
|
+
def rolandcheck d
|
312
|
+
return d if $ignoreChecksum
|
313
|
+
if d[1]=="41"
|
314
|
+
org=d[2]
|
315
|
+
s=0
|
316
|
+
[*5..(d.size-3)].each{|i|s+=d[i].to_i(16)}
|
317
|
+
csum=0x80-s%0x80
|
318
|
+
if $DEBUG && csum!=org.to_i(16)
|
319
|
+
"# sysEx: roland check sum bad?"
|
320
|
+
end
|
321
|
+
d[-2]=format("%02X",csum)
|
322
|
+
end
|
323
|
+
d
|
324
|
+
end
|
325
|
+
def sysEx2mes d
|
326
|
+
r=hext2hex(d)
|
327
|
+
r=rolandcheck(r)
|
328
|
+
"#{r[0]} #{varlenHex(r.size-1)} #{r[1..-1]*" "}"
|
329
|
+
end
|
330
|
+
def txt2hex t
|
331
|
+
r=[]
|
332
|
+
t.each_byte{|i|
|
333
|
+
r<<format("%02x",i)
|
334
|
+
}
|
335
|
+
size=r.size
|
336
|
+
[r*" ",varlenHex(size)]
|
337
|
+
end
|
338
|
+
def bendHex d
|
339
|
+
c=d.to_i+8192
|
340
|
+
c=[[c,0].max,16383].min
|
341
|
+
a=c>>7
|
342
|
+
b=c & 0b01111111
|
343
|
+
r=[a,b,b*0x100+a]
|
344
|
+
format("%04x ",r[2])
|
345
|
+
end
|
346
|
+
|
347
|
+
module MidiRead
|
348
|
+
def self.msplit s,dat
|
349
|
+
li=s.split('')
|
350
|
+
dat=dat.split('')
|
351
|
+
block={}
|
352
|
+
num=0
|
353
|
+
li.size.times{|i|
|
354
|
+
num+=1 if li[i,4]==dat
|
355
|
+
block[num] ? block[num]+=li[i] : block[num]=li[i]
|
356
|
+
}
|
357
|
+
block.keys.sort.map{|i|block[i]}
|
358
|
+
end
|
359
|
+
def self.head d
|
360
|
+
return @head if @head
|
361
|
+
r=self.msplit(d,'MTrk')
|
362
|
+
@head,@tracks=r[0],r[1..-1]
|
363
|
+
@head
|
364
|
+
end
|
365
|
+
def self.tracks d
|
366
|
+
return @tracks if @tracks
|
367
|
+
r=self.msplit(d,'MTrk')
|
368
|
+
@head,@tracks=r[0],r[1..-1]
|
369
|
+
@tracks
|
370
|
+
end
|
371
|
+
def self.read file,tracknum=false
|
372
|
+
@head=false
|
373
|
+
@tracks=false
|
374
|
+
d=""
|
375
|
+
if File.exist?(file)
|
376
|
+
open(file,"rb"){|f|
|
377
|
+
d=f.read
|
378
|
+
}
|
379
|
+
@head=self.head(d)
|
380
|
+
@tracks=self.tracks(d)
|
381
|
+
else
|
382
|
+
STDERR.puts" can't read file #{file}"
|
383
|
+
end
|
384
|
+
if tracknum
|
385
|
+
tracknum<tracks.size ? @tracks[tracknum-1] : @tracks[0]
|
386
|
+
else
|
387
|
+
[@head,@tracks]
|
388
|
+
end
|
389
|
+
end
|
390
|
+
def self.readtrack file,num=false
|
391
|
+
self.read file
|
392
|
+
if num
|
393
|
+
@tracks[num]
|
394
|
+
else
|
395
|
+
@tracks
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
def apply d,macro
|
400
|
+
d.scan(/\$\{[^ ;\$_*^,\)\(`'\/+-]+\}|\$[^ ;\$_*^,\)\(`'\/+-]+|./).map{|i|
|
401
|
+
case i
|
402
|
+
when /^\$\{([^ ;\$_*^,\)\(`'\/+-]+)\}|^\$([^ ;\$_*^,\)\(`'\/+-]+)/
|
403
|
+
key=$1||$2
|
404
|
+
if macro.keys.member?(key)
|
405
|
+
macro[key]
|
406
|
+
else
|
407
|
+
i
|
408
|
+
end
|
409
|
+
else
|
410
|
+
i
|
411
|
+
end
|
412
|
+
}*""
|
413
|
+
end
|
414
|
+
def rawHexPart d,macro={}
|
415
|
+
li=d.scan(/\$se\([^)]*\)|\$delta\([^)]*\)|\$bend\([^)]*\)|\(bend:[^)]*\)|\(expre:[^)]*\)|./)
|
416
|
+
res=[]
|
417
|
+
li.map{|i|
|
418
|
+
case i
|
419
|
+
when /\$se\(([^)]*)\)/
|
420
|
+
d=apply($1,macro)
|
421
|
+
sysEx2mes(d)
|
422
|
+
when /\$delta\(([^)]*)\)/
|
423
|
+
varlenHex($1)
|
424
|
+
when /\$bend\(([^)]*)\)/
|
425
|
+
d=apply($1,macro)
|
426
|
+
bendHex(d)
|
427
|
+
when /\(bend:([^)]*)\)/
|
428
|
+
"_b__#{$1.split(',')*"_"}?"
|
429
|
+
when /\(expre:([^)]*)\)/
|
430
|
+
"_e__#{$1.split(',')*"_"}?"
|
431
|
+
else
|
432
|
+
i
|
433
|
+
end
|
434
|
+
}*""
|
435
|
+
end
|
436
|
+
def revertPre d
|
437
|
+
d.gsub(/_b__([^?]*)\?/){"(bend:#{$1.split("_")*","})"}.
|
438
|
+
gsub(/_e__([^?]*)\?/){"(expre:#{$1.split("_")*","})"}
|
439
|
+
end
|
440
|
+
def worddata word,d
|
441
|
+
d=~/\(#{word}:(([[:digit:].]+),)?([-,.[:digit:]]+)\)/
|
442
|
+
if $&
|
443
|
+
pos=$1 ? $2.to_i : 0
|
444
|
+
depth=$3.split(',').map{|i|i.to_f}
|
445
|
+
[:"#{word}",pos,depth]
|
446
|
+
else
|
447
|
+
false
|
448
|
+
end
|
449
|
+
end
|
450
|
+
class OrderedSet
|
451
|
+
def initialize
|
452
|
+
@cyclecheck=false
|
453
|
+
end
|
454
|
+
def cyclecheck on=true
|
455
|
+
@cyclecheck=on
|
456
|
+
self
|
457
|
+
end
|
458
|
+
def orderby smaller,a,b
|
459
|
+
if smaller[a].member?(b)
|
460
|
+
return -1
|
461
|
+
elsif smaller[b].member?(a)
|
462
|
+
return 1
|
463
|
+
end
|
464
|
+
0
|
465
|
+
end
|
466
|
+
def ccheck s,depth=2
|
467
|
+
return false if depth<1 or not @cyclecheck
|
468
|
+
keys=s.keys
|
469
|
+
keys.each{|i|
|
470
|
+
tmp=s[i]
|
471
|
+
depth.times{
|
472
|
+
tmp=tmp.map{|k|[k,s[k]]}.flatten
|
473
|
+
}
|
474
|
+
if tmp.member?(i)
|
475
|
+
p "bad cycle #{i}, depth:#{depth}"
|
476
|
+
return true
|
477
|
+
end
|
478
|
+
}
|
479
|
+
false
|
480
|
+
end
|
481
|
+
def smerge o, depth=2
|
482
|
+
all=o.flatten.uniq
|
483
|
+
smaller={}
|
484
|
+
all.each{|i|
|
485
|
+
o.each{|k|
|
486
|
+
if k.member?(i)
|
487
|
+
smaller[i]=smaller[i] ? smaller[i]+k.select{|n|k.index(i)<k.index(n)} : k.select{|n|k.index(i)<k.index(n)}
|
488
|
+
end
|
489
|
+
}
|
490
|
+
}
|
491
|
+
raise if ccheck(smaller,depth)
|
492
|
+
smaller
|
493
|
+
end
|
494
|
+
def calc smaller,ar
|
495
|
+
s=ar.size
|
496
|
+
ar.each{|i|
|
497
|
+
ind=ar.index(i)
|
498
|
+
(s-ind-1).times{|n|
|
499
|
+
j=ar[ind+n+1]
|
500
|
+
r=orderby(smaller,i,j)
|
501
|
+
puts "#{r} #{i} #{j}" if $DEBUG && $debuglevel>1
|
502
|
+
return false if r>0
|
503
|
+
}
|
504
|
+
}
|
505
|
+
true
|
506
|
+
end
|
507
|
+
def common a,b
|
508
|
+
(a+b).uniq-(a-b)-(b-a)
|
509
|
+
end
|
510
|
+
def omerge base,o
|
511
|
+
return base if o==[[]]
|
512
|
+
return o[0] if base.size==0 && o.size==1
|
513
|
+
rest=o-[base]
|
514
|
+
r=base.dup
|
515
|
+
rest.each{|i|
|
516
|
+
n=i.dup
|
517
|
+
cmn=common(r,n)
|
518
|
+
if cmn.size>0
|
519
|
+
p1,p2=cmn[0],cmn[-1]
|
520
|
+
r1=r.select{|j|r.index(j)<r.index(p1)}
|
521
|
+
r3=r.select{|j|r.index(j)>r.index(p2)}
|
522
|
+
r2=r-r1-r3-[p1,p2]
|
523
|
+
n1=n.select{|j|n.index(j)<n.index(p1)}
|
524
|
+
n3=n.select{|j|n.index(j)>n.index(p2)}
|
525
|
+
n2=n-n1-n3-[p1,p2]
|
526
|
+
if p1==p2
|
527
|
+
mid=[p1]
|
528
|
+
else
|
529
|
+
mid=[p1]+omerge(r2,[n2])+[p2]
|
530
|
+
end
|
531
|
+
r=omerge(r1,[n1])+mid+omerge(r3,[n3])
|
532
|
+
else
|
533
|
+
ins0=r.size
|
534
|
+
n.size.times{
|
535
|
+
pop=n.pop
|
536
|
+
size=r.size
|
537
|
+
ins=[rand(size+1),ins0].min
|
538
|
+
r.insert(ins,pop)
|
539
|
+
ins0=ins
|
540
|
+
}
|
541
|
+
end
|
542
|
+
}
|
543
|
+
r
|
544
|
+
end
|
545
|
+
def sort o, depth=2
|
546
|
+
return [] if o==[]
|
547
|
+
stime=Time.now
|
548
|
+
o=o.sort_by{|i|i.size}
|
549
|
+
base=o[-1]
|
550
|
+
begin
|
551
|
+
s=smerge(o,depth)
|
552
|
+
rescue
|
553
|
+
return []
|
554
|
+
end
|
555
|
+
f=o.flatten.uniq
|
556
|
+
rest=f-base
|
557
|
+
c=0
|
558
|
+
cc=0
|
559
|
+
print "sort" if $DEBUG
|
560
|
+
while 1
|
561
|
+
c+=1
|
562
|
+
print "," if $DEBUG && c%20==0
|
563
|
+
r=rest.sort_by{|i|rand(rest.size*2+c)-s[i].size}
|
564
|
+
begin
|
565
|
+
cc+=1
|
566
|
+
f=omerge(base,o)
|
567
|
+
end until f
|
568
|
+
break if calc(s,f)
|
569
|
+
p f if $DEBUG && $debuglevel>1
|
570
|
+
end
|
571
|
+
t=Time.now-stime
|
572
|
+
puts " try: #{c} (check #{cc}) #{t}sec." if $DEBUG || t>10
|
573
|
+
f
|
574
|
+
end
|
575
|
+
end
|
576
|
+
class MarkTrack
|
577
|
+
def initialize
|
578
|
+
@mt={}
|
579
|
+
@maxtrack=0
|
580
|
+
@marks=[]
|
581
|
+
@markstracks={}
|
582
|
+
@added={}
|
583
|
+
@diff={}
|
584
|
+
end
|
585
|
+
def makekey t,m
|
586
|
+
"#{t}_#{m}"
|
587
|
+
end
|
588
|
+
def set m,t,pos
|
589
|
+
@maxtrack=t if t>@maxtrack
|
590
|
+
@marks<<m if not @marks.member?(m)
|
591
|
+
@markstracks[t]=[] if not @markstracks[t]
|
592
|
+
@markstracks[t]<<m if not @markstracks[t].member?(m)
|
593
|
+
@mt[makekey(t,m)]=pos
|
594
|
+
end
|
595
|
+
def getcount m,t
|
596
|
+
key=makekey(t,m)
|
597
|
+
@mt.keys.select{|k|k==key||k=~/^#{key}@/}.size
|
598
|
+
end
|
599
|
+
def get m,t
|
600
|
+
@mt[makekey(t,m)]
|
601
|
+
end
|
602
|
+
def getmax m
|
603
|
+
[*1..@maxtrack].map{|t|
|
604
|
+
key=makekey(t,m)
|
605
|
+
(@mt[key] ? @mt[key] : 0)+(@added[t] ? @added[t] : 0)
|
606
|
+
}.max
|
607
|
+
end
|
608
|
+
def sortmark
|
609
|
+
marks=@marks
|
610
|
+
s=[]
|
611
|
+
s=@markstracks.keys.map{|k|@markstracks[k]}
|
612
|
+
OrderedSet.new.cyclecheck.sort(s)
|
613
|
+
end
|
614
|
+
def calc
|
615
|
+
marks=sortmark
|
616
|
+
p marks if $DEBUG
|
617
|
+
@diff={}
|
618
|
+
@added={}
|
619
|
+
marks.each{|k|
|
620
|
+
max=getmax(k)
|
621
|
+
@maxtrack.times{|i|
|
622
|
+
t=i+1
|
623
|
+
key=makekey(t,k)
|
624
|
+
if @mt[key]
|
625
|
+
@diff[key]=max-@mt[key]-(@added[t]||0)
|
626
|
+
@added[t]=@added[t] ? @added[t]+@diff[key] : @diff[key]
|
627
|
+
end
|
628
|
+
}
|
629
|
+
}
|
630
|
+
puts ["mt:",@mt],["diff",@diff],["added",@added] if $DEBUG
|
631
|
+
@diff
|
632
|
+
end
|
633
|
+
end
|
634
|
+
module MidiHex
|
635
|
+
# 設定のため最初に呼ばなければならない
|
636
|
+
def self.prepare bpm,tbase=480,vel=0x40,oct=:near,vfuzzy=2
|
637
|
+
@startBpm=bpm
|
638
|
+
@midiname=false
|
639
|
+
@cmark="#"
|
640
|
+
@marktrack=MarkTrack.new
|
641
|
+
@octmode=oct
|
642
|
+
@tbase=tbase
|
643
|
+
@gateRate=100
|
644
|
+
@nowtime=0
|
645
|
+
@onlist=[]
|
646
|
+
@waitingtime=0
|
647
|
+
@rythmChannel=9
|
648
|
+
@notes={
|
649
|
+
"c"=>0,
|
650
|
+
"C"=>1,
|
651
|
+
"d"=>2,
|
652
|
+
"D"=>3,
|
653
|
+
"e"=>4,
|
654
|
+
"f"=>5,
|
655
|
+
"F"=>6,
|
656
|
+
"g"=>7,
|
657
|
+
"G"=>8,
|
658
|
+
"a"=>9,
|
659
|
+
"A"=>10,
|
660
|
+
"b"=>11,
|
661
|
+
"t"=>[0,@rythmChannel],
|
662
|
+
"s"=>[3,@rythmChannel],
|
663
|
+
"u"=>[6,@rythmChannel]
|
664
|
+
}
|
665
|
+
@ch=0
|
666
|
+
@velocity=vel
|
667
|
+
@velocityOrg=vel
|
668
|
+
@velocityFuzzy=vfuzzy
|
669
|
+
@accentPlus=10
|
670
|
+
@basekey=0x3C
|
671
|
+
@chordCenter=@chordCenterOrg=@basekey
|
672
|
+
@basekeyRythm=@basekeyOrg=@basekey
|
673
|
+
@bendrange=2
|
674
|
+
@bendCent=1
|
675
|
+
@prepareSet=[@tbase,@ch,@velocity,@velocityFuzzy,@basekey,@gateRate,@bendrange,@bendCent]
|
676
|
+
@chmax=15
|
677
|
+
@bendrangemax=127
|
678
|
+
file="midi-programChange-list.txt"
|
679
|
+
pfile="midi-percussion-map.txt"
|
680
|
+
base=File.dirname(__FILE__)
|
681
|
+
file=File.expand_path(file,base) if not File.exist?(file)
|
682
|
+
pfile=File.expand_path(pfile,base) if not File.exist?(pfile)
|
683
|
+
self.loadProgramChange(file)
|
684
|
+
self.loadPercussionMap(pfile)
|
685
|
+
end
|
686
|
+
def self.setmidiname name
|
687
|
+
@midiname=name
|
688
|
+
end
|
689
|
+
def self.getmidiname
|
690
|
+
@midiname
|
691
|
+
end
|
692
|
+
def self.setfile name
|
693
|
+
@title=false
|
694
|
+
cmark="".cmark
|
695
|
+
if name && File.exist?(name)
|
696
|
+
list=File.readlines(name).select{|i|i=~/^#{cmark}/}
|
697
|
+
t=list.map{|i|i=~/^#{cmark} *title */;$'}-[nil]
|
698
|
+
@title=t[0].chomp if t.size>0
|
699
|
+
m=list.map{|i|i=~/^#{cmark} *midifilename */;$'}-[nil]
|
700
|
+
if m.size>0
|
701
|
+
@midiname=m[0].chomp
|
702
|
+
@midiname+=".mid" if @midiname !~ /\.mid$/
|
703
|
+
end
|
704
|
+
end
|
705
|
+
@data=File.read(name).trim(" ;").toutf8 if name && File.exist?(name)
|
706
|
+
end
|
707
|
+
def self.getdata
|
708
|
+
@data
|
709
|
+
end
|
710
|
+
def self.setdata d
|
711
|
+
@data=d
|
712
|
+
end
|
713
|
+
def self.accent a
|
714
|
+
@accentPlus=a.to_i
|
715
|
+
end
|
716
|
+
def self.setGateRate g
|
717
|
+
@gateRate=[g,100].min
|
718
|
+
end
|
719
|
+
def self.bendRange v
|
720
|
+
case v
|
721
|
+
when /^\+/
|
722
|
+
@bendrange+=$'.to_i
|
723
|
+
when /^\-/
|
724
|
+
@bendrange-=$'.to_i
|
725
|
+
else
|
726
|
+
@bendrange=v.to_i
|
727
|
+
end
|
728
|
+
@bendrange=[[@bendrange,@bendrangemax].min,0].max
|
729
|
+
r=[]
|
730
|
+
r<<self.controlChange("101,0")
|
731
|
+
r<<self.controlChange("100,0")
|
732
|
+
r<<self.controlChange("6,#{@bendrange}")
|
733
|
+
r
|
734
|
+
end
|
735
|
+
def self.bendCent on
|
736
|
+
@bendCent=1
|
737
|
+
@bendCent=8192/@bendrange/100.0 if on
|
738
|
+
end
|
739
|
+
def self.trackPrepare tc=0
|
740
|
+
@tbase,@ch,@velocity,@velocityFuzzy,@basekey,@gateRate,@bendrange,@bendCent=@prepareSet
|
741
|
+
@strokespeed=0
|
742
|
+
@preGate=[]
|
743
|
+
@preVelocity=[]
|
744
|
+
@preNote=[]
|
745
|
+
@preLength=[]
|
746
|
+
@preBefore=[]
|
747
|
+
@preAfter=[]
|
748
|
+
@tracknum=tc+1
|
749
|
+
tc+=1 if tc>=9 # ch10 is drum kit channel
|
750
|
+
tc=@chmax if tc>@chmax
|
751
|
+
@ch=tc
|
752
|
+
end
|
753
|
+
def self.header format,track,tbase=@tbase
|
754
|
+
format=[format,0xff].min
|
755
|
+
track=[track,0xff].min
|
756
|
+
tbase=[tbase,0x7fff].min
|
757
|
+
@tbase=tbase
|
758
|
+
format=format("%02x",format)
|
759
|
+
track=format("%02x",track)
|
760
|
+
tbase=format("%04x",tbase)
|
761
|
+
"
|
762
|
+
# Standard MIDI File data start
|
763
|
+
# header
|
764
|
+
4D 54 68 64 # ヘッダ
|
765
|
+
00 00 00 06 # データ長:6[byte]
|
766
|
+
00 #{format} # フォーマット
|
767
|
+
00 #{track} # トラック数
|
768
|
+
#{tbase} # 1 拍の分解能 #{@tbase}
|
769
|
+
"
|
770
|
+
end
|
771
|
+
def self.byGate len,g=@gateRate
|
772
|
+
g=@preGate.shift if @preGate.size>0
|
773
|
+
l=(len*1.0*g/100).to_i
|
774
|
+
r=len-l
|
775
|
+
[l,r]
|
776
|
+
end
|
777
|
+
def self.soundOn key=@basekey,velocity=@velocity,ch=@ch,sharp=0
|
778
|
+
key=self.note2key(key) if key.class==String
|
779
|
+
key,ch=key if key.class==Array
|
780
|
+
ch=[ch,0x0f].min
|
781
|
+
velocity-=rand(@velocityFuzzy) if @velocityFuzzy>0
|
782
|
+
velocity=[velocity,0x7f].min
|
783
|
+
key+=sharp
|
784
|
+
@key=[[key,0x7f].min,0].max
|
785
|
+
@onlist<<@key
|
786
|
+
key=format("%02x",@key)
|
787
|
+
ch=format("%01x",ch)
|
788
|
+
vel=format("%02x",velocity)
|
789
|
+
start=@waitingtime
|
790
|
+
@waitingtime=0
|
791
|
+
@nowtime+=start
|
792
|
+
r=[Event.new(:o)]
|
793
|
+
r<<Event.new(:e,start," 9#{ch} #{key} #{vel} # #{start}後, sound on only , note #{@key} velocity #{velocity}\n")
|
794
|
+
r
|
795
|
+
end
|
796
|
+
def self.soundOff key=@basekey,ch=@ch,sharp=0
|
797
|
+
key=self.note2key(key) if key.class==String
|
798
|
+
key,ch=key if key.class==Array
|
799
|
+
ch=[ch,0x0f].min
|
800
|
+
key+=sharp
|
801
|
+
@key=[[key,0x7f].min,0].max
|
802
|
+
@onlist-=[@key]
|
803
|
+
key=format("%02x",@key)
|
804
|
+
ch=format("%01x",ch)
|
805
|
+
start=@waitingtime
|
806
|
+
@waitingtime=0
|
807
|
+
@nowtime+=start
|
808
|
+
r=[Event.new(:off)]
|
809
|
+
r<<Event.new(:end,start," 8#{ch} #{key} 00 # #{start} sound off only [#{(@nowtime/@tbase).to_i}, #{@nowtime%@tbase}]\n")
|
810
|
+
r
|
811
|
+
end
|
812
|
+
def self.oneNote len=@tbase,key=@basekey,velocity=@velocity,ch=@ch,sharp=0
|
813
|
+
velocity=@preVelocity.shift if @preVelocity.size>0
|
814
|
+
gate=@gateRate
|
815
|
+
ch=[ch,0x0f].min
|
816
|
+
velocity-=rand(@velocityFuzzy) if @velocityFuzzy>0
|
817
|
+
velocity=[velocity,0x7f].min
|
818
|
+
key+=sharp
|
819
|
+
@key=[[key,0x7f].min,0].max
|
820
|
+
key=format("%02x",@key)
|
821
|
+
ch=format("%01x",ch)
|
822
|
+
vel=format("%02x",velocity)
|
823
|
+
start=@waitingtime
|
824
|
+
@waitingtime=0
|
825
|
+
slen,rest=self.byGate(len,gate)
|
826
|
+
@nowtime+=start
|
827
|
+
r=[]
|
828
|
+
r<<Event.new(:e,start," 9#{ch} #{key} #{vel} # #{start}後, soundオン note #{@key} velocity #{velocity}\n")
|
829
|
+
b=@preAfter.shift
|
830
|
+
bends=expre=false
|
831
|
+
if b
|
832
|
+
bends=worddata("bend",b)
|
833
|
+
expre=worddata("expre",b)
|
834
|
+
mymerge(slen,bends,expre).each{|t,m,d|
|
835
|
+
case m
|
836
|
+
when :bend
|
837
|
+
r<<self.bend(t,d)
|
838
|
+
when :expre
|
839
|
+
r<<self.expre(t,d)
|
840
|
+
when :rest
|
841
|
+
slen=t
|
842
|
+
end
|
843
|
+
}
|
844
|
+
end
|
845
|
+
@nowtime+=slen
|
846
|
+
r<<Event.new(:e,slen," 8#{ch} #{key} 00 # #{slen}(gate:#{@gateRate})- #{len.to_i}(#{len.round(2)})tick後, soundオフ [#{(@nowtime/@tbase).to_i}, #{@nowtime%@tbase}]\n")
|
847
|
+
r<<self.bend(0,0) if bends
|
848
|
+
r<<self.expre(0,127) if expre
|
849
|
+
if rest>0
|
850
|
+
@nowtime+=rest
|
851
|
+
r<<Event.new(:end,rest," 8#{ch} #{key} 00 # #{rest} len-gate\n")
|
852
|
+
end
|
853
|
+
r
|
854
|
+
end
|
855
|
+
def self.dummyNote key,len,accent=false,sharp=0
|
856
|
+
vel=@velocity
|
857
|
+
vel+=@accentPlus
|
858
|
+
key=@preNote.shift if @preNote.size>0
|
859
|
+
len=@preLength.shift if @preLength.size>0
|
860
|
+
self.oneNote(len,key,vel,sharp)
|
861
|
+
end
|
862
|
+
def self.byKey key,len,accent=false,sharp=0
|
863
|
+
vel=@velocity
|
864
|
+
vel+=@accentPlus
|
865
|
+
self.oneNote(len,key,vel,@ch,sharp)
|
866
|
+
end
|
867
|
+
def self.notekey key,length=false,accent=false,sharp=0
|
868
|
+
len,velocity,ch=[@tbase,@velocity,@ch]
|
869
|
+
velocity+=@accentPlus if accent
|
870
|
+
len=length if length
|
871
|
+
if key.class==Fixnum
|
872
|
+
else
|
873
|
+
key,ch=key
|
874
|
+
end
|
875
|
+
key=key+@basekey
|
876
|
+
self.oneNote(len,key,velocity,ch,sharp)
|
877
|
+
end
|
878
|
+
def self.percussionNote key,len=@tbase,accent=false,sharp=0
|
879
|
+
vel=@velocity
|
880
|
+
vel+=@accentPlus if accent
|
881
|
+
self.oneNote(len,key,vel,@rythmChannel,sharp)
|
882
|
+
end
|
883
|
+
def self.notes c,l=false,accent=false,sharp=0
|
884
|
+
n=@notes[c]
|
885
|
+
if @octmode==:near && n.class != Array
|
886
|
+
if @lastnote
|
887
|
+
n+=12 if @lastnote-n>6
|
888
|
+
n-=12 if @lastnote-n<-6
|
889
|
+
end
|
890
|
+
@lastnote=n
|
891
|
+
(@basekey+=12;@lastnote-=12) if n>=12
|
892
|
+
(@basekey-=12;@lastnote+=12) if n<0
|
893
|
+
n=@lastnote
|
894
|
+
end
|
895
|
+
self.notekey(n,l,accent,sharp)
|
896
|
+
end
|
897
|
+
def self.shiftChord chord, base, limit=6
|
898
|
+
octave=12
|
899
|
+
chord=chord.orotate(-1) while chord[0]>base+limit
|
900
|
+
chord=chord.orotate(1) while chord[0]<base-limit
|
901
|
+
chord
|
902
|
+
end
|
903
|
+
def self.chordName c,l=false,accent=false,sharp=0
|
904
|
+
c=~/(.)([^(]*)(\((.*)\))?/
|
905
|
+
root=$1
|
906
|
+
type=$2
|
907
|
+
subtype=$4
|
908
|
+
same=false
|
909
|
+
same=(@lastchordName==c) if @lastchordName
|
910
|
+
if same
|
911
|
+
chord=@lastchord
|
912
|
+
if not (chord[0]-@firstchordbase).abs<7
|
913
|
+
puts "# same chord auto inversion, too far" if $DEBUG && $debuglevel>1
|
914
|
+
chord=self.shiftChord(chord,@firstchordbase)
|
915
|
+
end
|
916
|
+
else
|
917
|
+
@lastchordName=c
|
918
|
+
base=self.note2key(root)+sharp
|
919
|
+
ten=[0]
|
920
|
+
case type
|
921
|
+
when "power"
|
922
|
+
ten+=[7]
|
923
|
+
when "7"
|
924
|
+
ten+=[4,7,10]
|
925
|
+
when "m7"
|
926
|
+
ten+=[3,7,10]
|
927
|
+
when "maj7"
|
928
|
+
ten+=[4,7,11]
|
929
|
+
when "mmaj7"
|
930
|
+
ten+=[3,7,11]
|
931
|
+
when "maj" # no need
|
932
|
+
ten+=[4,7]
|
933
|
+
when "m"
|
934
|
+
ten+=[3,7]
|
935
|
+
when "6"
|
936
|
+
ten+=[4,7,9]
|
937
|
+
when "m6"
|
938
|
+
ten+=[3,7,9]
|
939
|
+
when "sus4"
|
940
|
+
ten+=[5,7]
|
941
|
+
when "aug" || "+"
|
942
|
+
ten+=[4,8]
|
943
|
+
when "dim"
|
944
|
+
ten+=[3,6]
|
945
|
+
when "dim7"
|
946
|
+
ten+=[3,6,9]
|
947
|
+
when ""
|
948
|
+
ten+=[4,7]
|
949
|
+
else
|
950
|
+
STDERR.puts "unknown chord type? #{type}"
|
951
|
+
end
|
952
|
+
if subtype
|
953
|
+
tention=subtype.split(',')
|
954
|
+
tention.each{|i|
|
955
|
+
case i
|
956
|
+
when "+5"
|
957
|
+
ten=ten-[7]+[8]
|
958
|
+
when "-5"
|
959
|
+
ten=ten-[7]+[6]
|
960
|
+
when "9"||"add9"
|
961
|
+
ten=ten+[14]
|
962
|
+
when "+9"
|
963
|
+
ten=ten+[15]
|
964
|
+
when "-9"
|
965
|
+
ten=ten+[13]
|
966
|
+
when "+11"
|
967
|
+
ten=ten+[6]
|
968
|
+
when "13"
|
969
|
+
ten=ten+[9]
|
970
|
+
when "-13"
|
971
|
+
ten=ten+[8]
|
972
|
+
end
|
973
|
+
}
|
974
|
+
end
|
975
|
+
ten=ten.sort
|
976
|
+
p "#{root} #{ten*','}" if $DEBUG
|
977
|
+
chord=ten.sort.map{|i|base+i}
|
978
|
+
chord=self.invert(@lastchord,chord)
|
979
|
+
if @firstchordbase && ! ((chord[0]-@firstchordbase).abs<12)
|
980
|
+
puts "# chord auto inversion, too far." if $DEBUG && $debuglevel>1
|
981
|
+
chord=self.shiftChord(chord,@firstchordbase)
|
982
|
+
end
|
983
|
+
end
|
984
|
+
@lastchord=chord
|
985
|
+
if ! @firstchord
|
986
|
+
@firstchord=chord
|
987
|
+
@firstchordbase=@firstchord[0]
|
988
|
+
end
|
989
|
+
self.chord(chord,l,accent)
|
990
|
+
end
|
991
|
+
def self.invert last,c
|
992
|
+
last=c if ! last
|
993
|
+
root=last[0]
|
994
|
+
r=c.map{|i|
|
995
|
+
s=(root-i).abs%12
|
996
|
+
if s>6
|
997
|
+
s=12-s
|
998
|
+
end
|
999
|
+
[s,i]
|
1000
|
+
}.sort_by{|s,i|s}[0][1]
|
1001
|
+
n=(root-r)%12
|
1002
|
+
r=n>6 ? root-(12-n) : root+n
|
1003
|
+
cc=c.map{|i|(i-r)%12}.sort.map{|i|i+r}
|
1004
|
+
cc
|
1005
|
+
end
|
1006
|
+
def self.chord c,l=false,accent=false,sharp=0
|
1007
|
+
r=[]
|
1008
|
+
sspeed=@strokespeed
|
1009
|
+
(c=c.reverse;sspeed=-sspeed) if sspeed<0
|
1010
|
+
span=c.size
|
1011
|
+
sspeed=l/span if span*sspeed>l
|
1012
|
+
c.each{|i|
|
1013
|
+
r+=self.soundOn(i,@velocity,@ch,sharp)
|
1014
|
+
@waitingtime+=sspeed
|
1015
|
+
}
|
1016
|
+
l-=sspeed*(span-1)
|
1017
|
+
@waitingtime,rest=self.byGate(l)
|
1018
|
+
c.each{|i|
|
1019
|
+
r+=self.soundOff(i,@ch,sharp)
|
1020
|
+
}
|
1021
|
+
r+=self.rest(rest) if rest>0
|
1022
|
+
r
|
1023
|
+
end
|
1024
|
+
def self.rest len=@tbase,ch=@ch
|
1025
|
+
chx=format("%01x",ch)
|
1026
|
+
@nowtime+=len
|
1027
|
+
r=[]
|
1028
|
+
r<<Event.new(:end,len," 8#{chx} 3C 00 # rest #{len.to_i}(#{len.round(2)})tick後, オフ:ch#{ch}, key:3C\n")
|
1029
|
+
r
|
1030
|
+
end
|
1031
|
+
def self.restHex len=@tbase,ch=@ch
|
1032
|
+
r=self.rest(len,ch)
|
1033
|
+
r[0].data
|
1034
|
+
end
|
1035
|
+
# d : hex data
|
1036
|
+
def self.metaEvent d,type=1
|
1037
|
+
t=format("%02X",type)
|
1038
|
+
len=varlenHex(d.split.join.size/2)
|
1039
|
+
" FF #{t} #{len} #{d}\n"
|
1040
|
+
end
|
1041
|
+
def self.metaHook d,type,pos=0
|
1042
|
+
hexd,len=txt2hex(d)
|
1043
|
+
delta=varlenHex(pos)
|
1044
|
+
e=self.metaEvent hexd,type
|
1045
|
+
"#{delta} #{e} # #{d}\n"
|
1046
|
+
end
|
1047
|
+
def self.metaTitle d=@title,pos=0
|
1048
|
+
return "" if not d
|
1049
|
+
self.metaHook d,3,pos
|
1050
|
+
end
|
1051
|
+
def self.metaCopyright d,pos=0
|
1052
|
+
self.metaHook d,2,pos
|
1053
|
+
end
|
1054
|
+
def self.metaText d,pos=0
|
1055
|
+
self.metaHook d,1,pos
|
1056
|
+
end
|
1057
|
+
def self.generaterText
|
1058
|
+
file=__FILE__
|
1059
|
+
thisVer=File.mtime(file).strftime("%Y-%m-%d")
|
1060
|
+
thisVer="" if $debuglevel && $debuglevel>1
|
1061
|
+
pos=@tbase
|
1062
|
+
self.metaText("generated by midi-simple-make.rb (#{thisVer})",pos)
|
1063
|
+
end
|
1064
|
+
def self.lastrest
|
1065
|
+
self.metaText("data end",@tbase)
|
1066
|
+
end
|
1067
|
+
def self.dummyEvent comment,pos=0,d="00",type=1
|
1068
|
+
delta=varlenHex(pos)
|
1069
|
+
t=format("%02X",type)
|
1070
|
+
len=varlenHex(d.split.join.size/2)
|
1071
|
+
"#{delta} FF #{t} #{len} #{d} # #{pos} #{comment}\n"
|
1072
|
+
end
|
1073
|
+
def self.tempo bpm, len=0
|
1074
|
+
@bpmStart=bpm if ! @bpm
|
1075
|
+
@bpm=bpm
|
1076
|
+
d_bpm=self.makebpm(@bpm)
|
1077
|
+
@nowtime+=len
|
1078
|
+
Event.new(:e,len,"#{self.metaEvent(d_bpm,0x51)} # 四分音符の長さ (bpm: #{@bpm}) マイクロ秒で3byte\n")
|
1079
|
+
end
|
1080
|
+
def self.starttempo
|
1081
|
+
self.tempo(@startBpm)
|
1082
|
+
end
|
1083
|
+
def self.makebpm bpm
|
1084
|
+
d="000000"+(60_000_000/bpm.to_f).to_i.to_s(16)
|
1085
|
+
d[-6..-1]
|
1086
|
+
end
|
1087
|
+
def self.controlChange v
|
1088
|
+
v=~/^([^,]*),([^,]*)(,(.*))?/
|
1089
|
+
n,v,len=$1.to_i,$2.to_i,$4.to_i
|
1090
|
+
ch=@ch
|
1091
|
+
v=[0,[v,0x7f].min].max
|
1092
|
+
ch=format("%01x",ch)
|
1093
|
+
n=format("%02x",n)
|
1094
|
+
data=format("%02x",v)
|
1095
|
+
t=@waitingtime+len
|
1096
|
+
@waitnigtime=0
|
1097
|
+
@nowtime+=t
|
1098
|
+
Event.new(:e,t," B#{ch} #{n} #{data}\n")
|
1099
|
+
end
|
1100
|
+
def self.expre len,d
|
1101
|
+
self.controlChange("11,#{d},#{len}")
|
1102
|
+
end
|
1103
|
+
def self.ProgramChange ch,inst,len=0
|
1104
|
+
ch=@ch if ch==false
|
1105
|
+
ch=[ch,0x0f].min
|
1106
|
+
inst=[inst,0xff].min
|
1107
|
+
chx=format("%01x",ch)
|
1108
|
+
instx=format("%02x",inst)
|
1109
|
+
@nowtime+=len
|
1110
|
+
Event.new(:e,len," C#{chx} #{instx} # program change ch#{ch} #{inst} [#{@programList[inst][1]}]\n")
|
1111
|
+
end
|
1112
|
+
# system exclusive message event
|
1113
|
+
# = F0 [len] [maker id(1-3 byte)] [data] F7
|
1114
|
+
def self.sysExEvent d
|
1115
|
+
d=d+" F7"
|
1116
|
+
s=varlenHex(d.split.join.size/2)
|
1117
|
+
" F0 #{s} #{d}"
|
1118
|
+
end
|
1119
|
+
# GM,GS,XG wakeup command need over 50milisec. ?
|
1120
|
+
# if not, midi player may hung up.
|
1121
|
+
def self.GMsystemOn len=0,mode=1
|
1122
|
+
# GM1,GM2
|
1123
|
+
m=mode==2 ? 3 : 1
|
1124
|
+
@nowtime+=len
|
1125
|
+
ex=self.sysExEvent("7E 7F 09 0#{m}")
|
1126
|
+
Event.new(:sys,len," #{ex} # GM\n")
|
1127
|
+
end
|
1128
|
+
def self.XGsystemOn len=0
|
1129
|
+
@nowtime+=len
|
1130
|
+
ex=self.sysExEvent("43 10 4C 00 00 7E 00")
|
1131
|
+
Event.new(:sys,len," #{ex} # XG\n")
|
1132
|
+
end
|
1133
|
+
def self.xgMasterTune d,len=0
|
1134
|
+
d=[[d.to_i,-100].max,100].min
|
1135
|
+
n=(d+100)*256/200
|
1136
|
+
m=n/16
|
1137
|
+
l=n%16
|
1138
|
+
ex=self.sysExEvent("43 10 27 30 00 00 #{format"%02x",m} #{format"%02x",l} 00")
|
1139
|
+
Event.new(:sys,len," #{ex} # XG midi master tune \n")
|
1140
|
+
end
|
1141
|
+
def self.GSreset len=0
|
1142
|
+
@nowtime+=len
|
1143
|
+
ex=self.sysExEvent("41 10 42 12 40 00 7F 00 41")
|
1144
|
+
Event.new(:sys,len," #{ex} # GS \n")
|
1145
|
+
end
|
1146
|
+
def self.bankSelect d
|
1147
|
+
d=~/([^,]*),([^,]*)(,(.*))?/
|
1148
|
+
msb,lsb=$1.to_i,$2.to_i
|
1149
|
+
msb=[msb,0x7f].min
|
1150
|
+
lsb=[lsb,0x7f].min
|
1151
|
+
len=$4.to_i
|
1152
|
+
msb=format("%02x",msb)
|
1153
|
+
lsb=format("%02x",lsb)
|
1154
|
+
ch=@ch
|
1155
|
+
ch=format("%01x",ch)
|
1156
|
+
@nowtime+=len
|
1157
|
+
@nowtime+=len
|
1158
|
+
r=[]
|
1159
|
+
r<<Event.new(:sys,len," B#{ch} 00 #{msb} # BankSelect MSB\n")
|
1160
|
+
r<<Event.new(:sys,len," B#{ch} 20 #{lsb} # BankSelect LSB\n")
|
1161
|
+
r
|
1162
|
+
end
|
1163
|
+
def self.bankSelectPC d
|
1164
|
+
d="#{rand(0x7f)},rand(0x7f)},#{rand(0x7f)}" if d=="?"
|
1165
|
+
d=~/(([^,]*),([^,]*)),([^,]*)(,(.*))?/
|
1166
|
+
len=$6.to_i
|
1167
|
+
inst=$4.to_i ##
|
1168
|
+
bs=self.bankSelect("#{$1},#{len}")
|
1169
|
+
pc=self.ProgramChange(@ch,inst,len)
|
1170
|
+
[bs,pc]
|
1171
|
+
end
|
1172
|
+
def self.programGet p,num=false
|
1173
|
+
return 0 if not @programList
|
1174
|
+
if p=~/\?/
|
1175
|
+
r=[@programList[rand(@programList.size)]]
|
1176
|
+
p "random: ",r if $DEBUG
|
1177
|
+
else
|
1178
|
+
r=@programList.select{|n,line|line=~/#{p}/i}
|
1179
|
+
puts "no instrument name like '#{p}' in list" if $DEBUG && r.size==0
|
1180
|
+
end
|
1181
|
+
num=[num,r.size].min if num
|
1182
|
+
if num && r.size>0
|
1183
|
+
res=r[num-1][0]
|
1184
|
+
else
|
1185
|
+
res=r.size>0 ? r[0][0] : 0
|
1186
|
+
end
|
1187
|
+
res
|
1188
|
+
end
|
1189
|
+
def self.percussionGet p
|
1190
|
+
p=~/^_([^!]+)/
|
1191
|
+
p=$1 if $1
|
1192
|
+
return p.to_i if p=~/^[[:digit:]]+$/
|
1193
|
+
return @snare if not @percussionList
|
1194
|
+
return @gmKit[p] if @gmKit.keys.member?(p)
|
1195
|
+
r=@percussionList.select{|num,line|line=~/#{p}/i}
|
1196
|
+
puts "no percussion name like '#{p}' in list" if $DEBUG && r.size==0
|
1197
|
+
r.size>0 ? r[0][0] : @snare
|
1198
|
+
end
|
1199
|
+
def self.bend pos,depth,ch=false
|
1200
|
+
ch=@ch if ! ch
|
1201
|
+
depth=(depth*@bendCent).to_i
|
1202
|
+
pos+=@waitingtime
|
1203
|
+
@waitingtime=0
|
1204
|
+
@nowtime+=pos
|
1205
|
+
Event.new(:e,pos," e#{format"%01x",ch} #{bendHex(depth)} # t:#{pos} bend #{depth}\n")
|
1206
|
+
end
|
1207
|
+
def self.masterVolume v,pos=0
|
1208
|
+
v=[[0,v].max,0x7f].min
|
1209
|
+
vol=format("%02x",v)
|
1210
|
+
ex=self.sysExEvent("7F 04 01 00 #{vol}")+" # master volume #{v}"
|
1211
|
+
Event.new(:e,pos,ex)
|
1212
|
+
end
|
1213
|
+
def self.note2key i
|
1214
|
+
# inside parenthesis -+ are octave
|
1215
|
+
oct=0
|
1216
|
+
i=~/^([-+]+)?(.+)/
|
1217
|
+
octave,tone=$1,$2
|
1218
|
+
i=tone if octave
|
1219
|
+
case octave
|
1220
|
+
when /-+/
|
1221
|
+
oct=-octave.size
|
1222
|
+
when /\++/
|
1223
|
+
oct=octave.size
|
1224
|
+
end
|
1225
|
+
k=if tone=~/^_/
|
1226
|
+
[self.percussionGet(tone),@rythmChannel]
|
1227
|
+
elsif @notes.keys.member?(i)
|
1228
|
+
@basekey+oct*12+@notes[i]
|
1229
|
+
else
|
1230
|
+
i.to_i
|
1231
|
+
end
|
1232
|
+
if k.class!=Array && k<0
|
1233
|
+
k+=12 while k<0
|
1234
|
+
elsif k.class!=Array && k>0x7f
|
1235
|
+
k-=12 while k>0x7f
|
1236
|
+
end
|
1237
|
+
k
|
1238
|
+
end
|
1239
|
+
def self.basekeySet d
|
1240
|
+
case d
|
1241
|
+
when "-"
|
1242
|
+
if @basekey<12
|
1243
|
+
STDERR.puts "octave too low."
|
1244
|
+
else
|
1245
|
+
@basekey-=12
|
1246
|
+
end
|
1247
|
+
when "+"
|
1248
|
+
if @basekey>0x7f-12
|
1249
|
+
STDERR.puts "octave too high."
|
1250
|
+
else
|
1251
|
+
@basekey+=12
|
1252
|
+
end
|
1253
|
+
else
|
1254
|
+
@basekey=d
|
1255
|
+
end
|
1256
|
+
end
|
1257
|
+
def self.chordCenter c
|
1258
|
+
case c
|
1259
|
+
when "reset"
|
1260
|
+
@chordCenter=@chordCenterOrg
|
1261
|
+
when "+"
|
1262
|
+
@chordCenter-=6
|
1263
|
+
when "-"
|
1264
|
+
@chordCenter+=6
|
1265
|
+
when /^\+(.+)/
|
1266
|
+
@chordCenter+=$1.to_i
|
1267
|
+
when /^-(.+)/
|
1268
|
+
@chordCenter-=$1.to_i
|
1269
|
+
else
|
1270
|
+
@chordCenter=c.to_i
|
1271
|
+
end
|
1272
|
+
@chordCenter=[[0,@chordCenter].max,0x7f].min
|
1273
|
+
@firstchordbase=@chordCenter
|
1274
|
+
end
|
1275
|
+
def self.preLength v
|
1276
|
+
@preLength=v.map{|i|
|
1277
|
+
case i
|
1278
|
+
when "o",""
|
1279
|
+
@tbase
|
1280
|
+
when /^\*/
|
1281
|
+
$'.to_f
|
1282
|
+
else
|
1283
|
+
i.to_f*@tbase
|
1284
|
+
end
|
1285
|
+
}
|
1286
|
+
end
|
1287
|
+
def self.preNote v
|
1288
|
+
@preNote=v.map{|i|
|
1289
|
+
case i
|
1290
|
+
when "?"
|
1291
|
+
rand(0x7f)
|
1292
|
+
else
|
1293
|
+
self.note2key(i)
|
1294
|
+
end
|
1295
|
+
}
|
1296
|
+
end
|
1297
|
+
def self.preVelocity v
|
1298
|
+
@preVelocity=v.map{|i|
|
1299
|
+
case i
|
1300
|
+
when "o",""
|
1301
|
+
@velocity
|
1302
|
+
when "-"
|
1303
|
+
@velocity-10
|
1304
|
+
when "+"
|
1305
|
+
@velocity+10
|
1306
|
+
else
|
1307
|
+
i.to_i
|
1308
|
+
end
|
1309
|
+
}
|
1310
|
+
end
|
1311
|
+
def self.preGate v
|
1312
|
+
@preGate=v.map{|i|
|
1313
|
+
case i
|
1314
|
+
when "o",""
|
1315
|
+
@gateRate
|
1316
|
+
when "-"
|
1317
|
+
@gateRate*0.5
|
1318
|
+
else
|
1319
|
+
i.to_i
|
1320
|
+
end
|
1321
|
+
}
|
1322
|
+
end
|
1323
|
+
def self.preAfter v
|
1324
|
+
@preAfter=v.map{|i|
|
1325
|
+
case i
|
1326
|
+
when "o",""
|
1327
|
+
false
|
1328
|
+
else
|
1329
|
+
revertPre(i)
|
1330
|
+
end
|
1331
|
+
}
|
1332
|
+
end
|
1333
|
+
def self.preBefore v
|
1334
|
+
@preBefore=v.map{|i|
|
1335
|
+
case i
|
1336
|
+
when "o",""
|
1337
|
+
false
|
1338
|
+
else
|
1339
|
+
i
|
1340
|
+
end
|
1341
|
+
}
|
1342
|
+
end
|
1343
|
+
def self.strokeSpeed s
|
1344
|
+
s=s.to_i
|
1345
|
+
@strokespeed=s
|
1346
|
+
end
|
1347
|
+
def self.setmark m
|
1348
|
+
n=@marktrack.getcount(m,@tracknum)
|
1349
|
+
m="#{m}@#{n+1}" if n>0
|
1350
|
+
@marktrack.set(m,@tracknum,@nowtime)
|
1351
|
+
Event.new(:mark,m,@tracknum,@nowtime)
|
1352
|
+
end
|
1353
|
+
def self.eventlist2str elist
|
1354
|
+
r=[]
|
1355
|
+
# EventList : [func,args] or [callonly, func,args] or others
|
1356
|
+
elist.each{|h|
|
1357
|
+
cmd,*arg=h
|
1358
|
+
r<<Event.new(:c,"# #{cmd} #{arg}")
|
1359
|
+
case cmd
|
1360
|
+
when :basekeyPlus
|
1361
|
+
@basekey+=arg[0]
|
1362
|
+
when :raw
|
1363
|
+
r<<Event.new(:raw,arg[0])
|
1364
|
+
when :ahead
|
1365
|
+
r<<Event.new(:ahead,arg[0])
|
1366
|
+
when :velocity
|
1367
|
+
@velocity=arg[0]
|
1368
|
+
when :velocityFuzzy
|
1369
|
+
@velocityFuzzy=arg[0]
|
1370
|
+
when :ch
|
1371
|
+
@ch=arg[0]
|
1372
|
+
when :waitingtime
|
1373
|
+
@waitingtime+=arg[0]
|
1374
|
+
when :call
|
1375
|
+
cmd,*arg=arg
|
1376
|
+
method(cmd).call(*arg)
|
1377
|
+
when :soundOff
|
1378
|
+
if arg[0]=="all"
|
1379
|
+
@onlist.each{|o|
|
1380
|
+
r<<method(cmd).call(o)
|
1381
|
+
}
|
1382
|
+
else
|
1383
|
+
r<<method(cmd).call(*arg)
|
1384
|
+
end
|
1385
|
+
else
|
1386
|
+
r<<method(cmd).call(*arg)
|
1387
|
+
end
|
1388
|
+
}
|
1389
|
+
rr=[]
|
1390
|
+
ahead=0
|
1391
|
+
after=0
|
1392
|
+
r.flatten!
|
1393
|
+
r.each{|i|
|
1394
|
+
if i.class==String
|
1395
|
+
rr<<i
|
1396
|
+
else
|
1397
|
+
case i.type
|
1398
|
+
when :ahead
|
1399
|
+
ahead=i.time
|
1400
|
+
next if ahead==0
|
1401
|
+
n=0
|
1402
|
+
n-=1 until (rr[n].time>0) || n<-10
|
1403
|
+
if n>-10
|
1404
|
+
ahead=[ahead,-rr[n].time].max
|
1405
|
+
rr[n].time+=ahead
|
1406
|
+
after=-ahead
|
1407
|
+
else
|
1408
|
+
after=0
|
1409
|
+
end
|
1410
|
+
when :end,:e,:sys
|
1411
|
+
(i.time+=after;after=0) if after>0
|
1412
|
+
(i.time+=after;after=0) if after<0 && i.time+after>=0
|
1413
|
+
rr<<i
|
1414
|
+
when :c,:raw,:mark
|
1415
|
+
rr<<i
|
1416
|
+
else
|
1417
|
+
"? #{i}"
|
1418
|
+
end
|
1419
|
+
end
|
1420
|
+
}
|
1421
|
+
rr.map{|i|
|
1422
|
+
case i
|
1423
|
+
when String
|
1424
|
+
i
|
1425
|
+
when Event
|
1426
|
+
i.data
|
1427
|
+
end
|
1428
|
+
}
|
1429
|
+
end
|
1430
|
+
def self.makefraze rundata,tc
|
1431
|
+
return "" if not rundata
|
1432
|
+
self.trackPrepare(tc)
|
1433
|
+
@systemWait=120
|
1434
|
+
@h=[]
|
1435
|
+
wait=[]
|
1436
|
+
@frest=0
|
1437
|
+
@frestc=0
|
1438
|
+
@nowtime=0
|
1439
|
+
@shiftbase=40
|
1440
|
+
accent=false
|
1441
|
+
sharp=0
|
1442
|
+
cmd=rundata.scan(/&\([^)]+\)|\([-+]*[[:digit:]]?\)|:[^\(,]+\([^\)\(]+\),|:[^,]+,|\([^:]*:[^)\(]*\)|_[^!_]+!|_[^_]__[^\?]+\?|v[[:digit:]]+|[<>][[:digit:]]*|\*?[[:digit:]]+\.[[:digit:]]+|\*?[[:digit:]]+|[-+[:alpha:]]|\^|`|'|./)
|
1443
|
+
cmd<<" " # dummy
|
1444
|
+
p "make start: ",cmd if $DEBUG
|
1445
|
+
cmd.each{|i|
|
1446
|
+
if wait.size>0
|
1447
|
+
t=@tbase
|
1448
|
+
i=~/^(\*)?([[:digit:]]+(\.[[:digit:]]+)?)/
|
1449
|
+
tickmode=$1
|
1450
|
+
t=$2.to_f if $&
|
1451
|
+
if $&
|
1452
|
+
if tickmode
|
1453
|
+
puts "tick: #{t}" if $DEBUG && $debuglevel>1
|
1454
|
+
else
|
1455
|
+
t*=@tbase
|
1456
|
+
end
|
1457
|
+
@frest+=(t-t.to_i)
|
1458
|
+
t=t.to_i
|
1459
|
+
if @frest>=1
|
1460
|
+
t+=@frest.to_i
|
1461
|
+
@frest=@frest-@frest.to_i
|
1462
|
+
@frestc+=1
|
1463
|
+
end
|
1464
|
+
end
|
1465
|
+
wait.each{|m,c|
|
1466
|
+
case m
|
1467
|
+
when :percussion
|
1468
|
+
@h<<[:percussionNote,c,t,accent,sharp]
|
1469
|
+
when :rawsound
|
1470
|
+
@h<<[:byKey,c,t,accent,sharp]
|
1471
|
+
when :sound
|
1472
|
+
@h<<[:notes,c,t,accent,sharp]
|
1473
|
+
when :dummyNote
|
1474
|
+
@h<<[:dummyNote,c,t,accent,sharp]
|
1475
|
+
when :chord
|
1476
|
+
@h<<[:chord,c,t,accent,sharp]
|
1477
|
+
when :chordName
|
1478
|
+
@h<<[:chordName,c,t,accent,sharp]
|
1479
|
+
when :rest
|
1480
|
+
@h<<[:rest,t]
|
1481
|
+
end
|
1482
|
+
}
|
1483
|
+
wait=[]
|
1484
|
+
accent=false
|
1485
|
+
sharp=0
|
1486
|
+
end
|
1487
|
+
case i
|
1488
|
+
when /^\(([-+]*)([[:digit:]])?\)/
|
1489
|
+
n=$2 ? $2.to_i : 1
|
1490
|
+
if $1.size>1
|
1491
|
+
n=$1.size
|
1492
|
+
end
|
1493
|
+
sh=$1 ? $1[0..0] : "+"
|
1494
|
+
sh="#{sh}#{n}".to_i
|
1495
|
+
sharp=sh
|
1496
|
+
when /^\(key:(-?)\+?([[:digit:]]+)\)/
|
1497
|
+
tr=$2.to_i
|
1498
|
+
tr*=-1 if $1=="-"
|
1499
|
+
@h<<[:basekeyPlus,tr]
|
1500
|
+
when /^\(mark:(.*)\)/
|
1501
|
+
@h<<[:setmark,$1]
|
1502
|
+
when /^\(V:(.*)\)/
|
1503
|
+
vs=$1.split(",")
|
1504
|
+
@h<<[:call,:preVelocity,vs]
|
1505
|
+
when /^\(N:(.*)\)/
|
1506
|
+
s=$1.split(",")
|
1507
|
+
@h<<[:call,:preNote,s]
|
1508
|
+
when /^\(L:(.*)\)/
|
1509
|
+
s=$1.split(",")
|
1510
|
+
@h<<[:call,:preLength,s]
|
1511
|
+
when /^\(G:(.*)\)/
|
1512
|
+
gs=$1.split(",")
|
1513
|
+
@h<<[:call,:preGate,gs]
|
1514
|
+
when /^\(A:(.*)\)/
|
1515
|
+
s=$1.split(",")
|
1516
|
+
@h<<[:call,:preAfter,s]
|
1517
|
+
when /^\(B:(.*)\)/
|
1518
|
+
s=$1.split(",")
|
1519
|
+
@h<<[:call,:preBefore,s]
|
1520
|
+
when /^\(roll:(.*)\)/
|
1521
|
+
@shiftbase=$1.to_i
|
1522
|
+
when /^\(key:reset\)/
|
1523
|
+
@h<<[:call,:basekeySet,@basekeyOrg]
|
1524
|
+
when /^\(p:(([[:digit:]]+),)?(([[:digit:]]+)|([\?[:alnum:]]+)(,([[:digit:]]))?)\)/
|
1525
|
+
channel=$1 ? $2.to_i : false
|
1526
|
+
subNo=false
|
1527
|
+
if $5
|
1528
|
+
subNo=$7.to_i if $7
|
1529
|
+
instrument=self.programGet($5,subNo)
|
1530
|
+
else
|
1531
|
+
instrument=$4.to_i
|
1532
|
+
end
|
1533
|
+
@h<<[:ProgramChange,channel,instrument]
|
1534
|
+
when /^\(bend:(([[:digit:]]+),)?([-,.[:digit:]]+)\)|^_b__([^?]+)\?/
|
1535
|
+
i="(bend:#{$4.gsub('_'){','}})" if $4
|
1536
|
+
x,pos,b=worddata("bend",i)
|
1537
|
+
npos=0
|
1538
|
+
b.each{|depth|
|
1539
|
+
@h<<[:bend,npos,depth]
|
1540
|
+
npos=pos
|
1541
|
+
}
|
1542
|
+
when /^\(bendCent:([^\)]*)\)/
|
1543
|
+
$1=~/on/i
|
1544
|
+
on=$& ? true : false
|
1545
|
+
@h<<[:call,:bendCent,on]
|
1546
|
+
when /^\(bendRange:([^\)]*)\)/
|
1547
|
+
@h<<[:bendRange,$1]
|
1548
|
+
when /^&\((.+)\)/
|
1549
|
+
raw=rawHexPart($1)
|
1550
|
+
@h<<[:raw,raw]
|
1551
|
+
when /^:([^\(,]+(\([^\)]+\))?),/
|
1552
|
+
wait<<[:chordName,$1]
|
1553
|
+
when /^_(([[:digit:]]+)|([[:alnum:]]+))!/
|
1554
|
+
if $2
|
1555
|
+
perc=$2.to_i
|
1556
|
+
else
|
1557
|
+
perc=self.percussionGet($3)
|
1558
|
+
end
|
1559
|
+
wait<<[:percussion,perc]
|
1560
|
+
when /^\(vFuzzy:([0-9]+)\)/
|
1561
|
+
@h<<[:velocityFuzzy,$1.to_i]
|
1562
|
+
when /^\(v:([0-9]+)\)/, /^v([0-9]+)/
|
1563
|
+
@h<<[:velocity,$1.to_i]
|
1564
|
+
when /^\(g:([0-9]+)\)/
|
1565
|
+
@h<<[:call,:setGateRate,$1.to_i]
|
1566
|
+
when /^\(volume:(.*)\)/
|
1567
|
+
@h<<[:masterVolume,$1.to_i]
|
1568
|
+
when /^\(tempo:reset\)/
|
1569
|
+
@h<<[:tempo,@bpmStart]
|
1570
|
+
when /^\(ch:(.*)\)/
|
1571
|
+
ch=$1.to_i
|
1572
|
+
ch=9 if $1=="drum"
|
1573
|
+
@h<<[:ch,ch]
|
1574
|
+
when /^\(cc:(.*)\)/
|
1575
|
+
@h<<[:controlChange,$1]
|
1576
|
+
when /^\(bs:(.*)\)/
|
1577
|
+
@h<<[:bankSelect,$1]
|
1578
|
+
when /^\(bspc:(.*)\)/
|
1579
|
+
@h<<[:bankSelectPC,$1]
|
1580
|
+
when /^\(gs:reset\)/
|
1581
|
+
@h<<[:GSreset,0]
|
1582
|
+
@h<<[:rest,@systemWait]
|
1583
|
+
when /^\(gm(2)?:on\)/
|
1584
|
+
gm=$1 ? 2 : 1
|
1585
|
+
@h<<[:GMsystemOn,0,gm]
|
1586
|
+
@h<<[:rest,@systemWait]
|
1587
|
+
when /^\(xg:on\)/
|
1588
|
+
@h<<[:XGsystemOn,0]
|
1589
|
+
@h<<[:rest,@systemWait]
|
1590
|
+
when /^\(syswait:\)/
|
1591
|
+
@h<<[:rest,@systemWait]
|
1592
|
+
when /^\(xgMasterTune:(.*)\)/
|
1593
|
+
@h<<[:xgMasterTune,$1]
|
1594
|
+
when /^\(pan:(<|>)?(.*)\)/
|
1595
|
+
pan=$2.to_i
|
1596
|
+
case $1
|
1597
|
+
when ">"
|
1598
|
+
pan+=64
|
1599
|
+
when "<"
|
1600
|
+
pan-=64
|
1601
|
+
else
|
1602
|
+
end
|
1603
|
+
@h<<[:controlChange,"10,#{pan}"]
|
1604
|
+
when /^\(wait:(\*)?(.*)\)/
|
1605
|
+
@h<<[:waitingtime,$1? $2.to_i : $2.to_f*@tbase]
|
1606
|
+
when /^\(accent:([^)]*)\)/
|
1607
|
+
@h<<[:call,:accent,$1]
|
1608
|
+
when /^\(on:(.*)\)/
|
1609
|
+
i=$1
|
1610
|
+
@h<<[:soundOn,i]
|
1611
|
+
when /^\(off:(.*)\)/
|
1612
|
+
@h<<[:soundOff,$1]
|
1613
|
+
when /^\(chordcenter:(.*)\)/
|
1614
|
+
@h<<[:call,:chordCenter,$1]
|
1615
|
+
when /^\(stroke:(.*)\)/
|
1616
|
+
@h<<[:call,:strokeSpeed,$1]
|
1617
|
+
when /^\((chord|C):(.*)\)/
|
1618
|
+
chord=$2.split.join.split(",") # .map{|i|self.note2key(i)}
|
1619
|
+
wait<<[:chord,chord]
|
1620
|
+
when /^\(text:(.*)\)/
|
1621
|
+
@h<<[:text,$1]
|
1622
|
+
when /^\(tempo:(.*)\)/
|
1623
|
+
@bpm=$1.to_i
|
1624
|
+
@h<<[:tempo,@bpm] if @bpm>0
|
1625
|
+
when /^\(\?:(.*)\)/
|
1626
|
+
ks=$1
|
1627
|
+
if ks=~/\-/
|
1628
|
+
ks=[*($`.to_i)..($'.to_i)]
|
1629
|
+
key=ks[rand(ks.size)]
|
1630
|
+
wait<<[:rawsound,key]
|
1631
|
+
else
|
1632
|
+
ks=ks.split(",")
|
1633
|
+
key=ks[rand(ks.size)]
|
1634
|
+
wait<<[:sound,key]
|
1635
|
+
end
|
1636
|
+
when /^\(x:(.*)\)/
|
1637
|
+
key=$1.to_i
|
1638
|
+
wait<<[:rawsound,key]
|
1639
|
+
when /^<(.*)/
|
1640
|
+
rate=1.25
|
1641
|
+
if $1.size>0
|
1642
|
+
rate=$1.to_i/100.0
|
1643
|
+
end
|
1644
|
+
@bpm=@bpm/rate
|
1645
|
+
@h<<[:tempo,@bpm]
|
1646
|
+
when /^>(.*)/
|
1647
|
+
rate=1.25
|
1648
|
+
if $1.size>0
|
1649
|
+
rate=$1.to_i/100.0
|
1650
|
+
end
|
1651
|
+
@bpm=@bpm*rate
|
1652
|
+
@h<<[:tempo,@bpm]
|
1653
|
+
when "-"
|
1654
|
+
@h<<[:call,:basekeySet,"-"]
|
1655
|
+
when "+"
|
1656
|
+
@h<<[:call,:basekeySet,"+"]
|
1657
|
+
when /^\*?[0-9]+/
|
1658
|
+
# (i.to_i-1).times{@h<<@h[-1]}
|
1659
|
+
when "`"
|
1660
|
+
@h<<[:ahead,-@shiftbase]
|
1661
|
+
when "'"
|
1662
|
+
@h<<[:ahead,@shiftbase]
|
1663
|
+
when "^"
|
1664
|
+
p "accent" if $DEBUG
|
1665
|
+
accent=true
|
1666
|
+
when "="
|
1667
|
+
@h<<@h[-1]
|
1668
|
+
when "r"
|
1669
|
+
wait<<[:rest,i]
|
1670
|
+
when "o"
|
1671
|
+
wait<<[:dummyNote,i]
|
1672
|
+
when " "
|
1673
|
+
when "?"
|
1674
|
+
wait<<[:rawsound,rand(0x7f)]
|
1675
|
+
else
|
1676
|
+
if @notes.keys.member?(i)
|
1677
|
+
wait<<[:sound,i]
|
1678
|
+
else
|
1679
|
+
STDERR.puts "[#{i}] undefined note?" # if $DEBUG
|
1680
|
+
end
|
1681
|
+
end
|
1682
|
+
}
|
1683
|
+
p @h if $DEBUG
|
1684
|
+
puts "float rest add times: #{@frestc}" if $DEBUG
|
1685
|
+
@h=self.eventlist2str(@h)
|
1686
|
+
@h*"\n# track: #{@tracknum} ==== \n"
|
1687
|
+
end
|
1688
|
+
def self.loadMap file, base=0
|
1689
|
+
if not File.exist?(file)
|
1690
|
+
map=false
|
1691
|
+
else
|
1692
|
+
category=""
|
1693
|
+
li=File.readlines(file).map{|i|
|
1694
|
+
i=i.toutf8
|
1695
|
+
# zero base
|
1696
|
+
# category name plus
|
1697
|
+
if i=~/^[[:digit:]]+/
|
1698
|
+
[i.split[0].to_i-base,"#{i.chomp.toutf8} #{category}"]
|
1699
|
+
else
|
1700
|
+
category=i.chomp.toutf8 if i.chomp.size>0
|
1701
|
+
false
|
1702
|
+
end
|
1703
|
+
}-[false]
|
1704
|
+
map=li.size>0 ? li : false
|
1705
|
+
end
|
1706
|
+
end
|
1707
|
+
def self.loadProgramChange file
|
1708
|
+
if not File.exist?(file)
|
1709
|
+
@programList=false
|
1710
|
+
else
|
1711
|
+
@programList=self.loadMap(file,1)
|
1712
|
+
end
|
1713
|
+
end
|
1714
|
+
def self.testGs cycle,key,scaleAll,intro,mapnum=3
|
1715
|
+
d=@programList.select{|i,v|v=~/#{key}/i}.map{|i,data|
|
1716
|
+
[mapnum].map{|lsb|
|
1717
|
+
msbs=[0,8,16,24,32]
|
1718
|
+
msbs=[0] if i>63
|
1719
|
+
msbs=[*0..5] if i>121
|
1720
|
+
msbs.map{|msb|
|
1721
|
+
#msb*=8
|
1722
|
+
"(bspc:#{msb},#{lsb},#{i},4) #{cycle}"
|
1723
|
+
}*""
|
1724
|
+
}*""
|
1725
|
+
}*""
|
1726
|
+
scale=scaleAll # mapnum<4 ? [*25..87].map{|i|"(x:#{i})"}*"" : scaleAll
|
1727
|
+
lsb=mapnum
|
1728
|
+
ps=[1,2,3,9,10,11,12,17,25,26,27,28,29,30,31,33,41,49,50,51,53,54,57,58,59,128].map{|i|i-1}
|
1729
|
+
perc=ps.map{|p|
|
1730
|
+
[0,8,16,24,25,32,40,48,56,127].map{|msb|
|
1731
|
+
"(bspc:#{msb},#{lsb},#{p},4) #{intro} #{scale}"
|
1732
|
+
}*""
|
1733
|
+
}*""
|
1734
|
+
d="(gs:reset)r14 "+d+"(ch:9)"+perc
|
1735
|
+
end
|
1736
|
+
def self.test cycle,mode=1
|
1737
|
+
cycle="cdef" if ! cycle
|
1738
|
+
key=$test
|
1739
|
+
p key
|
1740
|
+
inGM=[*35..81]
|
1741
|
+
outGM=[*0..127]-inGM
|
1742
|
+
# make sounds outside GM map shorter
|
1743
|
+
scaleAll=(inGM.map{|i|"(x:#{i})"}+outGM.map{|i|"(x:#{i})0.2"})*""
|
1744
|
+
s,k,h,l,o,c,cc=@gmSnare,@gmKick,@gmHiTom,@gmLoTom,@gmOpenH,@gmCloseH,@gmCrashCym
|
1745
|
+
intro="(x:#{k})0.2(x:#{cc})0.8(x:#{k})(x:#{s})(x:#{s})(x:#{c})(x:#{c})(x:#{o})(x:#{c})
|
1746
|
+
v64(x:#{h})0.68(x:#{l})0.66(x:#{l})0.66
|
1747
|
+
v42(x:#{o})0.34v32(x:#{c})0.33(x:#{c})0.33 v20(x:#{o})0.12(x:#{c})0.11(x:#{c})0.11v92(x:#{o})0.66v64"
|
1748
|
+
mode=1 if ! mode
|
1749
|
+
@data=(
|
1750
|
+
case mode
|
1751
|
+
when 1 || "gm"
|
1752
|
+
d=@programList.select{|i,v|v=~/#{key}/i}.map{|i,data|"(p:#{i})#{cycle}"}*""
|
1753
|
+
perc=scaleAll
|
1754
|
+
"(gm:on)r14 "+d+"(ch:9)"+intro+perc
|
1755
|
+
when 2 || "xg"
|
1756
|
+
d=@programList.select{|i,v|v=~/#{key}/i}.map{|i,data|
|
1757
|
+
[0,1,18,32,33,34,40,41,45,64,65,70,71,97,98].map{|lsb|
|
1758
|
+
[0].map{|msb|
|
1759
|
+
#msb*=8
|
1760
|
+
"(bspc:#{msb},#{lsb},#{i},4) #{cycle}"
|
1761
|
+
}*""
|
1762
|
+
}*""
|
1763
|
+
}*""
|
1764
|
+
lsb=0
|
1765
|
+
msb=127
|
1766
|
+
perc0=[0].map{|p| "(bspc:#{msb},#{lsb},#{p},4) #{scaleAll}"}*""
|
1767
|
+
scale=([*28..79]-[51,52,54,55,58,60,61,65,66,67,68,69,71,72,73,74]).map{|i|"(x:#{i})"}*"" # main unique sound maybe
|
1768
|
+
perc=[*1..48].map{|p| "(bspc:#{msb},#{lsb},#{p},4) #{intro} #{scaleAll}"}*""
|
1769
|
+
msb=126
|
1770
|
+
scale=([*36..42]+[*52..62]+[*68..73]+[*84..91]).map{|i|"(x:#{i})"}*""
|
1771
|
+
perc2=[*0..1].map{|p| "(bspc:#{msb},#{lsb},#{p},4) #{intro} #{scale}"}*""
|
1772
|
+
d="(xg:on)r14 "+d+"(ch:9)"+perc+perc2+perc0
|
1773
|
+
when 3 || "gs"
|
1774
|
+
mapnum=3 # 0(gm),1(sc-55),2(sc-88),3(sc-88pro),4(sc-8850)
|
1775
|
+
self.testGs(cycle,key,scaleAll,intro,mapnum)
|
1776
|
+
else
|
1777
|
+
end
|
1778
|
+
)
|
1779
|
+
end
|
1780
|
+
def self.loadPercussionMap file
|
1781
|
+
@snare=35
|
1782
|
+
@gmSnare,@gmKick,@gmHiTom,@gmLoTom,@gmOpenH,@gmCloseH,@gmCrashCym=38,35,50,45,46,42,49
|
1783
|
+
@gmKit={}
|
1784
|
+
@gmKit["s"],@gmKit["k"],@gmKit["h"],@gmKit["l"],@gmKit["o"],@gmKit["c"],@gmKit["cc"]=@gmSnare,@gmKick,@gmHiTom,@gmLoTom,@gmOpenH,@gmCloseH,@gmCrashCym
|
1785
|
+
if not File.exist?(file)
|
1786
|
+
@percussionList=false
|
1787
|
+
else
|
1788
|
+
@percussionList=self.loadMap(file,0)
|
1789
|
+
@snare=self.percussionGet("snare")
|
1790
|
+
end
|
1791
|
+
end
|
1792
|
+
def self.trackMake data
|
1793
|
+
@marksh||=@marktrack.calc
|
1794
|
+
data=data.split("\n").map{|i|
|
1795
|
+
i=~/^# marktrack\(([^\)]+)\)/
|
1796
|
+
if $&
|
1797
|
+
key=$1
|
1798
|
+
pos=@marksh[key]
|
1799
|
+
c="position mark: #{key}, #{pos*1.0/@tbase}"
|
1800
|
+
@marksh.keys.member?(key) ? self.dummyEvent(c,pos) : i
|
1801
|
+
else
|
1802
|
+
i
|
1803
|
+
end
|
1804
|
+
}*"\n"
|
1805
|
+
start="
|
1806
|
+
# track header
|
1807
|
+
4D 54 72 6B # MTrk
|
1808
|
+
"
|
1809
|
+
dsize=trackSizeHex(data,@cmark)
|
1810
|
+
trackend="
|
1811
|
+
00 FF 2F 00 # end of track
|
1812
|
+
"
|
1813
|
+
[start,dsize,data,trackend]
|
1814
|
+
end
|
1815
|
+
def self.dumpHex
|
1816
|
+
@h
|
1817
|
+
end
|
1818
|
+
end
|
1819
|
+
def multiplet d,tbase
|
1820
|
+
d=~/\/((\*)?([[:digit:].]+)?:)?(.*)\//
|
1821
|
+
tickmode=$2
|
1822
|
+
i=$4
|
1823
|
+
rate=$3 ? $3.to_f : 1
|
1824
|
+
rate=1 if rate==0
|
1825
|
+
if tickmode
|
1826
|
+
total=$3.to_i
|
1827
|
+
else
|
1828
|
+
total=tbase*rate
|
1829
|
+
end
|
1830
|
+
r=i.scan(/\(\?:[^\]]+\)|\(x:[^\]]+\)|\(chord:[^)]+\)|\(C:[^)]+\)|:[^\(,]+\([^\)]+\),|:[^,]+,|[[:digit:]\.]+|_[^!]+!|~|\([-+]*[[:digit:]]?\)|[-+^`']|./)
|
1831
|
+
wait=[]
|
1832
|
+
notes=[]
|
1833
|
+
mod=[]
|
1834
|
+
r.each{|i|
|
1835
|
+
case i
|
1836
|
+
when "-","+","^","`","'",/^\([-+]*[[:digit:]]?\)/
|
1837
|
+
mod<<i
|
1838
|
+
when /^\((\?|x|C|chord):[^\)]+\)|^\^?:[^,]+,/
|
1839
|
+
wait<<1
|
1840
|
+
notes<<"#{mod*""}#{i}"
|
1841
|
+
mod=[]
|
1842
|
+
when /^[[:digit:]]+/
|
1843
|
+
wait[-1]*=i.to_f
|
1844
|
+
when "="
|
1845
|
+
wait<<wait[-1]
|
1846
|
+
notes<<notes[-1]
|
1847
|
+
when " "
|
1848
|
+
else
|
1849
|
+
wait<<1
|
1850
|
+
notes<<"#{mod*""}#{i}"
|
1851
|
+
mod=[]
|
1852
|
+
end
|
1853
|
+
}
|
1854
|
+
sum=wait.inject{|s,i|s+i}
|
1855
|
+
ls=wait.map{|i|(i*1.0/sum*total).round} # .map{|i|i.round(dep)}
|
1856
|
+
er=(total-ls.inject{|s,i|s+i}).to_i
|
1857
|
+
if er>0
|
1858
|
+
er.times{|i|
|
1859
|
+
ls[i]+=1
|
1860
|
+
}
|
1861
|
+
else
|
1862
|
+
(-er).times{|i|
|
1863
|
+
ls[-1-i]-=1
|
1864
|
+
}
|
1865
|
+
end
|
1866
|
+
er=(total-ls.inject{|s,i|s+i})
|
1867
|
+
ls[-1]+=er
|
1868
|
+
result=[]
|
1869
|
+
notes.size.times{|i|
|
1870
|
+
result<<notes[i]
|
1871
|
+
result<<"*#{ls[i]}"
|
1872
|
+
}
|
1873
|
+
p "multiplet: ",total,ls.inject{|s,i|s+i} if $DEBUG && $debuglevel>1
|
1874
|
+
result*""
|
1875
|
+
end
|
1876
|
+
|
1877
|
+
def funcApply m,name,x
|
1878
|
+
a=m.keys.select{|k|k=~/^#{name}\(/}[0]
|
1879
|
+
fbodyOrg,*default=m[a]
|
1880
|
+
return false if ! fbodyOrg
|
1881
|
+
x=~/(([^:]*):)?/
|
1882
|
+
interval,x=$2,$'
|
1883
|
+
x=x.split(",")
|
1884
|
+
max=x.size
|
1885
|
+
x+=default
|
1886
|
+
a=~/\((.*)\)/
|
1887
|
+
args=$1.split(',').map{|k|"$#{k}"}
|
1888
|
+
n=0
|
1889
|
+
# p "x,args,mac:",x,args,fbody
|
1890
|
+
r=[]
|
1891
|
+
max.times{|i|
|
1892
|
+
fbody=fbodyOrg
|
1893
|
+
args.each{|k|
|
1894
|
+
fbody=fbody.gsub(k){x[n]}
|
1895
|
+
n+=1
|
1896
|
+
}
|
1897
|
+
r<<fbody
|
1898
|
+
}
|
1899
|
+
sep=""
|
1900
|
+
sep="(wait:#{interval})" if interval
|
1901
|
+
r*sep
|
1902
|
+
end
|
1903
|
+
def macroDef data
|
1904
|
+
macro={}
|
1905
|
+
mline=false
|
1906
|
+
tmp=[]
|
1907
|
+
name=""
|
1908
|
+
num=1
|
1909
|
+
s=data.scan(/macro +[^ ;:=]+ *:= *\( *;|macro +[^ ;:=]+ *:=[^;]+|[^ ;:=]+ *:= *\( *;|[^ ;:=]+ *:=[^;]+| *\) *;|[^;]+|./)
|
1910
|
+
data=s.map{|i|
|
1911
|
+
case i
|
1912
|
+
when /^(macro +)? *([^ ;:=]+) *:= *\( *;/
|
1913
|
+
mline=true
|
1914
|
+
num=1
|
1915
|
+
name=$2
|
1916
|
+
tmp=[]
|
1917
|
+
""
|
1918
|
+
when /^(macro +)? *([^ ;:=]+) *:=([^;]+)/
|
1919
|
+
r=$3
|
1920
|
+
key=$2
|
1921
|
+
chord=/([^$]|^)\{([^\{\}]*)\}/
|
1922
|
+
r.gsub!(chord){"#{$1}(C:#{$2})"} while r=~chord
|
1923
|
+
macro[key]=modifierComp(r,macro)
|
1924
|
+
""
|
1925
|
+
when / *\) *;/
|
1926
|
+
mline=false
|
1927
|
+
name=""
|
1928
|
+
when ";",/^ *$/
|
1929
|
+
i
|
1930
|
+
else
|
1931
|
+
if mline
|
1932
|
+
key="#{name}[#{num}]"
|
1933
|
+
macro[key]=i
|
1934
|
+
num+=1
|
1935
|
+
""
|
1936
|
+
else
|
1937
|
+
i
|
1938
|
+
end
|
1939
|
+
end
|
1940
|
+
}*""
|
1941
|
+
[macro,data]
|
1942
|
+
end
|
1943
|
+
def nestsearch d,macro
|
1944
|
+
a=d.scan(/\[[^\[\]]*\] *[[:digit:]]+/)!=[]
|
1945
|
+
r=d.scan(/\/[^\/]+\/|\[|\]|\.FINE|\.DS|\.DC|\.\$|\.toCODA|\.CODA|\.SKIP|\$\{[^ \{\}]+\}|\$[^ ;\$*_^`'+-]+|;|./).map{|i|
|
1946
|
+
case i
|
1947
|
+
when /^\$\{([^\}]+)\}/
|
1948
|
+
$1
|
1949
|
+
when /^\$/
|
1950
|
+
$'
|
1951
|
+
when /\/[^\/]+\//
|
1952
|
+
true
|
1953
|
+
else
|
1954
|
+
""
|
1955
|
+
end
|
1956
|
+
}
|
1957
|
+
b=(macro.keys-r).size<macro.keys.size
|
1958
|
+
c=r.member?(true)
|
1959
|
+
chord=false
|
1960
|
+
d=~/([^$]|^)\{([^\}]*)\}/
|
1961
|
+
chord=true if $&
|
1962
|
+
p "nest? #{a} #{b} #{c} #{chord}",r,macro if $DEBUG
|
1963
|
+
a||b||c||chord
|
1964
|
+
end
|
1965
|
+
def tie d,tbase
|
1966
|
+
res=[]
|
1967
|
+
# if no length word after '~' length is 1
|
1968
|
+
d.gsub!(/~([^*[:digit:]])?/){$1 ? "~1#{$1}" : $&} while d=~/~[^*[:digit:]]/
|
1969
|
+
li=d.scan(/\$\{[^\}]+\}|\$[^ ;\$_*^`'+-]+|\([^)\(]*\)|:[^\(,]+\([^)]+\),|:[^,]+,|_[^!]+!|_[^_]__[^?]+\?|v[[:digit:]]+|[<>][[:digit:]]*|\*?[[:digit:].]+|\([VGABLN]:[^)]+\)|~|./)
|
1970
|
+
li.each{|i|
|
1971
|
+
case i
|
1972
|
+
when /^(\*)?([[:digit:].]+)/
|
1973
|
+
tick=$1? $2.to_f : $2.to_f*tbase
|
1974
|
+
if res[-1][0]==:tick
|
1975
|
+
res[-1][1]+=tick
|
1976
|
+
else
|
1977
|
+
res<<[:tick,tick]
|
1978
|
+
end
|
1979
|
+
when "~"
|
1980
|
+
res<<[:tick,tbase] if res[-1][0]==:e
|
1981
|
+
when /^\([VGABLN]:[^)]+/
|
1982
|
+
res<<[:modifier,i]
|
1983
|
+
else
|
1984
|
+
res<<[:e,i]
|
1985
|
+
end
|
1986
|
+
}
|
1987
|
+
line=""
|
1988
|
+
frest=0
|
1989
|
+
(res.size-1).times{|i|
|
1990
|
+
next if res[i][0]!=:modifier
|
1991
|
+
next if res[i+1][0]!=:tick
|
1992
|
+
# if tick after modifier, it must be by tie mark
|
1993
|
+
n=i-1
|
1994
|
+
n-=1 while res[n][0]!=:tick
|
1995
|
+
res[n][1]+=res[i+1][1]
|
1996
|
+
res[i+1][0]=:omit
|
1997
|
+
}
|
1998
|
+
res.each{|mark,data|
|
1999
|
+
case mark
|
2000
|
+
when :e , :modifier
|
2001
|
+
line<<data
|
2002
|
+
when :tick
|
2003
|
+
tick=data.to_i
|
2004
|
+
frest+=data-tick
|
2005
|
+
(tick+=frest;puts "frest:#{frest}" if $DEBUG && $debuglevel>1;frest=0) if frest>1
|
2006
|
+
line<<"*#{tick}"
|
2007
|
+
when :omit
|
2008
|
+
puts "# shift tick data by tie part" if $DEBUG
|
2009
|
+
else
|
2010
|
+
STDERR.puts "tie?"
|
2011
|
+
end
|
2012
|
+
}
|
2013
|
+
p res,line if $DEBUG && $debuglevel>1
|
2014
|
+
line
|
2015
|
+
end
|
2016
|
+
# repeat block analysis: no relation with MIDI format
|
2017
|
+
def repCalc line,macro,tbase
|
2018
|
+
rpt=/\[([^\[\]]*)\] *([[:digit:]]+)/
|
2019
|
+
line.gsub!(rpt){$1*$2.to_i} while line=~rpt
|
2020
|
+
chord=/([^$]|^)\{([^\{\}]*)\}/
|
2021
|
+
line.gsub!(chord){"#{$1}(C:#{$2})"} while line=~chord
|
2022
|
+
line=line.scan(/(\.\$)|(\$([[:alnum:]]+)\(([^\)]+)\))|(.)/).map{|a,b,bname,barg,c|
|
2023
|
+
if a
|
2024
|
+
a
|
2025
|
+
elsif c
|
2026
|
+
c
|
2027
|
+
else
|
2028
|
+
r=funcApply(macro,bname,barg)
|
2029
|
+
r ? r : b
|
2030
|
+
end
|
2031
|
+
}*""
|
2032
|
+
a=line.scan(/\/[^\/]+\/|\[|\]|\.FINE|\.DS|\.DC|\.\$|\.toCODA|\.CODA|\.SKIP|\$\{[^ \{\}]+\}|\$[^ ;\$_*^,\)\(`'\/+-]+|\([^\)]*:|\)|./)
|
2033
|
+
a=a.map{|i|
|
2034
|
+
if i=~/^\/[^\/]+\//
|
2035
|
+
if i=~/\$/
|
2036
|
+
i=i.gsub(/\$\{([^ ;\$_*^,\)\(`'\/+-]+)\}/){macro[$1]}.gsub(/\$([^ ;\$_*^,\)\(`'\/+-]+)/){macro[$1]}
|
2037
|
+
end
|
2038
|
+
multiplet(i,tbase)
|
2039
|
+
else
|
2040
|
+
i
|
2041
|
+
end
|
2042
|
+
}
|
2043
|
+
hs={}
|
2044
|
+
a.each_with_index{|d,i|hs[i]=d}
|
2045
|
+
hs=hs.invert
|
2046
|
+
res=[]
|
2047
|
+
done=[]
|
2048
|
+
dsflag=dcflag=false
|
2049
|
+
counter=0
|
2050
|
+
repcount=0
|
2051
|
+
pointDS=0
|
2052
|
+
rep=[]
|
2053
|
+
while true
|
2054
|
+
countertmp=counter
|
2055
|
+
counter+=1 # next
|
2056
|
+
current=a[countertmp]
|
2057
|
+
puts "#{countertmp}: #{current}, #{rep},done: #{done*","}" if $DEBUG
|
2058
|
+
break if ! current
|
2059
|
+
case current
|
2060
|
+
when "["
|
2061
|
+
if ! done.member?(countertmp)
|
2062
|
+
repcount+=1
|
2063
|
+
rep<<countertmp
|
2064
|
+
done<<countertmp
|
2065
|
+
end
|
2066
|
+
when "]"
|
2067
|
+
if ! done.member?(countertmp)
|
2068
|
+
done<<countertmp
|
2069
|
+
counter=rep.shift+1
|
2070
|
+
end
|
2071
|
+
when ".DS"
|
2072
|
+
counter=pointDS
|
2073
|
+
dsflag=true
|
2074
|
+
when ".DC"
|
2075
|
+
counter=0
|
2076
|
+
dsflag=true
|
2077
|
+
when ".SKIP"
|
2078
|
+
if done.member?(countertmp)
|
2079
|
+
counter=done[-1]
|
2080
|
+
else
|
2081
|
+
done<<countertmp
|
2082
|
+
end
|
2083
|
+
when ".toCODA"
|
2084
|
+
if dsflag
|
2085
|
+
counter=hs[".CODA"]
|
2086
|
+
end
|
2087
|
+
when ".FINE"
|
2088
|
+
if (dsflag || dcflag)
|
2089
|
+
break
|
2090
|
+
end
|
2091
|
+
when /^\$\{([^ \{\}]+)\}/
|
2092
|
+
current=macro[$1]
|
2093
|
+
when /^\$([^ ;]+)/
|
2094
|
+
current=macro[$1]
|
2095
|
+
when ".$"
|
2096
|
+
pointDS=countertmp
|
2097
|
+
when ";"
|
2098
|
+
current=""
|
2099
|
+
else
|
2100
|
+
current
|
2101
|
+
end
|
2102
|
+
res<<current
|
2103
|
+
end
|
2104
|
+
res=(res-["[","]",".CODA",".DS",".DC",".FINE",".toCODA",".$",".SKIP"])*""
|
2105
|
+
res=repCalc(res,macro,tbase) while macro.keys.size>0 && nestsearch(res,macro)
|
2106
|
+
p res if $DEBUG && $debuglevel>1
|
2107
|
+
# 空白
|
2108
|
+
res=res.split.join
|
2109
|
+
res=tie(res,tbase)
|
2110
|
+
end
|
2111
|
+
def loadCalc d
|
2112
|
+
if d=~/\(loadf:(.+)(,(.+))?\)/
|
2113
|
+
file=$1
|
2114
|
+
num=$3 ? $3.to_i : false
|
2115
|
+
[:raw,MidiRead.readtrack(file,num)]
|
2116
|
+
else
|
2117
|
+
[:seq,d]
|
2118
|
+
end
|
2119
|
+
end
|
2120
|
+
def modifierComp t,macro
|
2121
|
+
rawHexPart(t,macro).scan(/\([VGABLN]:[^)]+\)|./).map{|i|
|
2122
|
+
case i
|
2123
|
+
when /^\((V|G):([^)]+)\)/
|
2124
|
+
mode=$1
|
2125
|
+
n=0
|
2126
|
+
v=$2.split(/,/).map{|i|i.split(/ +/)-[""]}.map{|i|
|
2127
|
+
i=["o"] if i==[]
|
2128
|
+
i.map{|c|
|
2129
|
+
c=c.split('') if c=~/^[-\+o]+$/
|
2130
|
+
c
|
2131
|
+
}
|
2132
|
+
}
|
2133
|
+
"(#{mode}:#{v*","})"
|
2134
|
+
when /^\((A|B|L|N):([^)]+)\)/
|
2135
|
+
mode=$1
|
2136
|
+
n=0
|
2137
|
+
v=$2.split(/,/).map{|i|i.split(/ +/)-[""]}.map{|i|
|
2138
|
+
i=["o"] if i==[]
|
2139
|
+
i.map{|c|
|
2140
|
+
c=c.split('') if c=~/^o+$/
|
2141
|
+
c
|
2142
|
+
}
|
2143
|
+
}
|
2144
|
+
"(#{mode}:#{v*","})"
|
2145
|
+
else
|
2146
|
+
i
|
2147
|
+
end
|
2148
|
+
}*""
|
2149
|
+
end
|
2150
|
+
|
2151
|
+
class MmlTracks
|
2152
|
+
attr_accessor :tracknum, :tbase, :rundatas, :rawdatas, :mx
|
2153
|
+
attr_accessor :bpm, :velocity, :octave, :vfuzzy, :data, :infile, :outfile
|
2154
|
+
def initialize tbase=480,pagesep='///',expfile='expfile.txt',cmark=';;'
|
2155
|
+
String.new.setcmark(cmark)
|
2156
|
+
@mx=MidiHex
|
2157
|
+
@rundatas=[]
|
2158
|
+
@rawdatas=[]
|
2159
|
+
@macro={}
|
2160
|
+
@tbase=tbase
|
2161
|
+
@tracks=[]
|
2162
|
+
@fuzzymode=false
|
2163
|
+
@fuzz=false
|
2164
|
+
@expfile=expfile
|
2165
|
+
@pagesep=pagesep
|
2166
|
+
@bpm=120
|
2167
|
+
@velocity=0x40
|
2168
|
+
@octave=:near
|
2169
|
+
@vfuzzy=2
|
2170
|
+
end
|
2171
|
+
def init test,fz
|
2172
|
+
@mx.prepare(@bpm,@tbase,@velocity,@octave,@vfuzzy)
|
2173
|
+
@mx.setfile(@infile)
|
2174
|
+
@mx.setmidiname(@outfile) if @outfile
|
2175
|
+
@mx.setdata(@data) if ! @mx.getdata
|
2176
|
+
(hint;exit) if (! @mx.getdata || ! @mx.getmidiname ) && ! test
|
2177
|
+
settest if test
|
2178
|
+
@tracks=@mx.getdata.tracks(@pagesep)
|
2179
|
+
showtracks if $DEBUG && $debuglevel>1
|
2180
|
+
fuzzy(fz)
|
2181
|
+
end
|
2182
|
+
def settest
|
2183
|
+
@mx.test($testdata,$testmode)
|
2184
|
+
end
|
2185
|
+
def fuzzy fz
|
2186
|
+
@fuzzymode=fz
|
2187
|
+
@fuzz=unirand(fz,@tracks.size) if fz
|
2188
|
+
if fz && (@tbase/fz<8)
|
2189
|
+
STDERR.puts "really?#{"?"*(8*fz/@tbase)}"
|
2190
|
+
end
|
2191
|
+
end
|
2192
|
+
def showtracks
|
2193
|
+
p @tracks
|
2194
|
+
end
|
2195
|
+
def setmacro
|
2196
|
+
@tracks.map{|track|
|
2197
|
+
m,track=macroDef(track)
|
2198
|
+
@macro.merge!(m)
|
2199
|
+
track=modifierComp(track,@macro)
|
2200
|
+
repCalc(track,@macro,tbase)
|
2201
|
+
}.each{|t|
|
2202
|
+
r=loadCalc(t)
|
2203
|
+
if @fuzzymode
|
2204
|
+
n=@fuzz.shift
|
2205
|
+
STDERR.puts "track shift: #{n} tick#{n>1 ? 's' : ''}"
|
2206
|
+
pre="r*#{n} "
|
2207
|
+
else
|
2208
|
+
pre=""
|
2209
|
+
end
|
2210
|
+
case r[0]
|
2211
|
+
when :raw
|
2212
|
+
@rawdatas<<r[1]
|
2213
|
+
when :seq
|
2214
|
+
@rundatas<<pre+r[1]
|
2215
|
+
end
|
2216
|
+
}
|
2217
|
+
p @macro if$DEBUG
|
2218
|
+
@rawdatas.flatten!
|
2219
|
+
open(@expfile,"w"){|f|f.puts @rundatas*"|||"} if @expfile
|
2220
|
+
@tracknum=@rawdatas.size+@rundatas.size
|
2221
|
+
@tracknum=@tracks.size
|
2222
|
+
end
|
2223
|
+
def settracks
|
2224
|
+
@htracks=[]
|
2225
|
+
tc=0
|
2226
|
+
# remember starting position check if data exist before sound
|
2227
|
+
@htracks << @mx.metaTitle + @mx.generaterText + @mx.starttempo.data + @mx.makefraze(@rundatas[0],tc) + @mx.lastrest
|
2228
|
+
@rundatas[1..-1].each{|track|
|
2229
|
+
tc+=1
|
2230
|
+
@htracks<< @mx.restHex + @mx.makefraze(track,tc) + @mx.lastrest
|
2231
|
+
}
|
2232
|
+
end
|
2233
|
+
def pack
|
2234
|
+
@header=@mx.header(1, @tracknum, @tbase)
|
2235
|
+
alla=[@header]+@htracks.map{|t|@mx.trackMake(t)}.flatten
|
2236
|
+
puts alla if $DEBUG
|
2237
|
+
all=alla.map{|i|i.trim("","#")}*""
|
2238
|
+
array=[all.split.join]
|
2239
|
+
@binary = array.pack( "H*" )
|
2240
|
+
end
|
2241
|
+
def make test=false,fz=false
|
2242
|
+
init(test,fz)
|
2243
|
+
setmacro
|
2244
|
+
settracks
|
2245
|
+
pack
|
2246
|
+
end
|
2247
|
+
def save outfile=@mx.getmidiname
|
2248
|
+
# save data. data = MIDI-header + seq-made MIDI-tracks + loaded extra MIDI-tracks.
|
2249
|
+
if outfile==""
|
2250
|
+
print @binary
|
2251
|
+
@rawdatas.each{|i|
|
2252
|
+
print i
|
2253
|
+
}
|
2254
|
+
else
|
2255
|
+
open(outfile,"wb"){|f|
|
2256
|
+
f.write @binary
|
2257
|
+
@rawdatas.each{|i|
|
2258
|
+
f.write i
|
2259
|
+
}
|
2260
|
+
}
|
2261
|
+
end
|
2262
|
+
end
|
2263
|
+
def compile infile,outfile='out.mid',data=""
|
2264
|
+
@infile=infile
|
2265
|
+
@outfile=outfile
|
2266
|
+
@data=data
|
2267
|
+
make
|
2268
|
+
save
|
2269
|
+
end
|
2270
|
+
end
|