strudel/packages/tone/tone.mjs
2022-04-23 23:36:26 +02:00

120 lines
4.1 KiB
JavaScript

import { Pattern } from '@strudel.cycles/core';
import * as _Tone from 'tone';
// import Tone from here, to make sure to get the same AudioContext
export const Tone = _Tone;
const {
AutoFilter,
Destination,
Filter,
Gain,
isNote,
Synth,
PolySynth,
MembraneSynth,
MetalSynth,
MonoSynth,
AMSynth,
DuoSynth,
FMSynth,
NoiseSynth,
PluckSynth,
Sampler,
getDestination,
Players,
} = Tone;
import * as tonePiano from '@tonejs/piano';
const { Piano } = tonePiano;
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
// "balanced" | "interactive" | "playback";
// Tone.setContext(new Tone.Context({ latencyHint: 'playback', lookAhead: 1 }));
export const getDefaultSynth = () => {
const s = new PolySynth().chain(new Gain(0.5), getDestination());
s.set({
oscillator: { type: 'triangle' },
envelope: {
release: 0.01,
},
});
return s;
};
// what about
// https://www.charlie-roberts.com/gibberish/playground/
// with this function, you can play the pattern with any tone synth
Pattern.prototype.tone = function (instrument) {
return this._withEvent((event) => {
const onTrigger = (time, event) => {
let note;
let velocity = event.context?.velocity ?? 0.75;
if (instrument instanceof PluckSynth) {
note = getPlayableNoteValue(event);
instrument.triggerAttack(note, time);
} else if (instrument instanceof NoiseSynth) {
instrument.triggerAttackRelease(event.duration.valueOf(), time); // noise has no value
} else if (instrument instanceof Piano) {
note = getPlayableNoteValue(event);
instrument.keyDown({ note, time, velocity });
instrument.keyUp({ note, time: time + event.duration.valueOf(), velocity });
} else if (instrument instanceof Sampler) {
note = getPlayableNoteValue(event);
instrument.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
} else if (instrument instanceof Players) {
if (!instrument.has(event.value)) {
throw new Error(`name "${event.value}" not defined for players`);
}
const player = instrument.player(event.value);
// velocity ?
player.start(time);
player.stop(time + event.duration.valueOf());
} else {
note = getPlayableNoteValue(event);
instrument.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
}
};
return event.setContext({ ...event.context, instrument, onTrigger });
});
};
Pattern.prototype.define('tone', (type, pat) => pat.tone(type), { composable: true, patternified: false });
// synth helpers
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;
};
// effect helpers
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();