root/lang/actionscript/flmml/trunk/src/com/txt_nifty/sketch/flmml/MML.as @ 27528

Revision 27173, 26.2 kB (checked in by tekisuke, 5 years ago)

lang/actionscript/flmml/: ダブルバッファリングが安定した!と思う。

Line 
1package com.txt_nifty.sketch.flmml {
2    import flash.events.EventDispatcher;
3    import flash.utils.*;
4    import mx.utils.StringUtil;
5
6    public class MML extends EventDispatcher {
7        protected var m_sequencer:MSequencer;
8        protected var m_tracks:Array;
9        protected var m_string:String;
10        protected var m_trackNo:int;
11        protected var m_octave:int;
12        protected var m_relativeDir:Boolean; //
13        protected var m_velocity:int;        // default velocity
14        protected var m_length:int;          // default length
15        protected var m_tempo:int;
16        protected var m_letter:int;
17        protected var m_keyoff:int;
18        protected var m_gate:int;
19        protected var m_maxGate:int;
20        protected var m_form:int;
21        protected var m_noteShift:int;
22        protected var m_warning:String;
23        protected var m_maxPipe:int;
24        protected static var MAX_PIPE:int = 3;
25
26        public function MML() {
27            m_sequencer = new MSequencer();
28            var self:MML = this;
29            m_sequencer.addEventListener(MMLEvent.COMPLETE, function(e:MMLEvent):void {
30                    m_sequencer.stop();
31                    self.dispatchEvent(new MMLEvent(MMLEvent.COMPLETE));
32                });
33            m_sequencer.addEventListener(MMLEvent.BUFFERING, function(e:MMLEvent):void {
34                    self.dispatchEvent(new MMLEvent(MMLEvent.BUFFERING, false, false, 0, 0, e.progress));
35                });
36        }
37
38        public function set onSignal(func:Function):void {
39            // ex) function func(globalTick:uint, event:int):void {}
40            m_sequencer.onSignal = func;
41        }
42
43        public function setSignalInterval(interval:int):void {
44            m_sequencer.setSignalInterval(interval);
45        }
46
47        public function getWarnings():String {
48            return m_warning;
49        }
50
51        protected function warning(warnId:int, str:String):void {
52            m_warning += MWarning.getString(warnId, str) +"\n";
53        }
54
55        protected function len2tick(len:int):int {
56            if (len == 0) return m_length;
57            return 384/len;
58        }
59
60        protected function note(noteNo:int):void {
61            //trace("note"+noteNo);
62            noteNo += m_noteShift + getKeySig();
63            var len:int;
64            len = getUInt(0);
65            var tick:int = len2tick(len);
66            tick = getDot(tick);
67            var keyon:int = (m_keyoff == 0) ? 0 : 1;
68            m_keyoff = 1;
69            if (getChar() == '&') { // tie
70                next();
71                m_keyoff = 0;
72            }
73            m_tracks[m_trackNo].recNote(noteNo + m_octave*12, tick, m_velocity, keyon, m_keyoff);
74        }
75
76        protected function rest():void {
77            //trace("rest");
78            var len:int;
79            len = getUInt(0);
80            var tick:int = len2tick(len);
81            tick = getDot(tick);
82            m_tracks[m_trackNo].recRest(tick);
83        }
84
85        protected function atmark():void {
86            var c:String = getChar();
87            var o:int = 1, a:int = 0, d:int = 64, s:int = 32, r:int = 0;
88            switch(c) {
89            case 'v': // Volume
90                next();
91                m_velocity = getUInt(m_velocity);
92                if (m_velocity > 127) m_velocity = 127;
93                break;
94            case 'e': // Envelope
95                next();
96                o = getUInt(o);
97                if (getChar() == ',') next();
98                a = getUInt(a);
99                if (getChar() == ',') next();
100                d = getUInt(d);
101                if (getChar() == ',') next();
102                s = getUInt(s);
103                if (getChar() == ',') next();
104                r = getUInt(r);
105                //trace("A"+a+",D"+d+",S"+s+",R"+r);
106                m_tracks[m_trackNo].recEnvelope(o, a, d, s, r);
107                break;
108            case 'n': // Noise frequency
109                next();
110                o = getUInt(0);
111                if (o < 0 || o > 127) o = 0;
112                m_tracks[m_trackNo].recNoiseFreq(o);
113                break;
114            case 'w': // pulse Width modulation
115                next();
116                o = getUInt(50);
117                if (o < 1) o = 1;
118                if (o > 99) o = 99;
119                m_tracks[m_trackNo].recPWM(o);
120                break;
121            case 'p': // Pan
122                next();
123                o = getUInt(64);
124                if (o < 1) o = 1;
125                if (o > 127) o = 127;
126                m_tracks[m_trackNo].recPan(o);
127                break;
128            case '\'': // formant filter
129                next();
130                o = m_string.indexOf('\'', m_letter);
131                if (o >= 0) {
132                    var vstr:String = m_string.substring(m_letter, o);
133                    var vowel:int = 0;
134                    switch(vstr) {
135                    case 'a': vowel = MFormant.VOWEL_A; break;
136                    case 'e': vowel = MFormant.VOWEL_E; break;
137                    case 'i': vowel = MFormant.VOWEL_I; break;
138                    case 'o': vowel = MFormant.VOWEL_O; break;
139                    case 'u': vowel = MFormant.VOWEL_U; break;
140                    default: vowel = -1; break;
141                    }
142                    m_tracks[m_trackNo].recFormant(vowel);
143                    m_letter = o + 1;
144                }
145                break;
146            case 'd': // Detune
147                next();
148                o = getSInt(0);
149                m_tracks[m_trackNo].recDetune(o);
150                break;
151            case 'l': // Low frequency oscillator (LFO)
152                {
153                    var dp:int = 0, wd:int = 0, fm:int = 1, rv:int = 1, dl:int = 0, tm:int = 0, cn:int = 0;
154                    next();
155                    dp = getUInt(dp);
156                    if (getChar() == ',') next();
157                    wd = getUInt(wd);
158                    if (getChar() == ',') {
159                        next();
160                        if (getChar() == '-') { rv = -1; next(); }
161                        fm = (getUInt(fm) + 1) * rv;
162                        if (getChar() == ',') {
163                            next();
164                            dl = getUInt(dl);
165                            if (getChar() == ',') {
166                                next();
167                                tm = getUInt(tm);
168                            }
169                        }
170                    }
171                    //trace("DePth"+dp+",WiDth"+wd+",ForM"+fm+",DeLay"+dl+",TiMe"+tm);
172                    m_tracks[m_trackNo].recLFO(dp, wd, fm, dl, tm);
173                }
174                break;
175            case 'f': // Filter
176                {
177                    var swt:int = 0, amt:int = 0, frq:int = 0, res:int = 0;
178                    next();
179                    swt = getSInt(swt);
180                    if (getChar() == ',') {
181                        next();
182                        amt = getSInt(amt);
183                        if (getChar() == ',') {
184                            next();
185                            frq = getUInt(frq);
186                            if (getChar() == ',') {
187                                next();
188                                res = getUInt(res);
189                            }
190                        }
191                    }
192                    m_tracks[m_trackNo].recLPF(swt, amt, frq, res);
193                }
194                break;
195            case 'q': // gate time 2
196                next();
197                m_tracks[m_trackNo].recGate2(getUInt(2) * 2); // '*2' according to TSSCP
198                break;
199                case 'i': // Input
200                {
201                    var sens:int = 0;
202                    next();
203                    sens = getUInt(sens);
204                    if (getChar() == ',') {
205                        next();
206                        a = getUInt(a);
207                        if (a > m_maxPipe) a = m_maxPipe;
208                    }
209                    m_tracks[m_trackNo].recInput(sens, a);
210                }
211                // @i[n],[m]   m:pipe no
212                // if (n == 0) off
213                // else sensitivity = n (max:8)
214                break;
215            case 'o': // Output
216                {
217                    var mode:int = 0;
218                    next();
219                    mode = getUInt(mode);
220                    if (getChar() == ',') {
221                        next();
222                        a = getUInt(a);
223                        if (a > m_maxPipe) {
224                            m_maxPipe = a;
225                            if (m_maxPipe >= MAX_PIPE) m_maxPipe = a = MAX_PIPE;
226                        }
227                    }
228                    m_tracks[m_trackNo].recOutput(mode, a);
229                }
230                // @o[n],[m]   m:pipe no
231                // if (n == 0) off
232                // if (n == 1) overwrite
233                // if (n == 2) add
234                break;
235            default:
236                m_form = getUInt(m_form);
237                m_tracks[m_trackNo].recForm(m_form);
238                break;
239            }
240        }
241
242        protected function firstLetter():void {
243            var c:String = getCharNext();
244            var c0:String;
245            var i:int;
246            switch(c) {
247            case "c": note(0);  break;
248            case "d": note(2);  break;
249            case "e": note(4);  break;
250            case "f": note(5);  break;
251            case "g": note(7);  break;
252            case "a": note(9);  break;
253            case "b": note(11); break;
254            case "r": rest(); break;
255            case "o": // Octave
256                m_octave = getUInt(m_octave);
257                if (m_octave < -2) m_octave = -2;
258                if (m_octave >  8) m_octave =  8;
259                break;
260            case "v": // Volume
261                m_velocity = getUInt((m_velocity-7)/8) * 8 + 7;
262                if (m_velocity < 0)   m_velocity = 0;
263                if (m_velocity > 127) m_velocity = 127;
264                break;
265            case "l": // Length
266                m_length = len2tick(getUInt(0));
267                m_length = getDot(m_length);
268                break;
269            case "t": // Tempo
270                m_tempo = getUInt(m_tempo);
271                if (m_tempo == 0) m_tempo = 1;
272                m_tracks[MTrack.TEMPO_TRACK].recTempo(m_tracks[m_trackNo].getRecGlobalTick(), m_tempo);
273                break;
274            case "q": // gate time (rate)
275                m_gate = getUInt(m_gate);
276                m_tracks[m_trackNo].recGate(m_gate / m_maxGate);
277                break;
278            case "<" : // octave shift
279                if (m_relativeDir) m_octave++; else m_octave--;
280                break;
281            case ">": // octave shift
282                if (m_relativeDir) m_octave--; else m_octave++;
283                break;
284            case ";": // end of track
285                if (m_tracks[m_trackNo].getNumEvents() > 0) {
286                    m_tracks[++m_trackNo] = createTrack();
287                }
288                break;
289            case "@":
290                atmark();
291                break;
292            case "x":
293                m_tracks[m_trackNo].recVolMode(getUInt(1));
294                break;
295            case "n":
296                c0 = getChar();
297                if (c0 == "s") { // Note Shift
298                    next();
299                    m_noteShift = getSInt(m_noteShift);
300                }
301                else
302                    warning(MWarning.UNKNOWN_COMMAND, c + c0);
303                break;
304            default:
305                {
306                    var cc:int = c.charCodeAt(0);
307                    if (cc < 128)
308                        warning(MWarning.UNKNOWN_COMMAND, c);
309                }
310                break;
311            }
312        }
313
314        protected function getCharNext():String {
315            return (m_letter < m_string.length) ? m_string.charAt(m_letter++) : '';
316        }
317
318        protected function getChar():String {
319            return (m_letter < m_string.length) ? m_string.charAt(m_letter) : '';
320        }
321
322        protected function next(i:int = 1):void {
323            m_letter += 1;
324        }
325
326        protected function getKeySig():int {
327            var k:int = 0;
328            var f:int = 1;
329            while(f) {
330                var c:String = getChar();
331                switch(c) {
332                case "+": case "#": k++; next(); break;
333                case "-":           k--; next(); break;
334                default: f = 0; break;
335                }
336            }
337            return k;
338        }
339
340        protected function getUInt(def:int):int {
341            var ret:int = 0;
342            var l:int = m_letter;
343            var f:int = 1;
344            while(f) {
345                var c:String = getChar();
346                switch(c) {
347                case '0': ret = ret * 10 + 0; next(); break;
348                case '1': ret = ret * 10 + 1; next(); break;
349                case '2': ret = ret * 10 + 2; next(); break;
350                case '3': ret = ret * 10 + 3; next(); break;
351                case '4': ret = ret * 10 + 4; next(); break;
352                case '5': ret = ret * 10 + 5; next(); break;
353                case '6': ret = ret * 10 + 6; next(); break;
354                case '7': ret = ret * 10 + 7; next(); break;
355                case '8': ret = ret * 10 + 8; next(); break;
356                case '9': ret = ret * 10 + 9; next(); break;
357                default: f = 0; break;
358                }
359            }
360            return (m_letter == l) ? def : ret;
361        }
362
363        protected function getSInt(def:int):int {
364            var c:String = getChar();
365            var s:int = 1;
366            if      (c == '-') { s = -1; next(); }
367            else if (c == '+') next();
368            return getUInt(def) * s;
369        }
370
371        protected function getDot(tick:int):int {
372            var c:String = getChar();
373            var intick:int = tick;
374            while(c == '.') {
375                next();
376                intick /= 2;
377                tick += intick;
378                c = getChar();
379            }
380            return tick;
381        }
382
383        public function createTrack():MTrack {
384            m_octave = 4;
385            m_velocity = 100;
386            m_noteShift = 0;
387            return new MTrack();
388        }
389
390        protected function begin():void {
391            m_letter = 0;
392        }
393
394        protected function process():void {
395            begin();
396            while(m_letter < m_string.length) {
397                firstLetter();
398            }
399        }
400
401        protected function processRepeat():void {
402            m_string = m_string.toLowerCase();
403            begin();
404            var repeat:Array = new Array();
405            var origin:Array = new Array();
406            var start:Array = new Array();
407            var last:Array = new Array();
408            var nest:int = -1;
409            while(m_letter < m_string.length) {
410                var c:String = getCharNext();
411                switch(c) {
412                case '/':
413                    if (getChar() == ':') {
414                        next();
415                        origin[++nest] = m_letter - 2;
416                        repeat[nest] = getUInt(2);
417                        start[nest] = m_letter;
418                        last[nest] = -1;
419                    }
420                    else if (nest >= 0) {
421                        last[nest] = m_letter - 1;
422                        m_string = m_string.substring(0, m_letter-1) + m_string.substring(m_letter);
423                        m_letter--;
424                    }
425                    else {
426                    }
427                    break;
428                case ':':
429                    if (getChar() == '/' && nest >= 0) {
430                        next();
431                        var contents:String = m_string.substring(start[nest], m_letter - 2);
432                        var newstr:String = m_string.substring(0, origin[nest]);
433                        for (var i:int = 0; i < repeat[nest]; i++) {
434                            if (i < repeat[nest]-1 || last[nest] < 0) newstr += contents;
435                            else newstr += m_string.substring(start[nest], last[nest]);
436                        }
437                        var l:int = newstr.length;
438                        newstr += m_string.substring(m_letter);
439                        m_string = newstr;
440                        m_letter = l;
441                        nest--;
442                    }
443                    break;
444                default:
445                    break;
446                }
447            }
448            if (nest >= 0) warning(MWarning.UNCLOSED_REPEAT, "");
449        }
450
451        protected function getIndex(idArr:Array, id:String):int {
452            for(var i:int = 0; i < idArr.length; i++)
453                if (((String)(idArr[i])) == id) return i;
454            return -1;
455        }
456
457        protected function insertLenOrder(idArr:Array, valArr:Array, id:String, val:String):void {
458            var len:int = id.length;
459            var i:int;
460            for(i = 0; i < idArr.length; i++) {
461                if (len >= ((String)(idArr[i])).length) break;
462            }
463            idArr.splice(i, 0, id);
464            valArr.splice(i, 0, val);
465        }
466
467        protected function replaceMacro(idArr:Array, valArr:Array):void {
468            var i:int;
469            for(i = 0; i < idArr.length; i++) {
470                var id:String = idArr[i];
471                if (m_string.substr(m_letter, id.length) == idArr[i]) {
472                    //trace("["+m_string.substr(m_letter, id.length)+"]");
473                    m_string = m_string.substring(0, m_letter-1) + valArr[i] + m_string.substring(m_letter + id.length);
474                    m_letter += id.length;
475                    break;
476                }
477            }
478        }
479
480        protected function macroInMacro(str:String, idArr:Array, valArr:Array, id0:String):String {
481            var idx:int;
482            for(var i:int = 0; i < idArr.length; i++) {
483                var id:String = "$"+idArr[i];
484                //trace("id:"+id);
485                idx = str.indexOf(id);
486                while(idx >= 0) {
487                    str = str.substring(0, idx) + valArr[i] + str.substring(idx + id.length);
488                    idx = str.indexOf(id, idx);
489                }
490            }
491            // recursive call is prevented.
492            id0 = "$" + id0;
493            idx = str.indexOf(id0);
494            while(idx >= 0) {
495                str = str.substring(0, idx) + str.substring(idx + id0.length);
496                idx = str.indexOf(id0, idx);
497                warning(MWarning.RECURSIVE_MACRO, id0);
498            }
499            return str;
500        }
501
502        protected function processMacro():void {
503            begin();
504            var top:int = 1;
505            var idArr:Array = new Array();
506            var valArr:Array = new Array();
507            var last:int;
508            // [a-zA-Z][a-zA-Z0-9#\+\(\)_]*
509            // ex.) $Am    =/:4a<ce>a:/;
510            //      $EonG# =/:4g#b<e>b:/;
511            //      $Gm    =/:4gb-<d>b-:/;
512            //      $DonF# =/:4f#a<d>a:/;
513            //      $Am $EonG# $Gm $DonF#
514            while(m_letter < m_string.length) {
515                var c:String = getCharNext();
516                switch(c) {
517                case '$':
518                    // macro definition?
519                    if (top) {
520                        last = m_string.indexOf(";", m_letter);
521                        if (last > m_letter) {
522                            var macro:String = m_string.substring(m_letter, last);
523                            var token:Array = macro.split("=");
524                            if (token.length >= 2 &&
525                                token[0].length >= 1) {
526                                var id:Array = token[0].match("[a-zA-Z_][a-zA-Z_0-9#\+\(\)]*");
527                                //trace("token:"+token[0]+"/"+id);
528                                if (id != null) {
529                                    //trace("macro:$"+id[0]+"="+token[1]);
530                                    if (id[0].length > 0) {
531                                        m_string = remove(m_string, --m_letter, last);
532                                        var idx:int = getIndex(idArr, id[0]);
533                                        // first definition
534                                        if (idx < 0) {
535                                            token[1] = macroInMacro(token[1], idArr, valArr, id[0]);
536                                            //trace("define $"+id[i]+"="+token[1]);
537                                            insertLenOrder(idArr, valArr, id[0], token[1]);
538                                        }
539                                        // macro redefinition
540                                        else {
541                                            token[1] = macroInMacro(token[1], idArr, valArr, id[0]);
542                                            //trace("redefine $"+id[i]+"="+token[1]);
543                                            valArr[idx] = token[1];
544                                        }
545                                    }
546                                }
547                            }
548                            // macro use
549                            else {
550                                replaceMacro(idArr, valArr);
551                                top = 0;
552                            }
553                        }
554                        // macro use
555                        else {
556                            replaceMacro(idArr, valArr);
557                            top = 0;
558                        }
559                    }
560                    // macro use
561                    else {
562                        replaceMacro(idArr, valArr);
563                        top = 0;
564                    }
565                    break;
566                case ';':
567                    top = 1;
568                    break;
569                default:
570                    if (!StringUtil.isWhitespace(c) && c != ' ') top = 0;
571                    break;
572                }
573            }
574            //
575            var exp:RegExp = /[\n\r\f]*#OCTAVE\s+REVERSE\s*[\n\r\f]/;
576            if (m_string.match(exp)) {
577                m_string = m_string.replace(exp, "");
578                m_relativeDir = false;
579            }
580        }
581
582        protected function processComment(str:String):void {
583            m_string = str;
584            begin();
585            var commentStart:int = -1;
586            while(m_letter < m_string.length) {
587                var c:String = getCharNext();
588                switch(c) {
589                case '/':
590                    if (getChar() == '*') {
591                        if (commentStart < 0) commentStart = m_letter - 1;
592                        next();
593                    }
594                    break;
595                case '*':
596                    if (getChar() == '/') {
597                        if (commentStart >= 0) {
598                            m_string = remove(m_string, commentStart, m_letter);
599                            m_letter = commentStart;
600                            commentStart = -1;
601                        }
602                        else {
603                            warning(MWarning.UNOPENED_COMMENT, "");
604                        }
605                    }
606                    break;
607                default:
608                    break;
609                }
610            }
611            if (commentStart >= 0) warning(MWarning.UNCLOSED_COMMENT, "");
612        }
613
614        static public function removeWhitespace(str:String):String {
615            return str.replace(new RegExp("[  \n\r\t\f]+","g"),"");
616        }
617
618        static public function remove(str:String, start:int, end:int):String {
619            return str.substring(0, start) + str.substring(end+1);
620        }
621
622        public function play(str:String):void {
623            if (m_sequencer.isPaused()) {
624                m_sequencer.play();
625                return;
626            }
627            m_sequencer.disconnectAll();
628            m_tracks = new Array();
629            m_tracks[0] = createTrack();
630            m_tracks[1] = createTrack();
631            m_warning = new String();
632
633            m_trackNo = MTrack.FIRST_TRACK;
634            m_octave = 4;
635            m_relativeDir = true;
636            m_velocity = 100;
637            m_length = len2tick(4);
638            m_tempo  = 120;
639            m_keyoff = 1;
640            m_gate = 15;
641            m_maxGate = 16;
642            m_form = MOscillator.PULSE;
643            m_noteShift = 0;
644            m_maxPipe = 0;
645
646            processComment(str);
647            //trace(m_string+"\n\n");
648            processMacro();
649            //trace(m_string);
650            m_string = removeWhitespace(m_string);
651            processRepeat();
652            //trace(m_string);
653            process();
654
655            // omit
656            if (m_tracks[m_tracks.length-1].getNumEvents() == 0) m_tracks.pop();
657
658            // conduct
659            m_tracks[MTrack.TEMPO_TRACK].conduct(m_tracks);
660
661            // post process
662            for(var i:int = MTrack.TEMPO_TRACK; i < m_tracks.length; i++) {
663                m_tracks[i].recRest(384);
664                m_tracks[i].recClose();
665                m_tracks[i].recRest(96);
666                m_tracks[i].recEOT();
667                m_sequencer.connect(m_tracks[i]);
668            }
669            m_sequencer.createPipes(m_maxPipe+1);
670
671            // dispatch event
672            dispatchEvent(new MMLEvent(MMLEvent.COMPILE_COMPLETE, false, false, 0, 0));
673
674            // play start
675            m_sequencer.play();
676        }
677
678        public function stop():void {
679            m_sequencer.stop();
680        }
681
682        public function pause():void {
683            m_sequencer.pause();
684        }
685
686        public function resume():void {
687            m_sequencer.play();
688        }
689
690        public function setMasterVolume(vol:int):void {
691            m_sequencer.setMasterVolume(vol);
692        }
693
694        public function getGlobalTick():uint {
695            return m_sequencer.getGlobalTick();
696        }
697
698        public function isPlaying():Boolean {
699            return m_sequencer.isPlaying();
700        }
701
702        public function isPaused():Boolean {
703            return m_sequencer.isPaused();
704        }
705    }
706}
Note: See TracBrowser for help on using the browser.