⌈⌋ branch:  Bitrhythm


Artifact Content

Artifact 52ee08afabe25897b3758460b76cc4c27dec66dfc3f28b68e1c8f6b77751988b:



<bitrhythm>

<div>
    <vstack id="header-playback">
        <hstack>
            <div class="ml-2">
                <button type="button" class="btn btn-primary w-1/10 ml-2 mt-1" onclick={addDial}>+ Dial</button>
                <button type="button" class="btn btn-primary w-1/10 ml-2 mt-1" onclick={addNumber}>+ Number</button>
                <!-- <button type="button" class="btn btn-primary w-1/10 ml-2 mt-1" onclick={addSample}>+ Sample File</button> -->
                <button type="button" class="btn btn-primary w-1/10 ml-2 mt-1" onclick={addSampleURL}>+ Sample URL</button>
            </div>

            <div class="ml-2" >
                <label for="tempo-value">Tempo / Ticks</label><br>
                <input type="text" id="tempo-value" value={state.tempo} style="width: 150px" onkeyup={ editTempo }/>
                <input type="text"  class="mt-2" id="tick-value" value={state.ticks} style="width: 150px" onkeyup={editTicks}/>
            </div>
            <div class="ml-2" style="min-width: 250px;">
                <label for="duration">Bars / Ticks / Seconds</label><br>
                <div id="duration" ></div>
            </div>
        </hstack>

        <div class="mt-2 ml-2" >
            <button type="button" class="btn btn-primary w-1/10 ml-2 mt-1"  onclick={play}>Play</button>
            <button type="button" class="btn btn-primary ml-2" onclick={save}>Save</button>
            <button type="button" class="btn btn-primary ml-2" onclick={reset}>Reset</button>
            <button type="button" class="btn btn-primary ml-2" onclick={reload}>Window Reload</button>
            <button type="button" class="btn btn-primary ml-2" onclick={download}>Save File</button>

            <input class="ml-1" name="edit-mode" id="edit-mode" type="checkbox"/>
            <label for="edit-mode">Edit</label>
            <input class="ml-1" name="load-mode" id="load-mode" type="checkbox"/>
            <label for="load-mode">Execute Transition</label>
            <input class="ml-1" name="load-mode" id="redo" type="checkbox"/>
            <label for="redo">Redo Bar</label>
        </div>

        <vstack id="samples-block">
            <div each={ key, index in state.samples} >
                <div if={ state.samples && state.samples[index] }>
                    <sample  setsample={setSample} rmsample={rmSample} samples={state.samples} ti={index + 1}></sample>
                </div>
            </div>
        </vstack>

        <hstack style="margin-top: 16px">
            <div each={ key, index in state.dials}>
                <dial rmdial={rmDial}  v={state.dials[index]} ti={index + 1}></dial>
            </div>
        </hstack>

        <hstack>
            <div each={ key, index in state.numbers}>
                <number rmnumber={rmNumber} v={state.numbers[index]} ti={index + 1}></number>
            </div>
        </hstack>

    </vstack>

    <div id="cued" class="p-2" style="color: white !important; height: 32px; font-size: 24px;"></div>
    <div id="error" class="p-2" style="color: yellow !important; height: 32px; font-size: 12px;"></div>
    <div id="canvas-container" style="position: relative;">
        <div id="p5" style="position: absolute; width: 100%; background: black"></div>
        <canvas id="visual" style="position: absolute; width: 100%; background: black;"></canvas>
        <div id="code" style="position: absolute;"></div>
    </div>
    
</div>

<style>
:host {
    margin-top: 4vh;
}
</style>

<script>
var oldCode = "";
var oldPatterns = [];

Mousetrap.stopCallback = function(e, element, combo) {
    return false;
}

Mousetrap.bind(['f9'], function(e) {
    if (document.getElementById('edit-mode').checked) {
        document.getElementById('edit-mode').checked = false;
    } else {
        document.getElementById('edit-mode').checked = true;
    }

    return false;
});

Mousetrap.bind(['ctrl+1'], function(e) {

    if (document.getElementById('redo').checked) {
        document.getElementById('redo').checked = false;
    } else {
        document.getElementById('redo').checked = true;
    }

    return false;
});

