import gcd from "compute-gcd";
import keys from "./lib/keys";
import color from "./lib/color";
import kalimba from "./lib/kalimba";
import organ from "./lib/organ";
import { getOutput } from "./lib/output";
import { browser, requestAudioContext, choice } from "./lib/util";
import { PRIMES } from "./lib/primes";
// import life from "./lib/life";
let instrument = kalimba;
const root = 440;
const s = 50;
let w, h, ws, hs;
const add_on = 0;
const mul_on = 1.0;
const add_off = 0.1;
const mul_off = 0.9;
let dragging = false;
let erasing = false;
let lastFreq = 0;
let notes = [];
let base_x = 0;
let base_y = 0;
let is_split = false;
requestAudioContext(() => {
const output = getOutput();
kalimba.load(output);
organ.load(output);
build();
bind();
});
function build() {
w = window.innerWidth;
h = window.innerHeight;
ws = Math.ceil(w / s);
hs = Math.ceil(h / s);
for (var i = 0; i < ws; i++) {
notes[i] = [];
for (var j = 0; j < hs; j++) {
notes[i][j] = add(i, j);
}
}
}
function rebuild() {
notes.forEach((row) => row.forEach((note) => note.destroy()));
build();
}
function play(freq) {
if (!freq.playing) {
freq.playing = true;
let frequency = freq.frequency;
// while (frequency < root) {
// frequency *= 2;
// }
// while (frequency > root) {
// frequency /= 2;
// }
organ.play(frequency);
freq.div.classList.add("playing");
}
}
function trigger(freq) {
kalimba.play(freq.frequency);
}
function pause(freq) {
if (freq.playing) {
freq.playing = false;
organ.pause(freq.frequency);
freq.div.classList.remove("playing");
}
}
function toggle(freq) {
if (freq.playing) {
pause(freq);
} else {
play(freq);
}
}
function add(i, j) {
const a = i + 1 + base_x;
const b = j + 1 + base_y;
// const a = i + 1;
// const b = i + j + 2;
// const a = PRIMES[i];
// const b = PRIMES[i + j + 1];
const div = document.createElement("div");
const frequency = (root * a) / b;
// const frequency = root * Math.pow(2, ((b / a) % 1) + 1);
let add = 0;
let frac;
div.style.left = i * s + "px";
div.style.top = j * s + "px";
const freq = {
frequency,
div,
i,
j,
playing: false,
destroy: () => {
div.parentNode.removeChild(div);
},
recolor: (numerator, denominator) => {
let aa = a / numerator;
let bb = b / denominator;
if (aa < bb) {
add = -Math.log(bb / aa) / 3.5;
} else {
add = Math.log(aa / bb) / 6;
}
let a_inv = a * denominator;
let b_inv = b * numerator;
let ba_gcd = gcd(a_inv, b_inv);
let a_disp = a_inv / ba_gcd;
let b_disp = b_inv / ba_gcd;
frac = Math.log2(aa / bb) % 1;
let frac_orig = Math.log2(a / b) % 1;
if (frac < 0) {
frac += 1;
}
if (frac_orig < 0) {
frac += 1;
}
if (frac_orig === 0) {
div.style.fontWeight = "900";
} else {
div.style.fontWeight = "500";
}
div.innerHTML = `
${a_disp}
/
${b_disp}
`;
if (freq.playing) {
div.style.backgroundColor = color(frac, add + add_on, mul_on);
} else {
div.style.backgroundColor = color(frac, add + add_off, mul_off);
}
},
};
freq.recolor(1, 1);
if (browser.isDesktop) {
div.addEventListener("mousedown", function (event) {
if (event.button === 2) {
// rightclick
event.preventDefault();
// notes.forEach((row) => row.forEach((note) => note.recolor(a, b)));
is_split = [a, b];
toggle(freq);
return;
}
div.style.backgroundColor = color(frac, add + add_on, mul_on);
dragging = true;
if (instrument === organ) {
toggle(freq);
erasing = !freq.playing;
} else {
trigger(freq);
}
});
div.addEventListener("mouseenter", function () {
div.style.backgroundColor = color(frac, add + add_on, mul_on);
if (dragging) {
if (instrument === organ) {
if (erasing) {
pause(freq);
} else {
toggle(freq);
}
} else {
trigger(freq);
}
}
});
div.addEventListener("mouseleave", function () {
div.style.backgroundColor = color(frac, add + add_off, mul_off);
});
div.addEventListener("contextmenu", function (event) {
if (!event.ctrlKey || !event.metaKey || !event.altKey) {
event.preventDefault();
}
});
} else {
div.addEventListener("touchstart", function (e) {
e.preventDefault();
toggle(freq);
erasing = !freq.playing;
lastFreq = freq;
});
}
document.body.appendChild(div);
return freq;
}
function bind() {
window.addEventListener("resize", build);
if (browser.isDesktop) {
document.addEventListener("mousedown", (event) => {
if (event.button !== 2) {
dragging = true;
}
});
document.addEventListener("mouseup", () => {
dragging = false;
});
} else {
document.addEventListener("touchstart", (e) => {
e.preventDefault();
dragging = true;
});
document.addEventListener("touchmove", (e) => {
e.preventDefault();
const x = Math.floor(e.touches[0].pageX / s);
const y = Math.floor(e.touches[0].pageY / s);
if (!(x in notes) || !(y in notes[x])) return;
const freq = notes[x][y];
if (freq !== lastFreq) {
if (dragging) {
if (erasing) {
pause(freq);
} else {
toggle(freq);
}
}
lastFreq = freq;
}
});
document.addEventListener("touchend", () => {
dragging = false;
});
}
}
function swap_instrument() {
instrument = instrument === kalimba ? organ : kalimba;
}
function keydown(e) {
// console.log(e.keyCode)
if (e.altKey || e.ctrlKey || e.metaKey) return;
let step = 1;
if (e.shiftKey) {
step += 4;
}
switch (e.keyCode) {
case 37: // left
base_x = Math.max(0, base_x - step);
rebuild();
break;
case 38: // up
base_y = Math.max(0, base_y - step);
rebuild();
break;
case 39: // right
base_x += step;
rebuild();
break;
case 40: // down
base_y += step;
rebuild();
break;
}
}
window.addEventListener("keydown", keydown, true);
keys.listen(function (index) {
index += 7;
const x = index % 7;
const y = Math.floor(index / 7);
const a = x;
const b = y + 1;
const freq = notes[a][b];
console.log(a, b, freq.frequency);
trigger(freq);
// const freq = scales.current().index(index)
// document.body.style.backgroundColor = color( index / scales.current().scale.length )
// instrument.toggle(freq)
});
let hash = window.location.hash || window.location.search;
if (hash.match("sin") || hash.match("organ")) {
instrument = organ;
}