Working version of bus

This commit is contained in:
Aria
2025-12-11 11:37:20 -06:00
parent 82ca77be9d
commit 2aeba2e53f
6 changed files with 111 additions and 25 deletions
+22
View File
@@ -2046,6 +2046,28 @@ export const { octave, oct } = registerControl('octave', 'oct');
* )
*/
export const { orbit } = registerControl('orbit', 'o');
/**
* A `bus` is a send which can be used for mixing patterns. It combines with..
* s("bus") to play that bus through another pattern (for, say, applying non-linear
* effects like distortion to multiple signals)
*
* otherPat.bmod(..) (to modulate another pattern with the bus)
*
* @name bus
* @param {number | Pattern} number
*/
export const { bus } = registerControl('bus');
/**
* Postgain multiplier prior to sending the signal to the audio bus.
*
* @name busgain
* @synonyms bgain
* @param {number | Pattern} number
*/
export const { busgain, bgain } = registerControl('busgain', 'bgain');
// TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that
export const { overgain } = registerControl('overgain');
// TODO: what is this? not found in tidal doc. Similar to above, but limited to 1
+17 -15
View File
@@ -3743,18 +3743,18 @@ addConfigAlias('env', 'depthabs', 'da');
addConfigAlias('env', 'acurve', 'ac');
addConfigAlias('env', 'dcurve', 'dc');
addConfigAlias('env', 'rcurve', 'rc');
addConfigAlias('omod', 'orbit', 'o');
addConfigAlias('omod', 'target', 't');
addConfigAlias('omod', 'depth', 'dep', 'dr');
addConfigAlias('omod', 'depthabs', 'da');
addConfigAlias('omod', 'dc');
addConfigAlias('bmod', 'orbit', 'o');
addConfigAlias('bmod', 'target', 't');
addConfigAlias('bmod', 'depth', 'dep', 'dr');
addConfigAlias('bmod', 'depthabs', 'da');
addConfigAlias('bmod', 'dc');
Pattern.prototype.modulate = function (type, config, idx) {
if (config == null || typeof config !== 'object') {
return this;
}
if (!['lfo', 'env', 'omod'].includes(type)) {
logger(`[core] Modulation type ${type} not found. Please use one of 'lfo', 'env', 'omod'`);
if (!['lfo', 'env', 'bmod'].includes(type)) {
logger(`[core] Modulation type ${type} not found. Please use one of 'lfo', 'env', 'bmod'`);
return this;
}
let output = this;
@@ -3828,13 +3828,15 @@ Pattern.prototype.env = function (config, idx) {
export const env = (config) => pure({}).env(config);
/**
* Modulates with the output from a given `orbit`
* Can be called in sequence like pat.obus(...).obus(...) to set up multiple modulators
* Modulates with the output from a given `bus`.
* Can be called in sequence like pat.bmod(...).bmod(...) to set up multiple modulators
*
* @name omod
* Send to an audio bus with `otherPat.bus(..)`.
*
* @name bmod
* @memberof Pattern
* @param {Object} config Orbit bus configuration.
* @param {string | Pattern} [config.orbit] Orbit to get modulation signal from
* @param {Object} config Bus modulation configuration.
* @param {string | Pattern} [config.bus] Bus to get modulation signal from
* @param {string | Pattern} [config.target] Node (and parameter if specified like `lpf.frequency`) to modulate. Aliases: t
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
@@ -3842,7 +3844,7 @@ export const env = (config) => pure({}).env(config);
* @param {number | Pattern} [config.dc] DC offset prior to application
* @returns Pattern
*/
Pattern.prototype.omod = function (config, idx) {
return this.modulate('omod', config, idx);
Pattern.prototype.bmod = function (config, idx) {
return this.modulate('bmod', config, idx);
};
export const omod = (config) => pure({}).omod(config);
export const bmod = (config) => pure({}).bmod(config);
+14 -5
View File
@@ -163,6 +163,7 @@ let defaultDefaultValues = {
distortvol: 1,
distorttype: 0,
delay: 0,
busgain: 1,
byteBeatExpression: '0',
delayfeedback: 0.5,
delaysync: 3 / 16,
@@ -521,10 +522,10 @@ function connectEnvelope(idx, params, nodeTracker, value) {
return envNode;
}
function connectOrbitModulator(params, nodeTracker, value) {
function connectBusModulator(params, nodeTracker, value) {
const ac = getAudioContext();
const { target, depth = 1, depthabs } = params;
const signal = controller.getOrbit(params.orbit).output;
const signal = controller.getBus(params.bus).output;
const dc = new ConstantSourceNode(ac, { offset: params.dc ?? 0 });
dc.start(params.begin);
const shifted = dc.connect(gainNode(1));
@@ -628,6 +629,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
delaysync = getDefaultValue('delaysync'),
delaytime,
orbit = getDefaultValue('orbit'),
bus,
busgain = getDefaultValue('busgain'),
room,
roomfade,
roomlp,
@@ -666,6 +669,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
delay = applyGainCurve(delay);
velocity = applyGainCurve(velocity);
tremolodepth = applyGainCurve(tremolodepth);
busgain = applyGainCurve(busgain);
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
const end = t + hapDuration;
@@ -970,6 +974,11 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
const reverbSend = orbitBus.sendReverb(post, room);
audioNodes.push(reverbSend);
}
if (bus != null) {
const busNode = audioController.getBus(bus);
const busSend = effectSend(post, busNode, busgain);
audioNodes.push(busSend);
}
if (djf != null) {
nodes['djf'] = orbitBus.getDjf(djf, t);
@@ -1027,9 +1036,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
audioNodes.push(env);
}
}
if (value.omod) {
for (const p of value.omod) {
const { toCleanup } = connectOrbitModulator({ ...p, begin: t, end: endWithRelease }, nodes, value);
if (value.bmod) {
for (const p of value.bmod) {
const { toCleanup } = connectBusModulator({ ...p, begin: t, end: endWithRelease }, nodes, value);
audioNodes.push(...toCleanup);
}
}
+2
View File
@@ -72,6 +72,8 @@ const CONTROL_DATA = {
// roomfade: { param: 'room.fade', min: 0, max: 1 },
roomlp: { param: 'room.lp', min: 20, max: 24000 },
djf: { param: 'djf.value', min: 0, max: 1 },
busgain: { param: 'bus.gain', default: 1, min: 0, max: 10 },
bgain: { param: 'bus.gain', default: 1, min: 0, max: 10 },
// SYNTHS
detune: { param: 'source.detune', min: 0, max: 1 },
+20 -4
View File
@@ -2,7 +2,10 @@ import { effectSend, getWorklet, webAudioTimeout } from './helpers.mjs';
import { errorLogger } from './logger.mjs';
import { clamp } from './util.mjs';
let hasChanged = (now, before) => now !== undefined && now !== before;
const hasChanged = (now, before) => now !== undefined && now !== before;
// Node with fixed stereo channel count to prevent clicking when the input signal
// switches from mono to stereo
const getStereoNode = (ac) => new GainNode(ac, { gain: 1, channelCount: 2, channelCountMode: 'explicit' });
export class Orbit {
reverbNode;
@@ -11,10 +14,11 @@ export class Orbit {
summingNode;
djfNode;
audioContext;
constructor(audioContext) {
this.audioContext = audioContext;
this.output = new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' });
this.summingNode = new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' });
this.output = getStereoNode(audioContext);
this.summingNode = getStereoNode(audioContext);
this.summingNode.connect(this.output);
}
@@ -164,6 +168,7 @@ export class SuperdoughAudioController {
audioContext;
output;
nodes = {};
buses = {};
constructor(audioContext) {
this.audioContext = audioContext;
@@ -171,10 +176,14 @@ export class SuperdoughAudioController {
}
reset() {
Array.from(this.nodes).forEach((node) => {
Object.values(this.nodes).forEach((node) => {
node.disconnect();
});
Object.values(this.buses).forEach((bus) => {
bus.disconnect();
});
this.nodes = {};
this.buses = {};
this.output.reset();
}
@@ -206,4 +215,11 @@ export class SuperdoughAudioController {
}
return this.nodes[orbitNum];
}
getBus(busNum) {
if (this.buses[busNum] == null) {
this.buses[busNum] = getStereoNode(this.audioContext);
}
return this.buses[busNum];
}
}
+36 -1
View File
@@ -1,5 +1,5 @@
import { clamp } from './util.mjs';
import { registerSound, soundMap } from './superdough.mjs';
import { getSuperdoughAudioController, registerSound, soundMap } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
import {
applyFM,
@@ -367,6 +367,41 @@ export function registerSynthSounds() {
{ prebake: true, type: 'synth' },
);
registerSound(
'bus',
(begin, value, onended) => {
const ac = getAudioContext();
const [attack, decay, sustain, release] = getADSRValues(
[value.attack, value.decay, value.sustain, value.release],
'linear',
[0.001, 0.05, 0.6, 0.01],
);
const holdend = begin + value.duration;
const end = holdend + release + 0.01;
const bus = getSuperdoughAudioController().getBus(value.n ?? 0);
const envGain = bus.connect(gainNode(1));
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
const timeoutNode = webAudioTimeout(
ac,
() => {
bus.disconnect(envGain);
onended();
},
begin,
end,
);
return {
node: envGain,
source: bus,
stop: (time) => {
timeoutNode.stop(time);
},
};
},
{ prebake: true, type: 'input' },
);
[...noises].forEach((s) => {
registerSound(
s,