Mousetrap.bind(['f10'], function(e) {
    if (document.getElementById('load-mode').checked) {
        document.getElementById('load-mode').checked = false;
    } else {
        document.getElementById('load-mode').checked = true;
    }
    return false;
});

Mousetrap.bind(['ctrl+0'], function(e) {
    $("#samples-block").toggle();
    return false;
});


for (let i = 1; i <= 8; i++) {
    Mousetrap.bind(['f' + i], function(e) {
        if (i <= samples.length) {
            p(i - 1);
        }
        return false;
    });
}

// var audio = new Audio();
// audio.loop = true;
const actx = Tone.context;
const dest = actx.createMediaStreamDestination();
const recorder = new MediaRecorder(dest.stream);
let chunks = [];
var sampleURL = "";
var sam;
this.props = opts;

this.state = {
    mem: {},
    dials: [],
    numbers: [],
    samples: [],
    tempo: 120,
    ticks: 16,
}

async function copyTextToClipboard(text) {
    try { await navigator.clipboard.writeText(text); }
    catch(err) {
        alert('Error in copying text: ', err);
    }
}

shouldUpdate(data, nextOpts) {
    return true;
}

this.on('mount', function() {
    var self = this;
    $("#load-mode").click();
    editor = CodeMirror(document.getElementById("code"), {
        mode: "null",
        spellcheck: false,
        autocorrect: false,
        scrollbarStyle: "null",
        lineWrapping: false,
        lineNumbers: false,
        styleActiveLine: false,
        styleSelectedText: true,
        matchBrackets: false,
        value: `// track_no, pattern, current_bit, samples, sample and Tone are available as globals`
    });

    editor.setSize(null, window.innerHeight - document.getElementById("header-playback").clientHeight - 160);

    if (this.props.song) {
        const lib = window.JsonUrl('lzma');
        lib.decompress(this.props.song).then(data => {
            var sample_names = data["sample_names"];
            var dial_count = data["dial_count"];
            var numbers_count = data["numbers_count"];
            delete data["sample_names"];
            delete data["dial_count"];
            delete data["numbers_count"];
            this.state.tempo = data.tempo;
            this.state.ticks = data.ticks;
            this.state.code = data.code;
            sample_names.map(function (url) {
                self.addURL(url);
            });
            for (var i = 0; i < dial_count; i++) {
                this.state.dials.push({});
            }
            for (var i = 0; i < numbers_count; i++) {
                this.state.numbers.push({});
            }
            editor.setValue(this.state.code);
            this.update();
            riot.mount('');
        })
    }


});




download() {
    
    function download(data, filename = "song.txt", type = "text/plain") {
            var file = new Blob([data], {type: type});
            var a = document.createElement("a"),
                    url = URL.createObjectURL(file);
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            setTimeout(function() {
                document.body.removeChild(a);
                window.URL.revokeObjectURL(url);  
            }, 0); 
    }
    download(editor.getValue())
}

save() {
    this.state.code = editor.getValue();
    var text = {
        tempo: this.state.tempo,
        dial_count: this.state.dials.length,
        numbers_count: this.state.numbers.length,
        sample_names: this.state.samples.map(function (item) {return item["__url"]}),
        ticks: 16,
        code: this.state.code,
    };
    const lib = window.JsonUrl('lzma');
	lib.compress(text).then(encodedData => {
        var link = "/song/" + encodedData;
        window.history.pushState({}, 'Bitrhythm', link);
        //window.open(link, "_blank");
    });
}

reload() {
    window.location.replace( "//" + window.location.host)
}

reset() {

    Tone.Master.mute = true;
    Tone.Transport.stop();
    var self = this;

    if (self.timer) {
        clearInterval(self.timer);
    }
    document.getElementById('tempo-value').disabled = false;
    document.getElementById('tick-value').disabled = false;
    editor.setValue("");

    this.state = {
        mem: {},
        dials: [],
        samples: [],
        tempo: 120,
        ticks: 16,
    }

    this.update();
    riot.mount('bitrhythm', {
        song: this.props.song
    })


}

editTempo(e) {
    this.update({
        state: {
            ...this.state,
            tempo: parseInt(e.target.value)
        }
    })
}

