Add a basic selection of instruments from the MIDI and chiptune categories

This commit is contained in:
mtkennerly
2019-07-21 10:40:50 -04:00
parent 359b56ad62
commit 7d2660465e
178 changed files with 1089 additions and 47 deletions

71
src/audio.ts Normal file
View File

@@ -0,0 +1,71 @@
import * as tone from "tone";
import { assertNever, notes } from "./index";
import { instrumentData } from "./data";
function setSamplerRelease(sampler: tone.Sampler, instrument: string) {
const instrumentDuration = instrumentData[instrument]["duration"];
switch (instrumentDuration) {
case "instant":
sampler.release = 0.05;
break;
case "mini":
sampler.release = 0.125;
break;
case "short":
sampler.release = 0.25;
break;
case "mid":
sampler.release = 0.4;
break;
case "long":
sampler.release = 1;
break;
case "extended":
sampler.release = 1.5;
break;
case "mega":
sampler.release = 5;
break;
case "constant":
sampler.release = 30;
break;
case "infinite":
sampler.release = 0.1;
break;
default:
assertNever(instrumentDuration);
}
}
function setSamplerCurve(sampler: tone.Sampler) {
(sampler as any).curve = "linear";
}
export function getSampler(
instrument: string,
extension: string = "ogg",
baseUrl: string = "/bosca-ceoil-js/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);
setSamplerCurve(sampler);
setSamplerRelease(sampler, instrument);
return sampler;
}
export function changeSampler(
sampler: tone.Sampler,
instrument: string,
extension: string = "ogg",
): tone.Sampler {
for (const note of notes) {
sampler.add(note, `${instrument}/${note.toLowerCase()}.${extension}`);
}
setSamplerCurve(sampler);
setSamplerRelease(sampler, instrument);
return sampler;
}

3
src/data.ts Normal file
View File

@@ -0,0 +1,3 @@
import { InstrumentData } from "./index";
export const instrumentData: InstrumentData = require("../instruments.yaml");

View File

@@ -1,17 +1,22 @@
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 = "/bosca-ceoil-js/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;
export function assertNever(x: never): never {
throw new Error(`Unexhaustive condition leading to value: ${x}`);
}
// Durations:
// - Constant means that the sound plays for the same amount of time
// regardless of the length of the note in Bosca Ceoil.
// - Infinite means that the sound plays for however arbitrarily long
// the Bosca Ceoil note is.
// - The others are finite times, meaning that the sound will play up to
// a max time, but after that point, it will go silent regardless of how
// long the note is in Bosca Ceoil.
export interface Instrument {
index: number;
duration: "instant" | "mini" | "short" | "mid" | "long" | "extended" | "mega" | "constant" | "infinite";
release: number;
category: Array<string>;
}
export type InstrumentData = { [key: string]: Instrument };
export const notes = ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"];

View File

@@ -1,5 +1,5 @@
import * as tone from 'tone';
import { getSampler } from "./index";
import { changeSampler, getSampler } from "./audio";
const dialogPolyfill = require("dialog-polyfill");
let playing = false;
@@ -25,6 +25,7 @@ interface Note {
scheduledEvent: number | null;
}
let instrumentName = "midi.piano1";
let sampler = getSampler("midi.piano1");
let patterns: { [key: number]: { [key: string]: Array<Note> } } = {};
for (const chord of chords) {
@@ -54,6 +55,20 @@ function resumeAudioContext() {
}
}
// mdl-selectfield doesn't play nice with custom onchange event listeners,
// so we use this to handle instrument changes instead.
function setInstrumentLoop(instrumentField: HTMLSelectElement) {
setInstrument(instrumentField);
setTimeout(() => { setInstrumentLoop(instrumentField); }, 250);
}
function setInstrument(instrumentField: HTMLSelectElement) {
if (instrumentName !== instrumentField.value) {
instrumentName = instrumentField.value;
changeSampler(sampler, instrumentField.value);
}
}
function setBpm() {
let bpmField = document.getElementById("bpm");
if (bpmField !== null && bpmField instanceof HTMLInputElement) {
@@ -247,6 +262,11 @@ function onLoad() {
}
}
let instrumentField = document.getElementById("instruments");
if (instrumentField !== null && instrumentField instanceof HTMLSelectElement) {
setInstrumentLoop(instrumentField);
}
let bpmField = document.getElementById("bpm");
if (bpmField !== null) {
bpmField.addEventListener("change", setBpm);