Merge branch 'main' into pump
This commit is contained in:
commit
9648e35de2
10 changed files with 142 additions and 43 deletions
|
|
@ -83,4 +83,14 @@ export default [
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Properties provided by AudioWorkletGlobalScope
|
||||||
|
files: ['packages/superdough/worklets.mjs'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
currentTime: 'readonly',
|
||||||
|
sampleRate: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import Fraction from './fraction.mjs';
|
import Fraction from './fraction.mjs';
|
||||||
|
import { stringifyValues } from './util.mjs';
|
||||||
|
|
||||||
export class Hap {
|
export class Hap {
|
||||||
/*
|
/*
|
||||||
|
|
@ -148,13 +149,7 @@ export class Hap {
|
||||||
}
|
}
|
||||||
|
|
||||||
showWhole(compact = false) {
|
showWhole(compact = false) {
|
||||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${
|
return `${this.whole == undefined ? '~' : this.whole.show()}: ${stringifyValues(this.value, compact)}`;
|
||||||
typeof this.value === 'object'
|
|
||||||
? compact
|
|
||||||
? JSON.stringify(this.value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
|
|
||||||
: JSON.stringify(this.value)
|
|
||||||
: this.value
|
|
||||||
}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
combineContext(b) {
|
combineContext(b) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
numeralArgs,
|
numeralArgs,
|
||||||
parseNumeral,
|
parseNumeral,
|
||||||
pairs,
|
pairs,
|
||||||
|
stringifyValues,
|
||||||
} from './util.mjs';
|
} from './util.mjs';
|
||||||
import drawLine from './drawLine.mjs';
|
import drawLine from './drawLine.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
@ -852,14 +853,29 @@ export class Pattern {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`, getData = (_, hap) => ({ hap })) {
|
/**
|
||||||
|
* Writes the content of the current event to the console (visible in the side menu).
|
||||||
|
* @name log
|
||||||
|
* @memberof Pattern
|
||||||
|
* @example
|
||||||
|
* s("bd sd").log()
|
||||||
|
*/
|
||||||
|
log(func = (hap) => `[hap] ${hap.showWhole(true)}`, getData = (hap) => ({ hap })) {
|
||||||
return this.onTrigger((...args) => {
|
return this.onTrigger((...args) => {
|
||||||
logger(func(...args), undefined, getData(...args));
|
logger(func(...args), undefined, getData(...args));
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
logValues(func = id) {
|
/**
|
||||||
return this.log((_, hap) => func(hap.value));
|
* A simplified version of `log` which writes all "values" (various configurable parameters)
|
||||||
|
* within the event to the console (visible in the side menu).
|
||||||
|
* @name logValues
|
||||||
|
* @memberof Pattern
|
||||||
|
* @example
|
||||||
|
* s("bd sd").gain("0.25 0.5 1").n("2 1 0").logValues()
|
||||||
|
*/
|
||||||
|
logValues(func = (value) => `[hap] ${stringifyValues(value, true)}`) {
|
||||||
|
return this.log((hap) => func(hap.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -1288,7 +1304,7 @@ export function sequenceP(pats) {
|
||||||
* @synonyms polyrhythm, pr
|
* @synonyms polyrhythm, pr
|
||||||
* @example
|
* @example
|
||||||
* stack("g3", "b3", ["e4", "d4"]).note()
|
* stack("g3", "b3", ["e4", "d4"]).note()
|
||||||
* // "g3,b3,[e4,d4]".note()
|
* // "g3,b3,[e4 d4]".note()
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* // As a chained function:
|
* // As a chained function:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||||
|
|
||||||
import Fraction from 'fraction.js';
|
import Fraction from 'fraction.js';
|
||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TimeSpan,
|
TimeSpan,
|
||||||
|
|
@ -55,6 +55,8 @@ import {
|
||||||
expand,
|
expand,
|
||||||
} from '../index.mjs';
|
} from '../index.mjs';
|
||||||
|
|
||||||
|
import { log, logValues } from '../pattern.mjs';
|
||||||
|
|
||||||
import { steady } from '../signal.mjs';
|
import { steady } from '../signal.mjs';
|
||||||
|
|
||||||
import { n, s } from '../controls.mjs';
|
import { n, s } from '../controls.mjs';
|
||||||
|
|
@ -1306,4 +1308,40 @@ describe('Pattern', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('log', () => {
|
||||||
|
it('logs to console', () => {
|
||||||
|
const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
const pattern = pure('a').log();
|
||||||
|
const haps = pattern.queryArc(0, 1);
|
||||||
|
|
||||||
|
// Force a trigger
|
||||||
|
haps.forEach((hap) => {
|
||||||
|
hap.context?.onTrigger?.(hap);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||||
|
'%c[hap] 0/1 → 1/1: a',
|
||||||
|
'background-color: black;color:white;border-radius:15px',
|
||||||
|
);
|
||||||
|
mockConsoleLog.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('logValues', () => {
|
||||||
|
it('logs values to console', () => {
|
||||||
|
const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
const pattern = pure('a').note('c#').logValues();
|
||||||
|
const haps = pattern.queryArc(0, 1);
|
||||||
|
|
||||||
|
// Force a trigger
|
||||||
|
haps.forEach((hap) => {
|
||||||
|
hap.context?.onTrigger?.(hap);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||||
|
'%c[hap] value:a note:c#',
|
||||||
|
'background-color: black;color:white;border-radius:15px',
|
||||||
|
);
|
||||||
|
mockConsoleLog.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -487,3 +487,13 @@ export function getCurrentKeyboardState() {
|
||||||
// }
|
// }
|
||||||
// return lcm((x * y) / gcd(x, y), ...z);
|
// return lcm((x * y) / gcd(x, y), ...z);
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
// Takes values -- typically derived from events, i.e. `hap`s -- and renders them
|
||||||
|
// into a readable format
|
||||||
|
export function stringifyValues(value, compact = false) {
|
||||||
|
return typeof value === 'object'
|
||||||
|
? compact
|
||||||
|
? JSON.stringify(value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
|
||||||
|
: JSON.stringify(value)
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ export function applyFM(param, value, begin) {
|
||||||
|
|
||||||
modulator = fmmod.node;
|
modulator = fmmod.node;
|
||||||
stop = fmmod.stop;
|
stop = fmmod.stop;
|
||||||
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
|
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].some((v) => v !== undefined)) {
|
||||||
// no envelope by default
|
// no envelope by default
|
||||||
modulator.connect(param);
|
modulator.connect(param);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -201,10 +201,7 @@ export function registerSynthSounds() {
|
||||||
const gainAdjustment = 1 / Math.sqrt(voices);
|
const gainAdjustment = 1 / Math.sqrt(voices);
|
||||||
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
||||||
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
|
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
|
||||||
// const fm = applyFM(o.parameters.get('frequency'), value, begin);
|
const fm = applyFM(o.parameters.get('frequency'), value, begin);
|
||||||
// https://codeberg.org/uzu/strudel/issues/1428
|
|
||||||
// if you think about re-enabling this, please test with fm > 1 first
|
|
||||||
// it's like 10x gain, so it's really dangerous
|
|
||||||
let envGain = gainNode(1);
|
let envGain = gainNode(1);
|
||||||
envGain = o.connect(envGain);
|
envGain = o.connect(envGain);
|
||||||
|
|
||||||
|
|
@ -216,7 +213,7 @@ export function registerSynthSounds() {
|
||||||
destroyAudioWorkletNode(o);
|
destroyAudioWorkletNode(o);
|
||||||
envGain.disconnect();
|
envGain.disconnect();
|
||||||
onended();
|
onended();
|
||||||
// fm?.stop();
|
fm?.stop();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
},
|
},
|
||||||
begin,
|
begin,
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,28 @@ import FFT from './fft.js';
|
||||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
const _mod = (n, m) => ((n % m) + m) % m;
|
const _mod = (n, m) => ((n % m) + m) % m;
|
||||||
|
|
||||||
|
// Restrict phase to the range [0, maxPhase) via wrapping
|
||||||
|
function wrapPhase(phase, maxPhase = 1) {
|
||||||
|
if (phase >= maxPhase) {
|
||||||
|
phase -= maxPhase;
|
||||||
|
} else if (phase < 0) {
|
||||||
|
phase += maxPhase;
|
||||||
|
}
|
||||||
|
return phase;
|
||||||
|
}
|
||||||
const blockSize = 128;
|
const blockSize = 128;
|
||||||
// adjust waveshape to remove frequencies above nyquist to prevent aliasing
|
// Smooth waveshape near discontinuities to remove frequencies above Nyquist and prevent aliasing
|
||||||
// referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517
|
// referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517
|
||||||
function polyBlep(phase, dt) {
|
function polyBlep(phase, dt) {
|
||||||
// 0 <= phase < 1
|
dt = Math.min(dt, 1 - dt);
|
||||||
|
// Start of cycle
|
||||||
if (phase < dt) {
|
if (phase < dt) {
|
||||||
phase /= dt;
|
phase /= dt;
|
||||||
// 2 * (phase - phase^2/2 - 0.5)
|
// 2 * (phase - phase^2/2 - 0.5)
|
||||||
return phase + phase - phase * phase - 1;
|
return phase + phase - phase * phase - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -1 < phase < 0
|
// End of cycle
|
||||||
else if (phase > 1 - dt) {
|
else if (phase > 1 - dt) {
|
||||||
phase = (phase - 1) / dt;
|
phase = (phase - 1) / dt;
|
||||||
// 2 * (phase^2/2 + phase + 0.5)
|
// 2 * (phase^2/2 + phase + 0.5)
|
||||||
|
|
@ -115,7 +125,6 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||||
|
|
||||||
process(inputs, outputs, parameters) {
|
process(inputs, outputs, parameters) {
|
||||||
const begin = parameters['begin'][0];
|
const begin = parameters['begin'][0];
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
if (currentTime >= parameters.end[0]) {
|
if (currentTime >= parameters.end[0]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +152,6 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||||
if (this.phase == null) {
|
if (this.phase == null) {
|
||||||
this.phase = _mod(time * frequency + phaseoffset, 1);
|
this.phase = _mod(time * frequency + phaseoffset, 1);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const dt = frequency / sampleRate;
|
const dt = frequency / sampleRate;
|
||||||
for (let n = 0; n < blockSize; n++) {
|
for (let n = 0; n < blockSize; n++) {
|
||||||
for (let i = 0; i < output.length; i++) {
|
for (let i = 0; i < output.length; i++) {
|
||||||
|
|
@ -305,7 +313,6 @@ class LadderProcessor extends AudioWorkletProcessor {
|
||||||
const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000);
|
const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000);
|
||||||
|
|
||||||
let cutoff = parameters.frequency[0];
|
let cutoff = parameters.frequency[0];
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
cutoff = (cutoff * 2 * _PI) / sampleRate;
|
cutoff = (cutoff * 2 * _PI) / sampleRate;
|
||||||
cutoff = cutoff > 1 ? 1 : cutoff;
|
cutoff = cutoff > 1 ? 1 : cutoff;
|
||||||
|
|
||||||
|
|
@ -438,18 +445,13 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
process(input, outputs, params) {
|
process(input, outputs, params) {
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
if (currentTime <= params.begin[0]) {
|
if (currentTime <= params.begin[0]) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
if (currentTime >= params.end[0]) {
|
if (currentTime >= params.end[0]) {
|
||||||
// this.port.postMessage({ type: 'onended' });
|
// this.port.postMessage({ type: 'onended' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let frequency = params.frequency[0];
|
|
||||||
//apply detune in cents
|
|
||||||
frequency = frequency * Math.pow(2, params.detune[0] / 1200);
|
|
||||||
|
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
const voices = params.voices[0];
|
const voices = params.voices[0];
|
||||||
|
|
@ -460,9 +462,6 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
|
|
||||||
for (let n = 0; n < voices; n++) {
|
for (let n = 0; n < voices; n++) {
|
||||||
const isOdd = (n & 1) == 1;
|
const isOdd = (n & 1) == 1;
|
||||||
|
|
||||||
//applies unison "spread" detune in semitones
|
|
||||||
const freq = applySemitoneDetuneToFrequency(frequency, getUnisonDetune(voices, freqspread, n));
|
|
||||||
let gainL = gain1;
|
let gainL = gain1;
|
||||||
let gainR = gain2;
|
let gainR = gain2;
|
||||||
// invert right and left gain
|
// invert right and left gain
|
||||||
|
|
@ -470,21 +469,21 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
gainL = gain2;
|
gainL = gain2;
|
||||||
gainR = gain1;
|
gainR = gain1;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const dt = freq / sampleRate;
|
|
||||||
|
|
||||||
for (let i = 0; i < output[0].length; i++) {
|
for (let i = 0; i < output[0].length; i++) {
|
||||||
|
// Main detuning
|
||||||
|
let freq = applySemitoneDetuneToFrequency(params.frequency[i] ?? params.frequency[0], params.detune[0] / 100);
|
||||||
|
// Individual voice detuning
|
||||||
|
freq = applySemitoneDetuneToFrequency(freq, getUnisonDetune(voices, freqspread, n));
|
||||||
|
// We must wrap this here because it is passed into sawblep below which
|
||||||
|
// has domain [0, 1]
|
||||||
|
const dt = _mod(freq / sampleRate, 1);
|
||||||
this.phase[n] = this.phase[n] ?? Math.random();
|
this.phase[n] = this.phase[n] ?? Math.random();
|
||||||
const v = waveshapes.sawblep(this.phase[n], dt);
|
const v = waveshapes.sawblep(this.phase[n], dt);
|
||||||
|
|
||||||
output[0][i] = output[0][i] + v * gainL;
|
output[0][i] = output[0][i] + v * gainL;
|
||||||
output[1][i] = output[1][i] + v * gainR;
|
output[1][i] = output[1][i] + v * gainR;
|
||||||
|
|
||||||
this.phase[n] += dt;
|
this.phase[n] = wrapPhase(this.phase[n] + dt);
|
||||||
|
|
||||||
if (this.phase[n] > 1.0) {
|
|
||||||
this.phase[n] = this.phase[n] - 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -493,7 +492,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
|
|
||||||
registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor);
|
registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor);
|
||||||
|
|
||||||
// Phase Vocoder sourced from // sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
// Phase Vocoder sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
||||||
const BUFFERED_BLOCK_SIZE = 2048;
|
const BUFFERED_BLOCK_SIZE = 2048;
|
||||||
|
|
||||||
function genHannWindow(length) {
|
function genHannWindow(length) {
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ function scaleOffset(scale, offset, note) {
|
||||||
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
|
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const transpose = register(['transpose', 'trans'], function transposeFn(intervalOrSemitones, pat) {
|
export const { transpose, trans } = register(['transpose', 'trans'], function transposeFn(intervalOrSemitones, pat) {
|
||||||
return pat.withHap((hap) => {
|
return pat.withHap((hap) => {
|
||||||
const note = hap.value.note ?? hap.value;
|
const note = hap.value.note ?? hap.value;
|
||||||
if (typeof note === 'number') {
|
if (typeof note === 'number') {
|
||||||
|
|
@ -151,7 +151,7 @@ export const transpose = register(['transpose', 'trans'], function transposeFn(i
|
||||||
* .note()
|
* .note()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const scaleTranspose = register(
|
export const { scaleTranspose, scaleTrans, strans } = register(
|
||||||
['scaleTranspose', 'scaleTrans', 'strans'],
|
['scaleTranspose', 'scaleTrans', 'strans'],
|
||||||
function (offset /* : number | string */, pat) {
|
function (offset /* : number | string */, pat) {
|
||||||
return pat.withHap((hap) => {
|
return pat.withHap((hap) => {
|
||||||
|
|
|
||||||
|
|
@ -5456,6 +5456,40 @@ exports[`runs examples > example "lock" example index 0 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "log" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/2 | s:bd ]",
|
||||||
|
"[ 1/2 → 1/1 | s:sd ]",
|
||||||
|
"[ 1/1 → 3/2 | s:bd ]",
|
||||||
|
"[ 3/2 → 2/1 | s:sd ]",
|
||||||
|
"[ 2/1 → 5/2 | s:bd ]",
|
||||||
|
"[ 5/2 → 3/1 | s:sd ]",
|
||||||
|
"[ 3/1 → 7/2 | s:bd ]",
|
||||||
|
"[ 7/2 → 4/1 | s:sd ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "logValues" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ (0/1 → 1/3) ⇝ 1/2 | s:bd gain:0.25 n:2 ]",
|
||||||
|
"[ 0/1 ⇜ (1/3 → 1/2) | s:bd gain:0.5 n:1 ]",
|
||||||
|
"[ (1/2 → 2/3) ⇝ 1/1 | s:sd gain:0.5 n:1 ]",
|
||||||
|
"[ 1/2 ⇜ (2/3 → 1/1) | s:sd gain:1 n:0 ]",
|
||||||
|
"[ (1/1 → 4/3) ⇝ 3/2 | s:bd gain:0.25 n:2 ]",
|
||||||
|
"[ 1/1 ⇜ (4/3 → 3/2) | s:bd gain:0.5 n:1 ]",
|
||||||
|
"[ (3/2 → 5/3) ⇝ 2/1 | s:sd gain:0.5 n:1 ]",
|
||||||
|
"[ 3/2 ⇜ (5/3 → 2/1) | s:sd gain:1 n:0 ]",
|
||||||
|
"[ (2/1 → 7/3) ⇝ 5/2 | s:bd gain:0.25 n:2 ]",
|
||||||
|
"[ 2/1 ⇜ (7/3 → 5/2) | s:bd gain:0.5 n:1 ]",
|
||||||
|
"[ (5/2 → 8/3) ⇝ 3/1 | s:sd gain:0.5 n:1 ]",
|
||||||
|
"[ 5/2 ⇜ (8/3 → 3/1) | s:sd gain:1 n:0 ]",
|
||||||
|
"[ (3/1 → 10/3) ⇝ 7/2 | s:bd gain:0.25 n:2 ]",
|
||||||
|
"[ 3/1 ⇜ (10/3 → 7/2) | s:bd gain:0.5 n:1 ]",
|
||||||
|
"[ (7/2 → 11/3) ⇝ 4/1 | s:sd gain:0.5 n:1 ]",
|
||||||
|
"[ 7/2 ⇜ (11/3 → 4/1) | s:sd gain:1 n:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "loop" example index 0 1`] = `
|
exports[`runs examples > example "loop" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/1 | s:casio loop:1 ]",
|
"[ 0/1 → 1/1 | s:casio loop:1 ]",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue