MML play

    @@ -37,10 +37,10 @@ var v = 0; for (var j = sounds.length, d; --j >= 0;) v += sounds[j][i]; - v = v < -128 ? 0: (v > 127 ? 255: 128 + v); + v = (v < -1 ? -1: (v >= .9 ? .9: v)) * 128 + 128; da.push(String.fromCharCode(v)); } - return "data:audio/wav," + escape(da.join("")); + return "data:audio/wav," + encodeURLComponent(da.join("")); }, play: function() { @@ -79,7 +79,7 @@ for (var i = (0.5 / p) | 1; (i -= 2) > 0;) v += sin(m * i) / i; v /= 2; - d[o + t] = f.apply(4 * v * eg(t) * vol / pi) | 0; + d[o + t] = f.apply(4 * v * eg(t) * vol / pi); } } else { for (var pv = 0; t < duration; ++t, pv = v) { @@ -87,7 +87,7 @@ for (var i = (0.5 / p) | 1; (i -= 2) > 0;) v += sin(m * i) / i; v /= 2; - d[o + t] = (4 * v * eg(t) * vol / pi) | 0; + d[o + t] = (4 * v * eg(t) * vol / pi); } } }, @@ -105,7 +105,7 @@ for (var i = (0.5 / p) | 0; --i > 0; ) v += sin(2 * i * m) / i; v /= 2; - d[o + t] = f.apply(2 * v * eg(t) * vol / pi) | 0; + d[o + t] = f.apply(2 * v * eg(t) * vol / pi); } } else { for (var pv = 0; t < duration; ++t, pv = v) { @@ -113,7 +113,7 @@ for (var i = (0.5 / p) | 0; --i > 0; ) v += sin(2 * i * m) / i; v /= 2; - d[o + t] = (2 * v * eg(t) * vol / pi) | 0; + d[o + t] = (2 * v * eg(t) * vol / pi); } } }, @@ -129,7 +129,7 @@ initialize: function(sg) { this.sg = sg; this.tempo_ = 120; - this.volume_ = 120; + this.volume_ = .8; this.tone_ = 'square'; this.initializeTone(); }, @@ -253,9 +253,9 @@ this.tempo(param); break; case "v": case "V": - if (param < 0) + if (param < 0 || param > 255) throw syntax_error; - this.volume(param); + this.volume(param / 256); break; case 's': case 'S': if (param < 0) @@ -296,8 +296,13 @@ var getAllTextsInElement = function(tag) { var retval = []; var traverse = function(tag) { - if (tag.localName == "input") - retval.push(tag.getAttribute("value")); + if (tag.localName == "input") { + retval.push(tag.value); + return; + } else if (tag.localName == "textarea") { + retval.push(tag.value); + return; + } var children = tag.childNodes; for (var i = 0; i < children.length; i++) { var child = children.item(i); @@ -319,13 +324,12 @@ var text = getAllTextsInElement(document); var m = null; for (var i = 0; i < text.length; i++) { - m = text[i].match(/mml:{([^}]+)}/); + m = text[i].match(/\{\{\{mml:([^}]+)\}\}\}/); if (m) break; } if (m) { var mmls = m[1].split(/\s*(?:\r|\n|\r\n|;)\s*/i); - console.log(m[1]); var s = []; for (var i = 0; i < mmls.length; ++i) { if (mmls[i] == '')
  • /*
     * @title bookmarklet
     * @description my bookmarklet
     * @include http://*
     * @license MIT License
     */
    
    (function() {
        var SoundPlayer = function() { this.initialize.apply(this, arguments); };
        SoundPlayer.packToUInt32LE = function(l) {
            var f = String.fromCharCode;
            return f(l & 255) + f((l >> 8) & 255) + f((l >> 16) & 255) + f((l >> 24) & 255);
        };
    
        SoundPlayer.prototype = {
            resolution: 0,
    
            initialize: function(resolution) {
                this.resolution = resolution;
            },
    
            generateWavURI: function(sounds) {
                var len = 0;
                for (var i = sounds.length; --i >= 0;)
                    len = Math.max(len, sounds[i].length);
    
                var da;
                var res = SoundPlayer.packToUInt32LE(this.resolution);
                da = [
                        "RIFF" + SoundPlayer.packToUInt32LE(len + 36),
                        "WAVEfmt\x20\x10\x00\x00\x00\x01\x00\x01\x00",
                        res, res, "\x01\x00\x08\x00data",
                        SoundPlayer.packToUInt32LE(len)
                ];
    
                for (var i = 0; i < len; ++i) {
                    var v = 0;
                    for (var j = sounds.length, d; --j >= 0;)
                        v += sounds[j][i];
                    v = (v < -1 ? -1: (v >= .9 ? .9: v)) * 128 + 128;
                    da.push(String.fromCharCode(v));
                }
                return "data:audio/wav," + encodeURLComponent(da.join(""));
            },
    
            play: function() {
                var d = this.generateWavURI(arguments);
                var n = document.createElement("audio");
                n.style.position = "absolute";
                n.style.left = "-1000px";
                document.body.appendChild(n);
                n.setAttribute("src", d);
                n.play();
            }
        };
    
        var SoundGenerator = function() { this.initialize.apply(this, arguments); };
        SoundGenerator.prototype = {
            data: null,
            resolution: null,
            envelopeGenerator: function(t) { return 1; },
            filter: null,
    
            initialize: function(resolution) {
                this.data = [];
                this.resolution = resolution;
            },
    
            generateSquareWave: function(freq, duration, vol) {
                var t = 0, d = this.data, res = this.resolution,
                    eg = this.envelopeGenerator, f = this.filter, o = d.length,
                    p = freq / res;
                var sin = Math.sin, pi = Math.PI;
                d[o + duration - 1] = 0;
                if (f != null) {
                    f.reset();
                    for (var pv = 0; t < duration; ++t, pv = v) {
                        var m = 2 * pi * t * p, v = pv;
                        for (var i = (0.5 / p) | 1; (i -= 2) > 0;)
                            v += sin(m * i) / i;
                        v /= 2;
                        d[o + t] = f.apply(4 * v * eg(t) * vol / pi);
                    }
                } else {
                    for (var pv = 0; t < duration; ++t, pv = v) {
                        var m = 2 * pi * t * p, v = pv;
                        for (var i = (0.5 / p) | 1; (i -= 2) > 0;)
                            v += sin(m * i) / i;
                        v /= 2;
                        d[o + t] = (4 * v * eg(t) * vol / pi);
                    }
                }
            },
    
            generateSawWave: function(freq, duration, vol) {
                var t = 0, d = this.data, res = this.resolution,
                    eg = this.envelopeGenerator, f = this.filter,
                    o = d.length, p = freq / res
                d[o + duration - 1] = 0;
                var sin = Math.sin, pi = Math.PI;
                if (f != null) {
                    f.reset();
                    for (var pv = 0; t < duration; ++t, pv = v) {
                        var m = pi * t * p, v = pv;
                        for (var i = (0.5 / p) | 0; --i > 0; )
                            v += sin(2 * i * m) / i;
                        v /= 2;
                        d[o + t] = f.apply(2 * v * eg(t) * vol / pi);
                    }
                } else {
                    for (var pv = 0; t < duration; ++t, pv = v) {
                        var m = pi * t * p, v = pv;
                        for (var i = (0.5 / p) | 0; --i > 0; )
                            v += sin(2 * i * m) / i;
                        v /= 2;
                        d[o + t] = (2 * v * eg(t) * vol / pi);
                    }
                }
            },
    
            generateSilence: function(duration) {
                var d = this.data, j;
                d[d.length + duration - 1] = 0;
            }
        };
    
        var MusicBuilder = function() { this.initialize.apply(this, arguments); };
        MusicBuilder.prototype = {
            initialize: function(sg) {
                this.sg = sg;
                this.tempo_ = 120;
                this.volume_ = .8;
                this.tone_ = 'square';
                this.initializeTone();
            },
    
            initializeTone: function() {
                var self = this;
                switch (this.tone_) {
                case 'saw':
                    this.sound = function(freq, duration) {
                        this.sg.generateSawWave(freq, duration, self.volume_);
                    };
                    break;
                case 'square':
                    this.sound = function(freq, duration) {
                        this.sg.generateSquareWave(freq, duration, self.volume_);
                    };
                    break;
                }
            },
    
            calculateDuration: function(len) {
                return (this.sg.resolution * 240 / this.tempo_) / len;
            },
    
            tone: function(v) {
                this.tone_ = v;
                this.initializeTone();
            },
    
            tempo: function(v) {
                this.tempo_ = v;
            },
    
            note: function(n, len) {
                var f = 55 << (n / 12);
                f *= Math.pow(2, (n % 12) / 12);
                this.sound(f, this.calculateDuration(len));
            },
    
            rest: function(len) {
                this.sg.generateSilence(this.calculateDuration(len));
            },
    
            envelope: function(eg) {
                this.sg.envelopeGenerator = eg;
            },
    
            parseMML: function(mml) {
                var regex = /([><])|([LMOSTVS@])\s*(\d*)|([ABCDEFG]([#+-]?)|R)\s*(?:(\d+)\s*|(\.+)\s*){0,2}|\s+/gi;
                var m;
                var pitch_base = 36, default_len = 4;
                var syntax_error = { message: "Syntax error" };
                var invalid_parameter = { message: "Invalid parameter" };
                var pitch_table = {
                    "a":  0, "A":  0,
                    "b":  2, "B":  2,
                    "c": -9, "C": -9,
                    "d": -7, "D": -7,
                    "e": -5, "E": -5,
                    "f": -4, "F": -4,
                    "g": -2, "G": -2
                };
                var envelope_cycle = 1000;
                var envelopes = [
                    function(t) { return 1; },
                    function(t) { return Math.max(0, 1 - t / envelope_cycle); },
                    function(t) { return Math.min(1, t / envelope_cycle); },
                    function(t) { return 1 - (t % envelope_cycle) / envelope_cycle; },
                    function(t) { return (t % envelope_cycle) / envelope_cycle; },
                    function(t) { return Math.abs((t % (envelope_cycle * 2)) - envelope_cycle) / envelope_cycle; },
                    function(t) { return 1 - Math.abs((t % (envelope_cycle * 2)) - envelope_cycle) / envelope_cycle; },
                    function(t) { return t < envelope_cycle ? 1 - t / envelope_cycle: 1; },
                    function(t) { return t < envelope_cycle ? t / envelope_cycle: 0; }
                ];
    
                var envelope_table = [
                    envelopes[0],
                    envelopes[1],
                    envelopes[1],
                    envelopes[1],
                    envelopes[8],
                    envelopes[8],
                    envelopes[8],
                    envelopes[8],
                    envelopes[3],
                    envelopes[1],
                    envelopes[5],
                    envelopes[7],
                    envelopes[4],
                    envelopes[2],
                    envelopes[6],
                    envelopes[8]
                ];
    
                tone_table = [ 'square', 'saw' ];
                while (m = regex.exec(mml)) {
                    switch (m[1]) {
                    case "<":
                        pitch_base += 12;
                        continue;
                    case ">":
                        pitch_base -= 12;
                        continue;
                    }
                    if (m[2]) {
                        var param = m[3] == '' ? -1: parseInt(m[3]);
                        switch (m[2]) {
                        case "l": case "L":
                            if (param < 0)
                                throw syntax_error;
                            default_len = param;
                            break;
                        case "o": case "O":
                            if (param < 0)
                                throw syntax_error;
                            pitch_base = (param - 1) * 12;
                            break;
                        case "t": case "T":
                            if (param < 0)
                                throw syntax_error;
                            this.tempo(param);
                            break;
                        case "v": case "V":
                            if (param < 0 || param > 255)
                                throw syntax_error;
                            this.volume(param / 256);
                            break;
                        case 's': case 'S':
                            if (param < 0)
                                throw syntax_error;
                            if (param >= envelope_table.length)
                                throw invalid_parameter; 
                            this.envelope(envelope_table[param]);
                            break;
                        case 'm': case 'M':
                            if (param < 0)
                                throw syntax_error;
                            if (param < 1)
                                throw invalid_parameter;
                            envelope_cycle = this.sg.resolution * param / 8000;
                            break;
                        case '@':
                            if (param < 0)
                                throw syntax_error;
                            if (param >= tone_table.length)
                                throw invalid_parameter; 
                            this.tone(tone_table[param]);
                        }
                        continue;
                    }
                    var l = typeof(m[6]) == 'undefined' ? default_len: parseInt(m[6]),
                        a = typeof(m[7]) == 'undefined' ? 0: m[7].length,
                        d = l * (1 << a) / ((1 << (a + 1)) - 1);
                    if (m[4]) {
                        this.note(pitch_base + pitch_table[m[4].charAt(0)]
                            + (m[5] ? ("#+".indexOf(m[5]) < 0 ? -1: 1): 0), d);
                    } else {
                        this.rest(d);
                    }
                }
            }
        };
    
        var getAllTextsInElement = function(tag) {
            var retval = [];
            var traverse = function(tag) {
                if (tag.localName == "input") {
                    retval.push(tag.value);
                    return;
                } else if (tag.localName == "textarea") {
                    retval.push(tag.value);
                    return;
                }
                var children = tag.childNodes;
                for (var i = 0; i < children.length; i++) {
                    var child = children.item(i);
                    switch (child.nodeType) {
                    case 1:
                        traverse(child);
                        break;
                    case 3:
                        if (child.nodeValue)
                            retval.push(child.nodeValue);
                        break;
                    }
                }
            };
            traverse(tag);
            return retval;
        };
    
        var text = getAllTextsInElement(document);
        var m = null;
        for (var i = 0; i < text.length; i++) {
            m = text[i].match(/\{\{\{mml:([^}]+)\}\}\}/);
            if (m)
                break;
        }
        if (m) {
            var mmls = m[1].split(/\s*(?:\r|\n|\r\n|;)\s*/i);
            var s = [];
            for (var i = 0; i < mmls.length; ++i) {
                if (mmls[i] == '')
                  continue;
                var m = new MusicBuilder(new SoundGenerator(16000));
                m.parseMML(mmls[i]);
                s.push(m.sg.data);
            }
            var sp = new SoundPlayer(16000);
            sp.play.apply(sp, s);
        }
    })();
    
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2010/05/18 17:19:42 - 2010-05-18
  2. 2010/05/18 15:59:52 - 2010-05-18
  3. 2010/05/18 15:58:48 - 2010-05-18
  4. 2010/05/18 15:52:41 - 2010-05-18
  5. 2010/05/18 15:52:09 - 2010-05-18
  6. 2010/05/18 15:51:24 - 2010-05-18
  7. 2010/05/18 15:39:18 - 2010-05-18
  8. 2010/05/18 15:38:00 - 2010-05-18
  9. 2010/05/18 15:36:21 - 2010-05-18
  10. 2010/05/18 15:27:07 - 2010-05-18