Add a basic selection of instruments from the MIDI and chiptune categories
This commit is contained in:
71
src/audio.ts
Normal file
71
src/audio.ts
Normal 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
3
src/data.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { InstrumentData } from "./index";
|
||||
|
||||
export const instrumentData: InstrumentData = require("../instruments.yaml");
|
37
src/index.ts
37
src/index.ts
@@ -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"];
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user