refactor: make webaudio glue as minimal as possible
This commit is contained in:
parent
930dabe4d6
commit
2cbd35f6d2
2 changed files with 37 additions and 21 deletions
|
|
@ -311,6 +311,7 @@ export const getADSRValues = (params, curve = 'linear', defaultValues) => {
|
||||||
const sustain = s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin;
|
const sustain = s != null ? s : (a != null && d == null) || (a == null && d == null) ? envmax : envmin;
|
||||||
return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)];
|
return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)];
|
||||||
};
|
};
|
||||||
|
|
||||||
let oscillators = {
|
let oscillators = {
|
||||||
sine: SineOsc,
|
sine: SineOsc,
|
||||||
saw: SawOsc,
|
saw: SawOsc,
|
||||||
|
|
@ -351,6 +352,33 @@ const defaultDefaultValues = {
|
||||||
|
|
||||||
let getDefaultValue = (key) => defaultDefaultValues[key];
|
let getDefaultValue = (key) => defaultDefaultValues[key];
|
||||||
|
|
||||||
|
const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
|
||||||
|
const accs = { '#': 1, b: -1, s: 1, f: -1 };
|
||||||
|
const note2midi = (note, defaultOctave = 3) => {
|
||||||
|
const [pc, acc = '', oct = defaultOctave] =
|
||||||
|
String(note)
|
||||||
|
.match(/^([a-gA-G])([#bsf]*)([0-9]*)$/)
|
||||||
|
?.slice(1) || [];
|
||||||
|
if (!pc) {
|
||||||
|
throw new Error('not a note: "' + note + '"');
|
||||||
|
}
|
||||||
|
const chroma = chromas[pc.toLowerCase()];
|
||||||
|
const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0;
|
||||||
|
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||||
|
};
|
||||||
|
const getFrequency = (value) => {
|
||||||
|
let { note, freq } = value;
|
||||||
|
note = note || 36;
|
||||||
|
if (typeof note === 'string') {
|
||||||
|
note = note2midi(note); // e.g. c3 => 48
|
||||||
|
}
|
||||||
|
if (!freq && typeof note === 'number') {
|
||||||
|
freq = Math.pow(2, (note - 69) / 12) * 440;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(freq);
|
||||||
|
};
|
||||||
|
|
||||||
export class DoughVoice {
|
export class DoughVoice {
|
||||||
constructor(value) {
|
constructor(value) {
|
||||||
// params without defaults:
|
// params without defaults:
|
||||||
|
|
@ -392,6 +420,7 @@ export class DoughVoice {
|
||||||
ir,
|
ir,
|
||||||
analyze,
|
analyze,
|
||||||
*/
|
*/
|
||||||
|
value.freq = getFrequency(value);
|
||||||
Object.assign(this, value);
|
Object.assign(this, value);
|
||||||
// params with defaults:
|
// params with defaults:
|
||||||
this.s = this.s ?? getDefaultValue('s');
|
this.s = this.s ?? getDefaultValue('s');
|
||||||
|
|
@ -421,6 +450,9 @@ export class DoughVoice {
|
||||||
this.release,
|
this.release,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
this._holdEnd = this._begin + this._duration; // needed for gate
|
||||||
|
this._end = this._holdEnd + this.release + 0.01; // needed for despawn
|
||||||
|
|
||||||
const SourceClass = oscillators[this.s] ?? TriOsc;
|
const SourceClass = oscillators[this.s] ?? TriOsc;
|
||||||
this._sound = new SourceClass();
|
this._sound = new SourceClass();
|
||||||
this._lpf = this.cutoff ? new Lpf() : null;
|
this._lpf = this.cutoff ? new Lpf() : null;
|
||||||
|
|
@ -482,9 +514,9 @@ export class Dough {
|
||||||
value.id = this.vid++;
|
value.id = this.vid++;
|
||||||
const voice = new DoughVoice(value);
|
const voice = new DoughVoice(value);
|
||||||
this.voices.push(voice);
|
this.voices.push(voice);
|
||||||
console.log('spawn', voice.id, value._holdDuration, 'voices:', this.voices.length);
|
console.log('spawn', voice.id, 'voices:', this.voices.length);
|
||||||
// schedule removal
|
// schedule removal
|
||||||
const endTime = Math.ceil(value._end * this.sampleRate);
|
const endTime = Math.ceil(voice._end * this.sampleRate);
|
||||||
this.schedule({ time: endTime /* + 48000 */, type: 'despawn', arg: voice.id });
|
this.schedule({ time: endTime /* + 48000 */, type: 'despawn', arg: voice.id });
|
||||||
}
|
}
|
||||||
despawn(vid) {
|
despawn(vid) {
|
||||||
|
|
|
||||||
|
|
@ -13,33 +13,17 @@ function initDoughWorklet() {
|
||||||
outputChannelCount: [2],
|
outputChannelCount: [2],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
/* webAudioTimeout(ac, () => destroyAudioWorkletNode(doughWorklet), begin, end); */
|
|
||||||
connectToDestination(doughWorklet); // channels?
|
connectToDestination(doughWorklet); // channels?
|
||||||
}
|
}
|
||||||
|
|
||||||
Pattern.prototype.supradough = function () {
|
Pattern.prototype.supradough = function () {
|
||||||
return this.onTrigger((_, hap, __, cps, begin) => {
|
return this.onTrigger((_, hap, __, cps, begin) => {
|
||||||
const { value } = hap;
|
hap.value._begin = begin;
|
||||||
// todo: could these calculations be made inside dough as well?
|
hap.value._duration = hap.duration / cps;
|
||||||
value.freq = getFrequencyFromValue(hap.value);
|
|
||||||
|
|
||||||
const release = getADSRValues(
|
|
||||||
[value.attack, value.decay, value.sustain, value.release],
|
|
||||||
'linear',
|
|
||||||
[0.001, 0.05, 0.6, 0.01],
|
|
||||||
)[3];
|
|
||||||
|
|
||||||
const duration = hap.duration / cps;
|
|
||||||
const holdEnd = begin + duration;
|
|
||||||
const end = holdEnd + release + 0.01;
|
|
||||||
value._begin = begin; // these are needed for the gate signal
|
|
||||||
value._end = end;
|
|
||||||
value._holdEnd = holdEnd;
|
|
||||||
value._holdDuration = duration + release;
|
|
||||||
|
|
||||||
if (!doughWorklet) {
|
if (!doughWorklet) {
|
||||||
initDoughWorklet();
|
initDoughWorklet();
|
||||||
}
|
}
|
||||||
doughWorklet.port.postMessage(value);
|
doughWorklet.port.postMessage(hap.value);
|
||||||
}, 1);
|
}, 1);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue