From 1b35dd0c5553220479c64e3dae46f4a5fb046962 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Apr 2022 21:34:05 +0200 Subject: [PATCH 01/11] add preliminary getFrequency --- packages/core/util.mjs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/core/util.mjs b/packages/core/util.mjs index 5ec88125..ba0d00a4 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -40,6 +40,23 @@ export const getPlayableNoteValue = (event) => { return note; }; +// TODO: make this compatible with midi numbers object values +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)); From a638f9505f3bc5937307f3f68da3ff7437e563e7 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Apr 2022 21:34:19 +0200 Subject: [PATCH 02/11] test fromMidi + getFrequency --- packages/core/test/util.test.mjs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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', () => { From 9cc568fe3e3be296451bb92e865cbc5b8e5aca36 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Apr 2022 21:36:10 +0200 Subject: [PATCH 03/11] remove tonejs experimental features --- packages/tone/tone.mjs | 154 ----------------------------------------- 1 file changed, 154 deletions(-) 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 }); From e0e3ab941e2acdacbdf81b9cfc097a75539df079 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Apr 2022 21:38:23 +0200 Subject: [PATCH 04/11] remove experimental tone tutorial --- repl/src/tutorial/tutorial.mdx | 53 ---------------------------------- 1 file changed, 53 deletions(-) diff --git a/repl/src/tutorial/tutorial.mdx b/repl/src/tutorial/tutorial.mdx index af64fc5e..117695c9 100644 --- a/repl/src/tutorial/tutorial.mdx +++ b/repl/src/tutorial/tutorial.mdx @@ -590,59 +590,6 @@ Helper to set the envelope of a Tone.js instrument. Intended to be used with Ton .tone(synth(adsr(0,.1,0,0)).chain(out()))`} /> -### Experimental: Patternification - -While the above methods work for static sounds, there is also the option to patternify tone methods. -This is currently experimental, because the performance is not stable, and audio glitches will appear after some time. -It would be great to get this to work without glitches though, because it is fun! - -#### synth(type) - -With .synth, you can create a synth with a variable wave type: - -").slow(4)`} -/> - -#### adsr(attack, decay?, sustain?, release?) - -Chainable Envelope helper: - - - -Due to having more than one argument, this method is not patternified. - -#### filter(cuttoff) - -Patternified filter: - - - -#### gain(value) - -Patternified gain: - - - -#### autofilter(value) - -Patternified autofilter: - -")`} -/> - ## Tonal API The Tonal API, uses [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations. From f210c4425b2d8c337ca23d3e5ef48638b9a9309d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 16 Apr 2022 21:39:14 +0200 Subject: [PATCH 05/11] make webaudio compatible with repl + rename osc to wave + migrate repl.html --- packages/webaudio/examples/repl.html | 11 +++++++---- packages/webaudio/package.json | 3 +++ packages/webaudio/webaudio.mjs | 12 ++++++++---- repl/src/App.js | 1 + 4 files changed, 19 insertions(+), 8 deletions(-) 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...