Initial commit

This commit is contained in:
mtkennerly
2019-07-16 03:31:45 -04:00
commit 178455555b
24 changed files with 6168 additions and 0 deletions

13
src/index.ts Normal file
View File

@@ -0,0 +1,13 @@
import * as tone from 'tone';
const notes = ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"];
export function getSampler(instrument: string, extension: string = "flac", baseUrl: string = "audio/"): tone.Sampler {
let samples: { [key: string]: string } = {};
for (const note of notes) {
samples[note] = `${instrument}/${note.toLowerCase()}.${extension}`;
}
let sampler = new tone.Sampler(samples, undefined, baseUrl);
(sampler as any).curve = "linear";
return sampler;
}

324
src/player.ts Normal file
View File

@@ -0,0 +1,324 @@
import * as tone from 'tone';
import { getSampler } from "./index";
let playing = false;
const letters = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
const chords = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const volume = new tone.Volume(0);
const lowPass = new tone.LowpassCombFilter(0, 0);
const delayEffect = new tone.FeedbackDelay(0, 0);
const chorusEffect = new tone.Chorus();
chorusEffect.wet.value = 0;
const reverbEffect = new tone.Freeverb(0, 3000);
reverbEffect.wet.value = 0;
const distortionEffect = new tone.BitCrusher(4);
distortionEffect.wet.value = 0;
const lowBoostEffect = new tone.Filter(0, "lowshelf");
const compressorEffect = new tone.Compressor(0);
const highPassEffect = new tone.Filter(0, "highpass");
interface Note {
length: number;
scheduledEvent: number | null;
}
let sampler = getSampler("midi.piano1");
let patterns: { [key: number]: { [key: string]: Array<Note> } } = {};
for (const chord of chords) {
patterns[chord] = {};
for (const letter of letters) {
patterns[chord][letter] = Array.from({ length: 16 }, () => { return { length: 0, scheduledEvent: null }; });
}
}
function toggleAudio() {
if (playing) {
tone.Transport.stop();
tone.Transport.position = "0";
} else {
resumeAudioContext();
tone.Transport.loopEnd = '1m';
tone.Transport.loop = true;
}
tone.Transport.toggle(0);
playing = !playing;
}
function resumeAudioContext() {
let ac = (tone as any).context;
if (ac.state !== "running") {
ac.resume();
}
}
function setBpm() {
let bpmField = document.getElementById("bpm");
if (bpmField !== null && bpmField instanceof HTMLInputElement) {
tone.Transport.bpm.value = parseInt(bpmField.value);
}
}
function setVolume() {
const field = document.getElementById("volume");
if (field !== null && field instanceof HTMLInputElement) {
let newValue = parseInt(field.value);
if (newValue === 0) {
volume.mute = true;
} else {
volume.volume.value = (newValue - 100) / 5;
volume.mute = false;
}
}
}
function setSwing() {
const field = document.getElementById("swing");
if (field !== null && field instanceof HTMLInputElement) {
tone.Transport.swing = parseFloat(field.value);
tone.Transport.swingSubdivision = "16n";
}
}
function setResonance() {
const field = document.getElementById("resonance");
if (field !== null && field instanceof HTMLInputElement) {
lowPass.resonance.value = parseFloat(field.value);
}
}
function setDampening() {
const field = document.getElementById("dampening");
if (field !== null && field instanceof HTMLInputElement) {
lowPass.dampening.value = parseInt(field.value);
}
}
function togglePlayButton() {
let playButton = document.getElementById("playButton");
if (playButton !== null) {
if (playButton.textContent !== null && playButton.textContent.trim().toLowerCase() === "play") {
playButton.textContent = "Stop";
} else {
playButton.textContent = "Play";
}
}
}
function setEffect(effect: string, value: number) {
switch (effect) {
case "delay":
delayEffect.delayTime.value = value === 0 ? 0 : tone.Time("8n") * 2 * value;
delayEffect.feedback.value = 0.15 * 2 * value;
break;
case "chorus":
chorusEffect.wet.value = value;
break;
case "reverb":
reverbEffect.roomSize.value = value * 0.9;
reverbEffect.wet.value = value;
break;
case "distortion":
distortionEffect.wet.value = value === 0 ? 0 : 1;
distortionEffect.bits = value;
break;
case "lowBoost":
lowBoostEffect.frequency.value = value;
lowBoostEffect.gain.value = value === 0 ? 0 : 20;
break;
case "compressor":
compressorEffect.threshold.value = value;
break;
case "highPass":
highPassEffect.frequency.value = value;
break;
}
}
function scheduleNote(chord: number, letter: string, index: number, length: number) {
unscheduleNote(chord, letter, index);
console.log(`scheduleNote(chord=${chord}, letter=${letter}, index=${index}, length=${length})`);
patterns[chord][letter][index]["length"] = length;
patterns[chord][letter][index]["scheduledEvent"] = tone.Transport.schedule(
time => {
sampler.triggerAttackRelease(`${letter}${chord}`, tone.Time("16n") * length, time);
tone.Draw.schedule(() => {
const noteElement = document.querySelector(`#${letter.replace("#", "\\#")}-${chord}-${index}`);
if (noteElement !== null) {
if (length <= 1) {
noteElement.classList.add("playing");
} else {
noteElement.classList.add("playingLong");
}
setTimeout(() => {
noteElement.classList.remove("playing");
noteElement.classList.remove("playingLong");
}, 100 * length);
}
}, time);
},
`0:0:${index}`
);
}
function unscheduleNote(chord: number, letter: string, index: number) {
patterns[chord][letter][index]["length"] = 0;
const schedulee = patterns[chord][letter][index]["scheduledEvent"];
if (schedulee !== null) {
tone.Transport.clear(schedulee);
patterns[chord][letter][index]["scheduledEvent"] = null;
}
}
function onClickNoteCell(event: MouseEvent, cell: HTMLTableCellElement, chord: number, letter: string, index: number) {
let length = patterns[chord][letter][index]["length"];
console.log(`onClickNoteCell(chord=${chord}, letter=${letter}, index=${index}) | length ${length}`);
if (event.shiftKey && event.ctrlKey) {
length = Math.max(length - 1, 0);
} else if (event.shiftKey) {
length = Math.max(Math.min(length + 1, 16), 2);
} else if (length > 0) {
length = 0;
} else {
length = 1;
}
cell.innerHTML = length.toString();
if (length <= 1) {
cell.classList.remove("noteLong");
} else {
cell.classList.add("noteLong");
}
if (length > 0 && length !== patterns[chord][letter][index]["length"]) {
cell.classList.add("active");
scheduleNote(chord, letter, index, length);
} else if (length === 0) {
cell.classList.remove("active");
unscheduleNote(chord, letter, index);
}
}
function onLoad() {
sampler.chain(
volume,
lowPass,
delayEffect,
chorusEffect,
reverbEffect,
distortionEffect,
lowBoostEffect,
compressorEffect,
highPassEffect,
tone.Master
);
document.onkeypress = event => {
if (event.keyCode === 32) {
toggleAudio();
togglePlayButton();
return false;
}
};
let playButton = document.getElementById("playButton");
if (playButton !== null) {
playButton.addEventListener("click", toggleAudio);
playButton.addEventListener("click", togglePlayButton);
}
let helpButton = document.getElementById("helpButton");
let helpModal = document.getElementById("helpModal");
if (helpButton !== null && helpModal !== null) {
helpButton.addEventListener("click", () => { (helpModal as any).showModal(); });
const helpModalClose = helpModal.querySelector(".close");
if (helpModalClose !== null) {
helpModalClose.addEventListener("click", () => {
(helpModal as any).close();
});
}
}
let bpmField = document.getElementById("bpm");
if (bpmField !== null) {
bpmField.addEventListener("change", setBpm);
}
let effectsMenu = document.getElementById("effectsMenu");
if (effectsMenu !== null) {
effectsMenu.addEventListener("click", event => {
event.stopPropagation();
});
}
const effects = ["delay", "chorus", "reverb", "distortion", "lowBoost", "compressor", "highPass"];
for (const effect of effects) {
const effectField = document.getElementById(`${effect}Effect`);
if (effectField !== null && effectField instanceof HTMLInputElement) {
effectField.addEventListener("change", () => {
setEffect(effect, parseFloat(effectField.value));
});
}
}
let patternTable = document.getElementById("pattern");
if (patternTable !== null && patternTable instanceof HTMLTableElement) {
for (const chord of chords.slice().reverse()) {
let first = true;
for (const letter of letters.slice().reverse()) {
let row = patternTable.insertRow();
if (first) {
first = false;
let chordHeader = document.createElement("th");
chordHeader.rowSpan = letters.length;
chordHeader.innerHTML = chord.toString();
row.appendChild(chordHeader);
}
let letterHeader = document.createElement("th");
letterHeader.innerHTML = letter;
row.appendChild(letterHeader);
for (const i of Array(16).keys()) {
let cell = row.insertCell();
cell.id = `${letter}-${chord}-${i}`;
cell.classList.add(`note-${letter}`);
cell.onclick = event => { onClickNoteCell(event, cell, chord, letter, i); };
}
}
}
}
const centralRow = document.getElementById("F-5-0");
if (centralRow !== null) {
centralRow.scrollIntoView({ "behavior": "smooth", "block": "center" });
}
let volumeField = document.getElementById("volume");
if (volumeField !== null) {
volumeField.addEventListener("change", setVolume);
}
let swingField = document.getElementById("swing");
if (swingField !== null) {
swingField.addEventListener("change", setSwing);
}
let resonanceField = document.getElementById("resonance");
if (resonanceField !== null) {
resonanceField.addEventListener("change", setResonance);
}
let dampeningField = document.getElementById("dampening");
if (dampeningField !== null) {
dampeningField.addEventListener("change", setDampening);
}
}
window.onload = onLoad;