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