import {Pattern as _Pattern} from "../_snowpack/link/strudel.js"; import { AutoFilter, Filter, Gain, isNote, Synth, PolySynth, MembraneSynth, MetalSynth, MonoSynth, AMSynth, DuoSynth, FMSynth, NoiseSynth, PluckSynth, Sampler, getDestination, Players } from "../_snowpack/pkg/tone.js"; import {Piano} from "../_snowpack/pkg/@tonejs/piano.js"; import {getPlayableNoteValue} from "../_snowpack/link/util.js"; export const defaultSynth = new PolySynth().chain(new Gain(0.5), getDestination()); defaultSynth.set({ oscillator: {type: "triangle"}, envelope: { release: 0.01 } }); const Pattern = _Pattern; Pattern.prototype.tone = function(instrument) { return this._withEvent((event) => { const onTrigger = (time, event2) => { let note; let velocity = event2.context?.velocity ?? 0.75; switch (instrument.constructor.name) { case "PluckSynth": note = getPlayableNoteValue(event2); instrument.triggerAttack(note, time); break; case "NoiseSynth": instrument.triggerAttackRelease(event2.duration, time); break; case "Piano": note = getPlayableNoteValue(event2); instrument.keyDown({note, time, velocity}); instrument.keyUp({note, time: time + event2.duration, velocity}); break; case "Sampler": note = getPlayableNoteValue(event2); instrument.triggerAttackRelease(note, event2.duration, time, velocity); break; case "Players": if (!instrument.has(event2.value)) { throw new Error(`name "${event2.value}" not defined for players`); } const player = instrument.player(event2.value); player.start(time); player.stop(time + event2.duration); break; default: note = getPlayableNoteValue(event2); instrument.triggerAttackRelease(note, event2.duration, time, velocity); } }; return event.setContext({...event.context, instrument, onTrigger}); }); }; Pattern.prototype.define("tone", (type, pat) => pat.tone(type), {composable: true, patternified: false}); export const amsynth = (options) => new AMSynth(options); export const duosynth = (options) => new DuoSynth(options); export const fmsynth = (options) => new FMSynth(options); export const membrane = (options) => new MembraneSynth(options); export const metal = (options) => new MetalSynth(options); export const monosynth = (options) => new MonoSynth(options); export const noise = (options) => new NoiseSynth(options); export const pluck = (options) => new PluckSynth(options); export const polysynth = (options) => new PolySynth(options); export const sampler = (options, baseUrl) => new Promise((resolve) => { const s = new Sampler(options, () => resolve(s), baseUrl); }); export const players = (options, baseUrl = "") => { options = !baseUrl ? options : Object.fromEntries(Object.entries(options).map(([key, value]) => [key, baseUrl + value])); return new Promise((resolve) => { const s = new Players(options, () => resolve(s)); }); }; export const synth = (options) => new Synth(options); export const piano = async (options = {velocities: 1}) => { const p = new Piano(options); await p.load(); return p; }; export const vol = (v) => new Gain(v); export const lowpass = (v) => new Filter(v, "lowpass"); export const highpass = (v) => new Filter(v, "highpass"); export const adsr = (a, d = 0.1, s = 0.4, r = 0.01) => ({envelope: {attack: a, decay: d, sustain: s, release: r}}); export const osc = (type) => ({oscillator: {type}}); export const out = () => getDestination(); const chainable = function(instr) { const _chain = instr.chain.bind(instr); let chained = []; instr.chain = (...args) => { chained = chained.concat(args); instr.disconnect(); return _chain(...chained, getDestination()); }; instr.filter = (freq = 1e3, type = "lowpass") => instr.chain(new Filter(freq, type)); instr.gain = (gain2 = 0.9) => instr.chain(new Gain(gain2)); return instr; }; export const poly = (type) => { const s = new PolySynth(Synth, {oscillator: {type}}).toDestination(); return chainable(s); }; Pattern.prototype._poly = function(type = "triangle") { const instrumentConfig = { oscillator: {type}, envelope: {attack: 0.01, decay: 0.01, sustain: 0.6, release: 0.01} }; if (!this.instrument) { this.instrument = poly(type); } return this._withEvent((event) => { const onTrigger = (time, event2) => { this.instrument.set(instrumentConfig); this.instrument.triggerAttackRelease(event2.value, event2.duration, time); }; return event.setContext({...event.context, instrumentConfig, onTrigger}); }); }; Pattern.prototype.define("poly", (type, pat) => pat.poly(type), {composable: true, patternified: true}); const getTrigger = (getChain, value) => (time, event) => { const chain = getChain(); if (!isNote(value)) { throw new Error("not a note: " + value); } chain.triggerAttackRelease(value, event.duration, time); setTimeout(() => { chain.dispose(); }, event.duration * 2e3); }; Pattern.prototype._synth = function(type = "triangle") { return this._withEvent((event) => { const instrumentConfig = { oscillator: {type}, envelope: {attack: 0.01, decay: 0.01, sustain: 0.6, release: 0.01} }; const getInstrument = () => { const instrument = new Synth(); instrument.set(instrumentConfig); return instrument; }; const onTrigger = getTrigger(() => getInstrument().toDestination(), event.value); return event.setContext({...event.context, getInstrument, instrumentConfig, onTrigger}); }); }; Pattern.prototype.adsr = function(attack = 0.01, decay = 0.01, sustain = 0.6, release = 0.01) { return this._withEvent((event) => { if (!event.context.getInstrument) { throw new Error("cannot chain adsr: need instrument first (like synth)"); } const instrumentConfig = {...event.context.instrumentConfig, envelope: {attack, decay, sustain, release}}; const getInstrument = () => { const instrument = event.context.getInstrument(); instrument.set(instrumentConfig); return instrument; }; const onTrigger = getTrigger(() => getInstrument().toDestination(), event.value); return event.setContext({...event.context, getInstrument, instrumentConfig, onTrigger}); }); }; Pattern.prototype.chain = function(...effectGetters) { return this._withEvent((event) => { if (!event.context?.getInstrument) { throw new Error("cannot chain: need instrument first (like synth)"); } const chain = (event.context.chain || []).concat(effectGetters); const getChain = () => { const effects = chain.map((getEffect) => getEffect()); return event.context.getInstrument().chain(...effects, getDestination()); }; const onTrigger = getTrigger(getChain, event.value); return event.setContext({...event.context, getChain, onTrigger, chain}); }); }; export const autofilter = (freq = 1) => () => new AutoFilter(freq).start(); export const filter = (freq = 1, q = 1, type = "lowpass") => () => new Filter(freq, type); export const gain = (gain2 = 0.9) => () => new Gain(gain2); Pattern.prototype._gain = function(g) { return this.chain(gain(g)); }; Pattern.prototype._filter = function(freq, q, type = "lowpass") { return this.chain(filter(freq, q, type)); }; Pattern.prototype._autofilter = function(g) { return this.chain(autofilter(g)); }; Pattern.prototype.define("synth", (type, pat) => pat.synth(type), {composable: true, patternified: true}); Pattern.prototype.define("gain", (gain2, pat) => pat.synth(gain2), {composable: true, patternified: true}); Pattern.prototype.define("filter", (cutoff, pat) => pat.filter(cutoff), {composable: true, patternified: true}); Pattern.prototype.define("autofilter", (cutoff, pat) => pat.filter(cutoff), {composable: true, patternified: true});