diff --git a/packages/core/test/util.test.mjs b/packages/core/test/util.test.mjs index 5e8c906e..bf402793 100644 --- a/packages/core/test/util.test.mjs +++ b/packages/core/test/util.test.mjs @@ -1,5 +1,6 @@ import { strict as assert } from 'assert'; -import { isNote, tokenizeNote, toMidi, mod, compose } from '../util.mjs'; +import { pure } from '../pattern.mjs'; +import { isNote, tokenizeNote, toMidi, fromMidi, mod, compose, getFrequency } from '../util.mjs'; describe('isNote', () => { it('should recognize notes without accidentals', () => { @@ -64,6 +65,21 @@ describe('toMidi', () => { assert.equal(toMidi('C##3'), 50); }); }); +describe('fromMidi', () => { + it('should turn midi into frequency', () => { + assert.equal(fromMidi(69), 440); + assert.equal(fromMidi(57), 220); + }); +}); +describe('getFrequency', () => { + it('should turn midi into frequency', () => { + const happify = (val, context = {}) => pure(val).firstCycle()[0].setContext(context); + assert.equal(getFrequency(happify('a4')), 440); + assert.equal(getFrequency(happify('a3')), 220); + assert.equal(getFrequency(happify(440, { type: 'frequency' })), 440); // TODO: migrate when values are objects.. + assert.equal(getFrequency(happify(432, { type: 'frequency' })), 432); + }); +}); describe('mod', () => { it('should work like regular modulo with positive numbers', () => { diff --git a/packages/core/util.mjs b/packages/core/util.mjs index 5ec88125..b9f0f2b8 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -40,6 +40,22 @@ export const getPlayableNoteValue = (event) => { return note; }; +export const getFrequency = (event) => { + let { value, context } = event; + // if value is number => interpret as midi number as long as its not marked as frequency + if (typeof value === 'object' && value.freq) { + return value.freq; + } + if (typeof value === 'number' && context.type !== 'frequency') { + value = fromMidi(event.value); + } else if (typeof value === 'string' && isNote(value)) { + value = fromMidi(toMidi(event.value)); + } else if (typeof value !== 'number') { + throw new Error('not a note or frequency:' + value); + } + return value; +}; + // rotate array by n steps (to the left) export const rotate = (arr, n) => arr.slice(n).concat(arr.slice(0, n)); diff --git a/packages/tone/tone.mjs b/packages/tone/tone.mjs index 5ad55edc..2426dac3 100644 --- a/packages/tone/tone.mjs +++ b/packages/tone/tone.mjs @@ -117,157 +117,3 @@ 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(); - -/* - -You are entering experimental zone - -*/ - -// the following code is an attempt to minimize tonejs code.. it is still an experiment - -const chainable = function (instr) { - const _chain = instr.chain.bind(instr); - let chained = []; - instr.chain = (...args) => { - chained = chained.concat(args); - instr.disconnect(); // disconnect from destination / previous chain - return _chain(...chained, getDestination()); - }; - // shortcuts: chaining multiple won't work forn now.. like filter(1000).gain(0.5). use chain + native Tone calls instead - // instr.filter = (freq = 1000, type: BiquadFilterType = 'lowpass') => - instr.filter = (freq = 1000, type = 'lowpass') => - instr.chain( - new Filter(freq, type), // .Q.setValueAtTime(q, time); - ); - instr.gain = (gain = 0.9) => instr.chain(new Gain(gain)); - return instr; -}; - -// helpers -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) { - // create only once to keep the js heap happy - // this.instrument = new PolySynth(Synth, instrumentConfig).toDestination(); - this.instrument = poly(type); - } - return this._withEvent((event) => { - const onTrigger = (time, event) => { - this.instrument.set(instrumentConfig); - this.instrument.triggerAttackRelease(event.value, event.duration, time); - }; - return event.setContext({ ...event.context, instrumentConfig, onTrigger }); - }); -}; - -Pattern.prototype.define('poly', (type, pat) => pat.poly(type), { composable: true, patternified: true }); - -/* - -You are entering danger zone - -*/ - -// everything below is nice in theory, but not healthy for the JS heap, as nodes get recreated on every call - -const getTrigger = (getChain, value) => (time, event) => { - const chain = getChain(); // make sure this returns a node that is connected toDestination // time - if (!isNote(value)) { - throw new Error('not a note: ' + value); - } - chain.triggerAttackRelease(value, event.duration, time); - setTimeout(() => { - // setTimeout is a little bit better compared to Transport.scheduleOnce - chain.dispose(); // mark for garbage collection - }, event.duration * 2000); -}; - -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: BiquadFilterType = 'lowpass') => - - - (freq = 1, q = 1, type = 'lowpass') => - () => - new Filter(freq, type); // .Q.setValueAtTime(q, time); - -export const gain = - (gain = 0.9) => - () => - new Gain(gain); - -Pattern.prototype._gain = function (g) { - return this.chain(gain(g)); -}; -// Pattern.prototype._filter = function (freq: number, q: number, type: BiquadFilterType = 'lowpass') { -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', (gain, pat) => pat.synth(gain), { 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 }); diff --git a/packages/webaudio/examples/repl.html b/packages/webaudio/examples/repl.html index 066943b3..97e945d3 100644 --- a/packages/webaudio/examples/repl.html +++ b/packages/webaudio/examples/repl.html @@ -11,9 +11,12 @@ Loading...