Initial commit
This commit is contained in:
13
src/index.ts
Normal file
13
src/index.ts
Normal 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
324
src/player.ts
Normal 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;
|
Reference in New Issue
Block a user