editTicks(e) {
    this.update({
        state: {
            ...this.state,
            ticks: e.target.value
        }
    })
}



async play() {
    var self = this;
    var cellx = window.cellx.cellx;
    var $ = jQuery;

    await Tone.context.resume()
    await Tone.start();
    await Tone.Transport.start();
    Tone.Transport.bpm.value = this.state.tempo;
    Tone.Transport.swing.value = 0;

    window.hit_map = {};

    var transition = function () {
    }

    var always = function () {
    }

    var render_loop = function () {
    }

    var animation = function () {
        render_loop();
        window.requestAnimationFrame(animation)
    }

    Tone.Master.mute = false;
    document.getElementById('tempo-value').disabled = true;
    document.getElementById('tick-value').disabled = true;

    var mem = self.state.mem;
    window.mem = mem;
    var handlers = {};
    window.count = -1;

    var text = editor.getValue();

    editor.on("change", function () {
        text = editor.getValue();
    });

    var patterns = [ cellx("0000") ]; // need this for first eval

    var bars = 0;
    var tick = 0;

    var quarter_beat_length = 60000.0 / this.state.tempo;
    var beat_length = quarter_beat_length * 4;
    var delta = beat_length / this.state.ticks;

    window.samples = this.state.samples;
    var eval_guard = false;
    self.timer = setInterval(function () {
    count = count + 1;
    tick = (count % this.state.ticks);
    if (tick === 0) ++bars;

    $("#duration").html("" + bars + "." + tick + " / " + count + " / " + window.roundTo(Tone.Transport.seconds, 2));
    always();
    for (var i = 0; i < patterns.length; i++) {
        if (i == 0) {
            eval_guard = true;
        } else {
            eval_guard = false;

        }
        var dials = self.state.dials;
        var numbers = self.state.numbers;

        if (document.getElementById('edit-mode').checked) {
            var p_text  = oldPatterns[i];
        } else {
            var p_text = patterns[i];
            oldPatterns[i] = p_text;
        }
        if (p_text() && p_text().length !== 0) {
            var track_no = i + 1;
            var pattern = pattern_parse(p_text());
            var meta = pattern_meta(p_text());
            var isHit = (pattern.split('')[tick] == "1") ? true : false;

            try {
                if (document.getElementById('edit-mode').checked) {
                    eval(oldCode);
                } else {
                    eval(text);
                    if (document.getElementById('redo').checked) {
                        document.getElementById('redo').checked = false;
                        tick = (count % this.state.ticks);
                        count -= tick + 1;
                        i = 0;
                        continue;
                    }
                    if (document.getElementById('load-mode').checked) {
                        document.getElementById('load-mode').checked = false;
                        transition();
                    }
                    oldCode = text;
                }
                $("#error").html("");
            } catch (ex) {
                $("#error").html(ex);
                eval(oldCode);
            }
        }
    }
  }.bind(this), delta)

  window.requestAnimationFrame(animation);

}


start() {
    recorder.start();
}

stop() {
    recorder.stop();
}

addDial() {
    this.state.dials.push({});
    this.update();
}

addNumber() {
    this.state.numbers.push({});
    this.update();
}

addURL(value) {
    var self = this;
    this.state.samples.push({"__name": value});
    var sam;
    sam = new Tone.Sampler({
        urls:  {
            "C3": value,
        }
    });
    sam["__name"] = value;
    sam["__url"] = value;
    self.setSample(sam, self.state.samples.length - 1);
}

addSampleURL() {
    var self = this;
    alertify.prompt( 'Enter Sample URL', '', ''
        , function(evt, value) {
            self.addURL(value);
        }
        , function() {
            alertify.error('Cancel')
        }
    );
}

addSample() {
    this.state.samples.push({});
    this.update();
}

rmSample(index) {
   this.state.samples.splice(index, 1);
   this.update();
}

rmDial(index) {
   this.state.dials.splice(index, 1);
   this.update();
}

rmNumber(index) {
   this.state.numbers.splice(index, 1);
   this.update();
}

setSample(e, i) {
    this.state.samples[i] = e;
    this.update();
}

</script>

</bitrhythm>