mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-29 08:01:50 -04:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae202ac5bb | |||
| d7bf5db8be | |||
| 48231ea12a | |||
| c8a442234e | |||
| efda32eedb | |||
| 95d04d1149 | |||
| 358b1e3f56 | |||
| fe7e8f7add | |||
| 45d329d3d1 | |||
| 5e99393f5a | |||
| 9650c8eb50 | |||
| fab8d2193d | |||
| 150835aa4d | |||
| 76237016a3 | |||
| 6f4d8ebacf | |||
| 3cecf4a50e | |||
| 16b5faf333 | |||
| c4826b7dfb | |||
| bd61c1dc7e | |||
| ceb3aa797d | |||
| 9b3277da1c | |||
| 0b711e879c | |||
| e01621d0cc | |||
| 196f70ab6b | |||
| 1d4f2b81d8 | |||
| d28c8dac8e | |||
| eb18648a2a | |||
| 0ec9826de5 | |||
| af7855402f | |||
| 68b1cb87ca | |||
| b65cc2128f | |||
| a82ebbeef6 | |||
| d4a1b571fd | |||
| d44f6d3ded | |||
| 3b8f309a22 | |||
| 39e7f70079 | |||
| 7e5a907ce5 | |||
| d7afb8a6c0 | |||
| a278f21f1b | |||
| 36587ae747 | |||
| 6c01c16a0c | |||
| be36898803 | |||
| df047fd48d | |||
| 68840de188 | |||
| 020a6bc75f | |||
| 7bc6fcbf2a | |||
| 654c1a86f0 | |||
| ac3f477f30 | |||
| 573cd1ae41 | |||
| 4c49cd9297 | |||
| 70d2da81d8 | |||
| bd73d96847 | |||
| 7cf9db4872 | |||
| c9381a11f9 |
@@ -1,27 +1,16 @@
|
|||||||
name: Strudel test + warm deploy
|
name: Strudel tests
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-and-warm-deploy:
|
build:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
env:
|
|
||||||
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20]
|
node-version: [20]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Debug Forgejo context
|
|
||||||
run: |
|
|
||||||
echo "hmm"
|
|
||||||
echo "EVENT_NAME = ${{ forgejo.event_name }}"
|
|
||||||
echo "REF = ${{ forgejo.ref }}"
|
|
||||||
echo "REF_NAME = ${{ forgejo.ref_name }}"
|
|
||||||
echo "EVENT_NAME = $FORGEJO_EVENT_NAME"
|
|
||||||
echo "REF = $FORGEJO_REF"
|
|
||||||
echo "REF_NAME = $FORGEJO_REF_NAME"
|
|
||||||
- name: apt install ztd
|
- name: apt install ztd
|
||||||
run: apt update && apt install -y zstd
|
run: apt update && apt install -y zstd
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
@@ -35,16 +24,3 @@ jobs:
|
|||||||
- run: pnpm run format-check
|
- run: pnpm run format-check
|
||||||
- run: pnpm run lint
|
- run: pnpm run lint
|
||||||
- run: pnpm test
|
- run: pnpm test
|
||||||
- name: Build
|
|
||||||
if: ${{ forgejo.event_name == 'push' && forgejo.ref == 'refs/heads/main' }}
|
|
||||||
run: pnpm build
|
|
||||||
- name: Warm deploy
|
|
||||||
# automatically deploy to warm on pushes to main
|
|
||||||
if: ${{ forgejo.event_name == 'push' && forgejo.ref == 'refs/heads/main' }}
|
|
||||||
run: |
|
|
||||||
eval $(ssh-agent -s)
|
|
||||||
echo "$SSH_PRIVATE_KEY" | ssh-add -
|
|
||||||
apt update && apt install -y rsync
|
|
||||||
mkdir ~/.ssh
|
|
||||||
ssh-keyscan matrix.toplap.org > ~/.ssh/known_hosts
|
|
||||||
rsync -atv --delete --delete-after --progress ./website/dist/ strudel@matrix.toplap.org:/home/strudel/deploy/warm.strudel.cc
|
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ export class StrudelMirror {
|
|||||||
this.onDraw = onDraw || this.draw;
|
this.onDraw = onDraw || this.draw;
|
||||||
this.id = id || s4();
|
this.id = id || s4();
|
||||||
this.solo = solo;
|
this.solo = solo;
|
||||||
|
this.hasPainters = false;
|
||||||
|
|
||||||
this.drawer = new Drawer((haps, time, _, painters) => {
|
this.drawer = new Drawer((haps, time, _, painters) => {
|
||||||
const currentFrame = haps.filter((hap) => hap.isActive(time));
|
const currentFrame = haps.filter((hap) => hap.isActive(time));
|
||||||
@@ -177,7 +178,7 @@ export class StrudelMirror {
|
|||||||
onToggle: (started) => {
|
onToggle: (started) => {
|
||||||
replOptions?.onToggle?.(started);
|
replOptions?.onToggle?.(started);
|
||||||
if (started) {
|
if (started) {
|
||||||
this.drawer.start(this.repl.scheduler);
|
this.shouldAnimate() && this.drawer.start(this.repl.scheduler);
|
||||||
if (this.solo) {
|
if (this.solo) {
|
||||||
// stop other repls when this one is started
|
// stop other repls when this one is started
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
@@ -207,9 +208,10 @@ export class StrudelMirror {
|
|||||||
updateWidgets(this.editor, widgets);
|
updateWidgets(this.editor, widgets);
|
||||||
updateMiniLocations(this.editor, this.miniLocations);
|
updateMiniLocations(this.editor, this.miniLocations);
|
||||||
replOptions?.afterEval?.(options);
|
replOptions?.afterEval?.(options);
|
||||||
|
this.hasPainters = options.pattern.getPainters().length > 0;
|
||||||
// if no painters are set (.onPaint was not called), then we only need
|
// if no painters are set (.onPaint was not called), then we only need
|
||||||
// the present moment (for highlighting)
|
// the present moment (for highlighting)
|
||||||
const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0];
|
const drawTime = this.hasPainters ? this.drawTime : [0, 0];
|
||||||
this.drawer.setDrawTime(drawTime);
|
this.drawer.setDrawTime(drawTime);
|
||||||
// invalidate drawer after we've set the appropriate drawTime
|
// invalidate drawer after we've set the appropriate drawTime
|
||||||
this.drawer.invalidate(this.repl.scheduler);
|
this.drawer.invalidate(this.repl.scheduler);
|
||||||
@@ -237,6 +239,7 @@ export class StrudelMirror {
|
|||||||
cmEditor.style.backgroundColor = 'transparent';
|
cmEditor.style.backgroundColor = 'transparent';
|
||||||
}
|
}
|
||||||
const settings = codemirrorSettings.get();
|
const settings = codemirrorSettings.get();
|
||||||
|
this.isPatternHighlightingEnabled = parseBooleans(settings.isPatternHighlightingEnabled);
|
||||||
this.setFontSize(settings.fontSize);
|
this.setFontSize(settings.fontSize);
|
||||||
this.setFontFamily(settings.fontFamily);
|
this.setFontFamily(settings.fontFamily);
|
||||||
|
|
||||||
@@ -321,8 +324,17 @@ export class StrudelMirror {
|
|||||||
flash(this.editor, ms);
|
flash(this.editor, ms);
|
||||||
}
|
}
|
||||||
highlight(haps, time) {
|
highlight(haps, time) {
|
||||||
|
if (!this.isPatternHighlightingEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
highlightMiniLocations(this.editor, time, haps);
|
highlightMiniLocations(this.editor, time, haps);
|
||||||
}
|
}
|
||||||
|
shouldAnimate() {
|
||||||
|
return (
|
||||||
|
this.repl.scheduler.started &&
|
||||||
|
(this.isPatternHighlightingEnabled || this.hasPainters || this.onDraw !== this.draw)
|
||||||
|
);
|
||||||
|
}
|
||||||
setFontSize(size) {
|
setFontSize(size) {
|
||||||
this.root.style.fontSize = size + 'px';
|
this.root.style.fontSize = size + 'px';
|
||||||
}
|
}
|
||||||
@@ -343,6 +355,11 @@ export class StrudelMirror {
|
|||||||
this.editor.dispatch({
|
this.editor.dispatch({
|
||||||
effects: compartments[key].reconfigure(newValue),
|
effects: compartments[key].reconfigure(newValue),
|
||||||
});
|
});
|
||||||
|
if (key === 'isPatternHighlightingEnabled') {
|
||||||
|
this.isPatternHighlightingEnabled = value;
|
||||||
|
this.drawer.stop();
|
||||||
|
this.shouldAnimate() && this.drawer.start(this.repl.scheduler);
|
||||||
|
}
|
||||||
if (key === 'theme') {
|
if (key === 'theme') {
|
||||||
activateTheme(value);
|
activateTheme(value);
|
||||||
}
|
}
|
||||||
|
|||||||
+194
-23
@@ -72,6 +72,27 @@ export function registerControl(names, ...aliases) {
|
|||||||
return bag;
|
return bag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function registerMultiControl(names, maxControls, ...aliases) {
|
||||||
|
names = Array.isArray(names) ? names : [names];
|
||||||
|
let bag = {};
|
||||||
|
for (let i = 1; i <= maxControls; i++) {
|
||||||
|
let theseAliases = [...aliases];
|
||||||
|
let theseNames = [...names];
|
||||||
|
if (i === 1) {
|
||||||
|
// adds e.g. fm1 as an alias for fm
|
||||||
|
const aliases1 = theseAliases.map((a) => `${a}1`);
|
||||||
|
const names1 = theseNames.map((n) => `${n}1`);
|
||||||
|
theseAliases = theseAliases.concat(aliases1).concat(names1);
|
||||||
|
} else {
|
||||||
|
theseAliases = theseAliases.map((a) => `${a}${i}`);
|
||||||
|
theseNames = theseNames.map((n) => `${n}${i}`);
|
||||||
|
}
|
||||||
|
const subBag = registerControl(theseNames, ...theseAliases);
|
||||||
|
bag = { ...bag, ...subBag };
|
||||||
|
}
|
||||||
|
return bag;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters
|
* Select a sound / sample by name. When using mininotation, you can also optionally supply 'n' and 'gain' parameters
|
||||||
* separated by ':'.
|
* separated by ':'.
|
||||||
@@ -380,12 +401,13 @@ export const { accelerate } = registerControl('accelerate');
|
|||||||
* Sets the velocity from 0 to 1. Is multiplied together with gain.
|
* Sets the velocity from 0 to 1. Is multiplied together with gain.
|
||||||
*
|
*
|
||||||
* @name velocity
|
* @name velocity
|
||||||
|
* @synonyms vel
|
||||||
* @example
|
* @example
|
||||||
* s("hh*8")
|
* s("hh*8")
|
||||||
* .gain(".4!2 1 .4!2 1 .4 1")
|
* .gain(".4!2 1 .4!2 1 .4 1")
|
||||||
* .velocity(".4 1")
|
* .velocity(".4 1")
|
||||||
*/
|
*/
|
||||||
export const { velocity } = registerControl('velocity');
|
export const { velocity, vel } = registerControl('velocity', 'vel');
|
||||||
/**
|
/**
|
||||||
* Controls the gain by an exponential amount.
|
* Controls the gain by an exponential amount.
|
||||||
*
|
*
|
||||||
@@ -435,6 +457,9 @@ export const { attack, att } = registerControl('attack', 'att');
|
|||||||
* Whole numbers and simple ratios sound more natural,
|
* Whole numbers and simple ratios sound more natural,
|
||||||
* while decimal numbers and complex ratios sound metallic.
|
* while decimal numbers and complex ratios sound metallic.
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the harmonicity of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmh2`)
|
||||||
|
*
|
||||||
* @name fmh
|
* @name fmh
|
||||||
* @param {number | Pattern} harmonicity
|
* @param {number | Pattern} harmonicity
|
||||||
* @example
|
* @example
|
||||||
@@ -444,25 +469,40 @@ export const { attack, att } = registerControl('attack', 'att');
|
|||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh');
|
export const { fmh, fmh1, fmh2, fmh3, fmh4, fmh5, fmh6, fmh7, fmh8 } = registerMultiControl(['fmh', 'fmi'], 8, 'fmh');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the Frequency Modulation of the synth.
|
* Sets the Frequency Modulation of the synth.
|
||||||
* Controls the modulation index, which defines the brightness of the sound.
|
* Controls the modulation index, which defines the brightness of the sound.
|
||||||
*
|
*
|
||||||
* @name fm
|
* A number may be added afterwards to control the modulation index of
|
||||||
|
* any of the 8 individual FMs (e.g. `fm3`). Also, FMs may be routed into
|
||||||
|
* each other with matrix commands like `fm13`, which would send `fm1` back into
|
||||||
|
* `fm3`
|
||||||
|
*
|
||||||
|
* @name fmi
|
||||||
* @param {number | Pattern} brightness modulation index
|
* @param {number | Pattern} brightness modulation index
|
||||||
* @synonyms fmi
|
* @synonyms fm
|
||||||
* @example
|
* @example
|
||||||
* note("c e g b g e")
|
* note("c e g b g e")
|
||||||
* .fm("<0 1 2 8 32>")
|
* .fm("<0 1 2 8 32>")
|
||||||
* ._scope()
|
* ._scope()
|
||||||
|
* @example
|
||||||
|
* s("sine").note("F1").seg(8)
|
||||||
|
* .fm(4).fm2(rand.mul(4)).fm3(saw.mul(8).slow(8))
|
||||||
|
* .fmh(1.06).fmh2(10).fmh3(0.1)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm');
|
export const { fmi, fmi1, fmi2, fmi3, fmi4, fmi5, fmi6, fmi7, fmi8, fm, fm1, fm2, fm3, fm4, fm5, fm6, fm7, fm8 } =
|
||||||
|
registerMultiControl(['fmi', 'fmh'], 8, 'fm');
|
||||||
|
|
||||||
// fm envelope
|
// fm envelope
|
||||||
/**
|
/**
|
||||||
* Ramp type of fm envelope. Exp might be a bit broken..
|
* Ramp type of fm envelope. Exp might be a bit broken..
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the envelope of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmenv4`)
|
||||||
|
*
|
||||||
* @name fmenv
|
* @name fmenv
|
||||||
* @param {number | Pattern} type lin | exp
|
* @param {number | Pattern} type lin | exp
|
||||||
* @example
|
* @example
|
||||||
@@ -474,11 +514,19 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm');
|
|||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmenv } = registerControl('fmenv');
|
export const { fmenv, fmenv1, fmenv2, fmenv3, fmenv4, fmenv5, fmenv6, fmenv7, fmenv8 } = registerMultiControl(
|
||||||
|
'fmenv',
|
||||||
|
8,
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attack time for the FM envelope: time it takes to reach maximum modulation
|
* Attack time for the FM envelope: time it takes to reach maximum modulation
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the attack of the envelope of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmatt5`)
|
||||||
|
*
|
||||||
* @name fmattack
|
* @name fmattack
|
||||||
|
* @synonyms fmatt
|
||||||
* @param {number | Pattern} time attack time
|
* @param {number | Pattern} time attack time
|
||||||
* @example
|
* @example
|
||||||
* note("c e g b g e")
|
* note("c e g b g e")
|
||||||
@@ -487,11 +535,33 @@ export const { fmenv } = registerControl('fmenv');
|
|||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmattack } = registerControl('fmattack');
|
export const {
|
||||||
|
fmattack,
|
||||||
|
fmattack1,
|
||||||
|
fmattack2,
|
||||||
|
fmattack3,
|
||||||
|
fmattack4,
|
||||||
|
fmattack5,
|
||||||
|
fmattack6,
|
||||||
|
fmattack7,
|
||||||
|
fmattack8,
|
||||||
|
fmatt,
|
||||||
|
fmatt1,
|
||||||
|
fmatt2,
|
||||||
|
fmatt3,
|
||||||
|
fmatt4,
|
||||||
|
fmatt5,
|
||||||
|
fmatt6,
|
||||||
|
fmatt7,
|
||||||
|
fmatt8,
|
||||||
|
} = registerMultiControl('fmattack', 8, 'fmatt');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waveform of the fm modulator
|
* Waveform of the fm modulator
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the waveform
|
||||||
|
* any of the 8 individual FMs (e.g. `fmwave6`)
|
||||||
|
*
|
||||||
* @name fmwave
|
* @name fmwave
|
||||||
* @param {number | Pattern} wave waveform
|
* @param {number | Pattern} wave waveform
|
||||||
* @example
|
* @example
|
||||||
@@ -500,12 +570,19 @@ export const { fmattack } = registerControl('fmattack');
|
|||||||
* n("0 1 2 3".fast(4)).chord("<Dm Am F G>").voicing().s("sawtooth").fmwave("brown").fm(.6)
|
* n("0 1 2 3".fast(4)).chord("<Dm Am F G>").voicing().s("sawtooth").fmwave("brown").fm(.6)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmwave } = registerControl('fmwave');
|
export const { fmwave, fmwave1, fmwave2, fmwave3, fmwave4, fmwave5, fmwave6, fmwave7, fmwave8 } = registerMultiControl(
|
||||||
|
'fmwave',
|
||||||
|
8,
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
|
* Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the decay of the envelope of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmdec6`)
|
||||||
|
*
|
||||||
* @name fmdecay
|
* @name fmdecay
|
||||||
|
* @synonyms fmdec
|
||||||
* @param {number | Pattern} time decay time
|
* @param {number | Pattern} time decay time
|
||||||
* @example
|
* @example
|
||||||
* note("c e g b g e")
|
* note("c e g b g e")
|
||||||
@@ -515,11 +592,35 @@ export const { fmwave } = registerControl('fmwave');
|
|||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmdecay } = registerControl('fmdecay');
|
export const {
|
||||||
|
fmdecay,
|
||||||
|
fmdecay1,
|
||||||
|
fmdecay2,
|
||||||
|
fmdecay3,
|
||||||
|
fmdecay4,
|
||||||
|
fmdecay5,
|
||||||
|
fmdecay6,
|
||||||
|
fmdecay7,
|
||||||
|
fmdecay8,
|
||||||
|
fmdec,
|
||||||
|
fmdec1,
|
||||||
|
fmdec2,
|
||||||
|
fmdec3,
|
||||||
|
fmdec4,
|
||||||
|
fmdec5,
|
||||||
|
fmdec6,
|
||||||
|
fmdec7,
|
||||||
|
fmdec8,
|
||||||
|
} = registerMultiControl('fmdecay', 8, 'fmdec');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sustain level for the FM envelope: how much modulation is applied after the decay phase
|
* Sustain level for the FM envelope: how much modulation is applied after the decay phase
|
||||||
*
|
*
|
||||||
|
* A number may be added afterwards to control the sustain of the envelope of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmsus7`)
|
||||||
|
*
|
||||||
* @name fmsustain
|
* @name fmsustain
|
||||||
|
* @synonyms fmsus
|
||||||
* @param {number | Pattern} level sustain level
|
* @param {number | Pattern} level sustain level
|
||||||
* @example
|
* @example
|
||||||
* note("c e g b g e")
|
* note("c e g b g e")
|
||||||
@@ -529,10 +630,69 @@ export const { fmdecay } = registerControl('fmdecay');
|
|||||||
* ._scope()
|
* ._scope()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmsustain } = registerControl('fmsustain');
|
export const {
|
||||||
// these are not really useful... skipping for now
|
fmsustain,
|
||||||
export const { fmrelease } = registerControl('fmrelease');
|
fmsustain1,
|
||||||
export const { fmvelocity } = registerControl('fmvelocity');
|
fmsustain2,
|
||||||
|
fmsustain3,
|
||||||
|
fmsustain4,
|
||||||
|
fmsustain5,
|
||||||
|
fmsustain6,
|
||||||
|
fmsustain7,
|
||||||
|
fmsustain8,
|
||||||
|
fmsus,
|
||||||
|
fmsus1,
|
||||||
|
fmsus2,
|
||||||
|
fmsus3,
|
||||||
|
fmsus4,
|
||||||
|
fmsus5,
|
||||||
|
fmsus6,
|
||||||
|
fmsus7,
|
||||||
|
fmsus8,
|
||||||
|
} = registerMultiControl('fmsustain', 8, 'fmsus');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release time for the FM envelope: how much modulation is applied after the note is released
|
||||||
|
*
|
||||||
|
* A number may be added afterwards to control the release of the envelope of
|
||||||
|
* any of the 8 individual FMs (e.g. `fmrel8`)
|
||||||
|
*
|
||||||
|
* @name fmrelease
|
||||||
|
* @synonyms fmrel
|
||||||
|
* @param {number | Pattern} time release time
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const {
|
||||||
|
fmrelease,
|
||||||
|
fmrelease1,
|
||||||
|
fmrelease2,
|
||||||
|
fmrelease3,
|
||||||
|
fmrelease4,
|
||||||
|
fmrelease5,
|
||||||
|
fmrelease6,
|
||||||
|
fmrelease7,
|
||||||
|
fmrelease8,
|
||||||
|
fmrel,
|
||||||
|
fmrel1,
|
||||||
|
fmrel2,
|
||||||
|
fmrel3,
|
||||||
|
fmrel4,
|
||||||
|
fmrel5,
|
||||||
|
fmrel6,
|
||||||
|
fmrel7,
|
||||||
|
fmrel8,
|
||||||
|
} = registerMultiControl('fmrelease', 8, 'fmrel');
|
||||||
|
|
||||||
|
// FM Matrix
|
||||||
|
// Note: we do not declare top-level exports here since it would add
|
||||||
|
// ~162 more explicit exports. This is likely fine as the most common use-case would be to at least
|
||||||
|
// declare one other FM prior to utilizing the matrix functionality, but if we ever decide we need it,
|
||||||
|
// TODO to add it explicitly / go with the globalThis approach
|
||||||
|
for (let i = 0; i <= 8; i++) {
|
||||||
|
for (let j = 0; j <= 8; j++) {
|
||||||
|
registerControl(`fmi${i}${j}`, `fm${i}${j}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
|
* Select the sound bank to use. To be used together with `s`. The bank name (+ "_") will be prepended to the value of `s`.
|
||||||
@@ -1323,14 +1483,13 @@ export const { lpdepth } = registerControl('lpdepth');
|
|||||||
* Depth of the LFO for the lowpass filter, in HZ
|
* Depth of the LFO for the lowpass filter, in HZ
|
||||||
*
|
*
|
||||||
* @name lpdepthfrequency
|
* @name lpdepthfrequency
|
||||||
* @synonyms
|
* @synonyms lpdepthfreq
|
||||||
* lpdethfreq
|
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
* @example
|
* @example
|
||||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepthfrequency("<200 500 100 0>")
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepthfrequency("<200 500 100 0>")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const { lpdepthfrequency } = registerControl('lpdepthfrequency', 'lpdepthfreq');
|
export const { lpdepthfrequency, lpdepthfreq } = registerControl('lpdepthfrequency', 'lpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the lowpass filter
|
* Shape of the LFO for the lowpass filter
|
||||||
@@ -1384,14 +1543,13 @@ export const { bpdepth } = registerControl('bpdepth');
|
|||||||
* Depth of the LFO for the bandpass filter, in HZ
|
* Depth of the LFO for the bandpass filter, in HZ
|
||||||
*
|
*
|
||||||
* @name bpdepthfrequency
|
* @name bpdepthfrequency
|
||||||
* @synonyms
|
* @synonyms bpdepthfreq
|
||||||
* bpdethfreq
|
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
* @example
|
* @example
|
||||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).bpdepthfrequency("<200 500 100 0>")
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).bpdepthfrequency("<200 500 100 0>")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const { bpdepthfrequency } = registerControl('bpdepthfrequency', 'bpdepthfreq');
|
export const { bpdepthfrequency, bpdepthfreq } = registerControl('bpdepthfrequency', 'bpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the bandpass filter
|
* Shape of the LFO for the bandpass filter
|
||||||
@@ -1439,20 +1597,19 @@ export const { hpsync } = registerControl('hpsync');
|
|||||||
* @name hpdepth
|
* @name hpdepth
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
*/
|
*/
|
||||||
export const { hpdepth, hpdepthfreq } = registerControl('hpdepth');
|
export const { hpdepth } = registerControl('hpdepth');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Depth of the LFO for the hipass filter, in hz
|
* Depth of the LFO for the hipass filter, in hz
|
||||||
*
|
*
|
||||||
* @name hpdepthfrequency
|
* @name hpdepthfrequency
|
||||||
* @synonyms
|
* @synonyms hpdepthfreq
|
||||||
* hpdethfreq
|
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
* @example
|
* @example
|
||||||
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).hpdepthfrequency("<200 500 100 0>")
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).hpdepthfrequency("<200 500 100 0>")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const { hpdepthfrequency } = registerControl('hpdepthfrequency', 'hpdepthfreq');
|
export const { hpdepthfrequency, hpdepthfreq } = registerControl('hpdepthfrequency', 'hpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the highpass filter
|
* Shape of the LFO for the highpass filter
|
||||||
@@ -2629,3 +2786,17 @@ export const scrub = register(
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transient shaper. Gives independent control over the emphasis on transients
|
||||||
|
* and sustains
|
||||||
|
*
|
||||||
|
* @name transient
|
||||||
|
* @param {number | Pattern} attack Emphasis on transients; between -1 (deaccentuate) and 1 (accentuate)
|
||||||
|
* @param {number | Pattern} sustain Emphasis on the sustains; between -1 (deaccentuate) and 1 (accentuate)
|
||||||
|
* @example
|
||||||
|
* s("bd").transient("<-1 -0.5 0 0.5 1>")
|
||||||
|
* @example
|
||||||
|
* s("hh*16").bank("tr909").transient("<-1:1 1:-1>")
|
||||||
|
*/
|
||||||
|
export const { transient } = registerControl(['transient', 'transsustain']);
|
||||||
|
|||||||
@@ -574,7 +574,8 @@ export class Pattern {
|
|||||||
* Returns a new Pattern, which only returns haps that meet the given test.
|
* Returns a new Pattern, which only returns haps that meet the given test.
|
||||||
* @param {Function} hap_test - a function which returns false for haps to be removed from the pattern
|
* @param {Function} hap_test - a function which returns false for haps to be removed from the pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @noAutocomplete
|
* @example
|
||||||
|
* s("bd*8").velocity(rand).filterHaps((h) => (h.whole.begin % 1) < h.value.velocity)
|
||||||
*/
|
*/
|
||||||
filterHaps(hap_test) {
|
filterHaps(hap_test) {
|
||||||
return new Pattern((state) => this.query(state).filter(hap_test));
|
return new Pattern((state) => this.query(state).filter(hap_test));
|
||||||
@@ -585,7 +586,11 @@ export class Pattern {
|
|||||||
* inside haps.
|
* inside haps.
|
||||||
* @param {Function} value_test
|
* @param {Function} value_test
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @noAutocomplete
|
* @example
|
||||||
|
* const drums = s("bd sd bd sd")
|
||||||
|
* kick: drums.filterValues((v) => v.s === 'bd').duck(2)
|
||||||
|
* snare: drums.filterValues((v) => v.s === 'sd')
|
||||||
|
* bass: s("saw!4").note("G#1").lpf(80).lpenv(4).orbit(2)
|
||||||
*/
|
*/
|
||||||
filterValues(value_test) {
|
filterValues(value_test) {
|
||||||
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value))).setSteps(this._steps);
|
return new Pattern((state) => this.query(state).filter((hap) => value_test(hap.value))).setSteps(this._steps);
|
||||||
@@ -1587,7 +1592,13 @@ export const func = curry((a, b) => reify(b).func(a));
|
|||||||
*
|
*
|
||||||
* @param {string | string[]} name name of the function, or an array of names to be used as synonyms
|
* @param {string | string[]} name name of the function, or an array of names to be used as synonyms
|
||||||
* @param {function} func function with 1 or more params, where last is the current pattern
|
* @param {function} func function with 1 or more params, where last is the current pattern
|
||||||
* @noAutocomplete
|
* @param {bool} patternify defaults to true; if set to false, you will have more control over the arguments to `func` as they will be
|
||||||
|
* in their raw form and it will be up to you to patternify them and/or query them for values
|
||||||
|
* @example
|
||||||
|
* const vlpf = register('vlpf', (freq, pat) => {
|
||||||
|
* return pat.fmap((v) => ({...v, cutoff: freq * (v.velocity ?? 1) }));
|
||||||
|
* })
|
||||||
|
* s("saw").seg(8).velocity(rand).vlpf(800)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function register(name, func, patternify = true, preserveSteps = false, join = (x) => x.innerJoin()) {
|
export function register(name, func, patternify = true, preserveSteps = false, join = (x) => x.innerJoin()) {
|
||||||
@@ -2676,8 +2687,13 @@ export const hsl = register('hsl', (h, s, l, pat) => {
|
|||||||
/**
|
/**
|
||||||
* Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array).
|
* Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array).
|
||||||
* @name tag
|
* @name tag
|
||||||
* @noAutocomplete
|
|
||||||
* @param {string} tag anything unique
|
* @param {string} tag anything unique
|
||||||
|
* @example
|
||||||
|
* s("saw!16").note("F1")
|
||||||
|
* .lpf(tri.range(40, 80).slow(4)).lpenv(5).lpq(4).lpd(0.15)
|
||||||
|
* .when(rand.late(0.1).gte(0.5), x => x.transpose("12").tag('altered'))
|
||||||
|
* .when(rand.late(0.2).gte(0.5), x => x.s("square").tag('altered'))
|
||||||
|
* .when("<0 1>", x => x.filter((hap) => hap.hasTag('altered')))
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.tag = function (tag) {
|
Pattern.prototype.tag = function (tag) {
|
||||||
return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) }));
|
return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) }));
|
||||||
@@ -2688,15 +2704,16 @@ Pattern.prototype.tag = function (tag) {
|
|||||||
* @name filter
|
* @name filter
|
||||||
* @param {Function} test function to test Hap
|
* @param {Function} test function to test Hap
|
||||||
* @example
|
* @example
|
||||||
* s("hh!7 oh").filter(hap => hap.value.s==='hh')
|
* s("hh!7 oh").filter(hap => hap.value.s === 'hh')
|
||||||
*/
|
*/
|
||||||
export const filter = register('filter', (test, pat) => pat.withHaps((haps) => haps.filter(test)));
|
export const filter = register('filter', (test, pat) => pat.withHaps((haps) => haps.filter(test)));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters haps by their begin time
|
* Filters haps by their begin time
|
||||||
* @name filterWhen
|
* @name filterWhen
|
||||||
* @noAutocomplete
|
|
||||||
* @param {Function} test function to test Hap.whole.begin
|
* @param {Function} test function to test Hap.whole.begin
|
||||||
|
* @example
|
||||||
|
* oneCycle: s("bd*4").filterWhen((t) => t < 1)
|
||||||
*/
|
*/
|
||||||
export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) => test(h.whole.begin)));
|
export const filterWhen = register('filterWhen', (test, pat) => pat.filter((h) => test(h.whole.begin)));
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
|
|
||||||
import { Pattern, getTime, State, TimeSpan } from '@strudel/core';
|
import { Pattern, getTime, State, TimeSpan } from '@strudel/core';
|
||||||
|
|
||||||
|
const ANIMATE_INTERVAL = 1000 / 15; // 15 FPS
|
||||||
|
|
||||||
export const getDrawContext = (id = 'test-canvas', options) => {
|
export const getDrawContext = (id = 'test-canvas', options) => {
|
||||||
let { contextType = '2d', pixelated = false, pixelRatio = window.devicePixelRatio } = options || {};
|
let { contextType = '2d', pixelated = false, pixelRatio = window.devicePixelRatio } = options || {};
|
||||||
let canvas = document.querySelector('#' + id);
|
let canvas = document.querySelector('#' + id);
|
||||||
@@ -112,7 +114,13 @@ export class Framer {
|
|||||||
}
|
}
|
||||||
start() {
|
start() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
let lastTime = 0;
|
||||||
let frame = requestAnimationFrame(function updateHighlights(time) {
|
let frame = requestAnimationFrame(function updateHighlights(time) {
|
||||||
|
if (time - lastTime < ANIMATE_INTERVAL) {
|
||||||
|
frame = requestAnimationFrame(updateHighlights);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastTime = time;
|
||||||
try {
|
try {
|
||||||
self.onFrame(time);
|
self.onFrame(time);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
+26
-9
@@ -479,14 +479,24 @@ Pattern.prototype.midi = function (midiport, options = {}) {
|
|||||||
|
|
||||||
let listeners = {};
|
let listeners = {};
|
||||||
const refs = {};
|
const refs = {};
|
||||||
|
const refsByChan = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
|
* MIDI input: Opens a MIDI input port to receive MIDI control change messages.
|
||||||
|
*
|
||||||
|
* The output is a function that accepts a midi cc value to query as well as (optionally) a midi channel
|
||||||
* @param {string | number} input MIDI device name or index defaulting to 0
|
* @param {string | number} input MIDI device name or index defaulting to 0
|
||||||
* @returns {Function}
|
* @returns {function(number, number=): Pattern} A function from (cc, channel?) to a pattern.
|
||||||
|
* When queried, the pattern will produces the most recently received midi value (normalized to 0 to 1)
|
||||||
|
* that came through that cc number (and channel, if provided)
|
||||||
* @example
|
* @example
|
||||||
* let cc = await midin('IAC Driver Bus 1')
|
* const cc = await midin('IAC Driver Bus 1')
|
||||||
* note("c a f e").lpf(cc(0).range(0, 1000)).lpq(cc(1).range(0, 10)).sound("sawtooth")
|
* note("c a f e").lpf(cc(0).range(0, 1000)).lpq(cc(1).range(0, 10)).sound("sawtooth")
|
||||||
|
* @example
|
||||||
|
* const allCC = await midin('IAC Driver Bus 1')
|
||||||
|
* const cc = (ccNum) => allCC(ccNum, 2) // just channel 2
|
||||||
|
* note("c a f e").s("saw")
|
||||||
|
* .when(cc(0).gt(0), x => x.postgain(0))
|
||||||
*/
|
*/
|
||||||
export async function midin(input) {
|
export async function midin(input) {
|
||||||
if (isPattern(input)) {
|
if (isPattern(input)) {
|
||||||
@@ -511,17 +521,24 @@ export async function midin(input) {
|
|||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// ensure refs for this input are initialized
|
refs[input] ??= {};
|
||||||
if (!refs[input]) {
|
refsByChan[input] ??= {};
|
||||||
refs[input] = {};
|
const cc = (cc, chan) => {
|
||||||
}
|
if (chan !== undefined) {
|
||||||
const cc = (cc) => ref(() => refs[input][cc] || 0);
|
return ref(() => refsByChan[input][cc]?.[chan] || 0);
|
||||||
|
}
|
||||||
|
return ref(() => refs[input][cc] || 0);
|
||||||
|
};
|
||||||
|
|
||||||
listeners[input] && device.removeListener('midimessage', listeners[input]);
|
listeners[input] && device.removeListener('midimessage', listeners[input]);
|
||||||
listeners[input] = (e) => {
|
listeners[input] = (e) => {
|
||||||
const cc = e.dataBytes[0];
|
const ccNum = e.dataBytes[0];
|
||||||
const v = e.dataBytes[1];
|
const v = e.dataBytes[1];
|
||||||
refs[input] && (refs[input][cc] = v / 127);
|
const chan = e.message.channel;
|
||||||
|
const scaled = v / 127;
|
||||||
|
refsByChan[input][ccNum] ??= {};
|
||||||
|
refsByChan[input][ccNum][chan] = scaled;
|
||||||
|
refs[input][ccNum] = scaled;
|
||||||
};
|
};
|
||||||
device.addListener('midimessage', listeners[input]);
|
device.addListener('midimessage', listeners[input]);
|
||||||
return cc;
|
return cc;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
getADSRValues,
|
getADSRValues,
|
||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
|
onceEnded,
|
||||||
} from '@strudel/webaudio';
|
} from '@strudel/webaudio';
|
||||||
import gm from './gm.mjs';
|
import gm from './gm.mjs';
|
||||||
|
|
||||||
@@ -170,12 +171,12 @@ export function registerSoundfonts() {
|
|||||||
|
|
||||||
bufferSource.stop(envEnd);
|
bufferSource.stop(envEnd);
|
||||||
const stop = (releaseTime) => {};
|
const stop = (releaseTime) => {};
|
||||||
bufferSource.onended = () => {
|
onceEnded(bufferSource, () => {
|
||||||
bufferSource.disconnect();
|
bufferSource.disconnect();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
node.disconnect();
|
node.disconnect();
|
||||||
onended();
|
onended();
|
||||||
};
|
});
|
||||||
return { node, stop };
|
return { node, stop };
|
||||||
},
|
},
|
||||||
{ type: 'soundfont', prebake: true, fonts },
|
{ type: 'soundfont', prebake: true, fonts },
|
||||||
|
|||||||
+137
-77
@@ -1,7 +1,7 @@
|
|||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs';
|
|
||||||
import { getNoiseBuffer } from './noise.mjs';
|
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
import { getNoiseBuffer } from './noise.mjs';
|
||||||
|
import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs';
|
||||||
|
|
||||||
export const noises = ['pink', 'white', 'brown', 'crackle'];
|
export const noises = ['pink', 'white', 'brown', 'crackle'];
|
||||||
|
|
||||||
@@ -349,25 +349,17 @@ export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
|||||||
constantNode.connect(zeroGain);
|
constantNode.connect(zeroGain);
|
||||||
|
|
||||||
// Schedule the `onComplete` callback to occur at `stopTime`
|
// Schedule the `onComplete` callback to occur at `stopTime`
|
||||||
constantNode.onended = () => {
|
onceEnded(constantNode, () => {
|
||||||
// Ensure garbage collection
|
releaseAudioNode(zeroGain);
|
||||||
try {
|
releaseAudioNode(constantNode);
|
||||||
zeroGain.disconnect();
|
|
||||||
} catch {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
constantNode.disconnect();
|
|
||||||
} catch {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
onComplete();
|
onComplete();
|
||||||
};
|
});
|
||||||
constantNode.start(startTime);
|
constantNode.start(startTime);
|
||||||
constantNode.stop(stopTime);
|
constantNode.stop(stopTime);
|
||||||
return constantNode;
|
return constantNode;
|
||||||
}
|
}
|
||||||
const mod = (freq, range = 1, type = 'sine') => {
|
|
||||||
|
const mod = (freq, type = 'sine') => {
|
||||||
const ctx = getAudioContext();
|
const ctx = getAudioContext();
|
||||||
let osc;
|
let osc;
|
||||||
if (noises.includes(type)) {
|
if (noises.includes(type)) {
|
||||||
@@ -379,69 +371,89 @@ const mod = (freq, range = 1, type = 'sine') => {
|
|||||||
osc.type = type;
|
osc.type = type;
|
||||||
osc.frequency.value = freq;
|
osc.frequency.value = freq;
|
||||||
}
|
}
|
||||||
|
|
||||||
osc.start();
|
osc.start();
|
||||||
const g = gainNode(range);
|
return osc;
|
||||||
osc.connect(g); // -range, range
|
|
||||||
return { node: g, stop: (t) => osc.stop(t), osc: osc };
|
|
||||||
};
|
};
|
||||||
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
|
||||||
|
const fm = (frequencyparam, harmonicityRatio, wave = 'sine') => {
|
||||||
const carrfreq = frequencyparam.value;
|
const carrfreq = frequencyparam.value;
|
||||||
const modfreq = carrfreq * harmonicityRatio;
|
const modfreq = carrfreq * harmonicityRatio;
|
||||||
const modgain = modfreq * modulationIndex;
|
return { osc: mod(modfreq, wave), freq: modfreq };
|
||||||
return mod(modfreq, modgain, wave);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function applyFM(param, value, begin) {
|
export function applyFM(param, value, begin) {
|
||||||
const {
|
const ac = getAudioContext();
|
||||||
fmh: fmHarmonicity = 1,
|
const toStop = []; // fm oscillators we will expose `stop` for
|
||||||
fmi: fmModulationIndex,
|
const fms = {};
|
||||||
fmenv: fmEnvelopeType = 'exp',
|
// Matrix
|
||||||
fmattack: fmAttack,
|
for (let i = 1; i <= 8; i++) {
|
||||||
fmdecay: fmDecay,
|
for (let j = 0; j <= 8; j++) {
|
||||||
fmsustain: fmSustain,
|
let control;
|
||||||
fmrelease: fmRelease,
|
if (i === j + 1) {
|
||||||
fmvelocity: fmVelocity,
|
// Standard fm3 -> fm2 -> fm1 -> param usage
|
||||||
fmwave: fmWaveform = 'sine',
|
const iS = i === 1 ? '' : i;
|
||||||
duration,
|
control = `fmi${iS}`;
|
||||||
} = value;
|
} else {
|
||||||
let modulator;
|
control = `fmi${i}${j}`;
|
||||||
let stop = () => {};
|
}
|
||||||
|
const amt = value[control];
|
||||||
if (fmModulationIndex) {
|
if (!amt) continue;
|
||||||
const ac = getAudioContext();
|
let io = [];
|
||||||
const envGain = ac.createGain();
|
for (let [isMod, idx] of [
|
||||||
const fmmod = fm(param, fmHarmonicity, fmModulationIndex, fmWaveform);
|
[true, i], // source
|
||||||
|
[false, j], // target
|
||||||
modulator = fmmod.node;
|
]) {
|
||||||
stop = fmmod.stop;
|
if (idx === 0) {
|
||||||
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].some((v) => v !== undefined)) {
|
io.push(param);
|
||||||
// no envelope by default
|
continue;
|
||||||
modulator.connect(param);
|
}
|
||||||
} else {
|
if (!fms[idx]) {
|
||||||
const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]);
|
const idxS = idx === 1 ? '' : idx;
|
||||||
const holdEnd = begin + duration;
|
const { osc, freq } = fm(param, value[`fmh${idxS}`] ?? 1, value[`fmwave${idxS}`] ?? 'sine');
|
||||||
getParamADSR(
|
toStop.push(osc);
|
||||||
envGain.gain,
|
const toCleanup = [osc]; // nodes we want to cleanup after oscillator `stop`
|
||||||
attack,
|
const adsr = ['attack', 'decay', 'sustain', 'release'].map((s) => value[`fm${s}${idxS}`]);
|
||||||
decay,
|
let output = osc;
|
||||||
sustain,
|
if (adsr.some((v) => v !== undefined)) {
|
||||||
release,
|
const envGain = ac.createGain();
|
||||||
0,
|
const [attack, decay, sustain, release] = getADSRValues(adsr);
|
||||||
1,
|
const holdEnd = begin + value.duration;
|
||||||
begin,
|
const fmEnvelopeType = value[`fmenv${idxS}`] ?? 'exp';
|
||||||
holdEnd,
|
getParamADSR(
|
||||||
fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
|
envGain.gain,
|
||||||
);
|
attack,
|
||||||
modulator.connect(envGain);
|
decay,
|
||||||
envGain.connect(param);
|
sustain,
|
||||||
|
release,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
begin,
|
||||||
|
holdEnd,
|
||||||
|
fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
|
||||||
|
);
|
||||||
|
toCleanup.push(envGain);
|
||||||
|
output = osc.connect(envGain);
|
||||||
|
}
|
||||||
|
fms[idx] = { input: osc.frequency, output, freq, osc, toCleanup };
|
||||||
|
}
|
||||||
|
const { input, output, freq, osc, toCleanup } = fms[idx];
|
||||||
|
const g = gainNode(amt * freq);
|
||||||
|
io.push(isMod ? output.connect(g) : input);
|
||||||
|
cleanupOnEnd(osc, [...toCleanup, g]);
|
||||||
|
}
|
||||||
|
if (!io[1]) {
|
||||||
|
logger(
|
||||||
|
`[superdough] control ${control} failed to connect FM ${i} to target ${j} due to missing frequency parameter (likely because fm${j} is noise)`,
|
||||||
|
'warning',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
io[0].connect(io[1]);
|
||||||
}
|
}
|
||||||
fmmod.osc.onended = () => {
|
|
||||||
envGain.disconnect();
|
|
||||||
modulator.disconnect();
|
|
||||||
fmmod.osc.disconnect();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return { stop };
|
return {
|
||||||
|
stop: (t) => toStop.forEach((m) => m?.stop(t)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saturation curves
|
// Saturation curves
|
||||||
@@ -554,10 +566,58 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
|||||||
return Number(freq);
|
return Number(freq);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const destroyAudioWorkletNode = (node) => {
|
// This helper should be used instead of the `node.onended = callback` pattern
|
||||||
if (node == null) {
|
// It adds a mechanism to help minimize gc retention
|
||||||
return;
|
export const onceEnded = (node, callback) => {
|
||||||
}
|
const onended = callback;
|
||||||
node.disconnect();
|
node.onended = function cleanup() {
|
||||||
node.parameters.get('end')?.setValueAtTime(0, 0);
|
onended && onended();
|
||||||
|
this.onended = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const releaseAudioNode = (node) => {
|
||||||
|
if (node == null) return;
|
||||||
|
|
||||||
|
// check we received an AudioNode
|
||||||
|
if (!(node instanceof AudioNode)) {
|
||||||
|
throw new Error('releaseAudioNode can only release an AudioNode');
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/disconnect
|
||||||
|
node.disconnect();
|
||||||
|
|
||||||
|
// make sure all AudioScheduledSourceNodes are in a stopped state
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/AudioScheduledSourceNode
|
||||||
|
if (node instanceof AudioScheduledSourceNode) {
|
||||||
|
if (node.onended && node.onended.name !== 'cleanup') {
|
||||||
|
logger(
|
||||||
|
`[superdough] Deprecation warning: it seems your code path is setting 'node.onended = callback' instead of using the onceEnded helper`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
node.stop();
|
||||||
|
} catch (e) {
|
||||||
|
// At the stage, `start` was not called on the node
|
||||||
|
// but an `onended` callback releasing resources may exist
|
||||||
|
// and we want it to fire :
|
||||||
|
// - we force a start/stop cycle so that `onended` gets called
|
||||||
|
// - we `lock` the node so that no-one can start it
|
||||||
|
node.start(node.context.currentTime + 5); // will never happen
|
||||||
|
node.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/webaudio-1.1/#AudioNode-actively-processing
|
||||||
|
// An AudioWorkletNode is actively processing when its AudioWorkletProcessor's [[callable process]]
|
||||||
|
// returns true and either its active source flag is true or
|
||||||
|
// any AudioNode connected to one of its inputs is actively processing.
|
||||||
|
if (node instanceof AudioWorkletNode) {
|
||||||
|
node.parameters.get('end')?.setValueAtTime(0, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Once the `anchor` node has ended, release all nodes in `toCleanup`
|
||||||
|
export const cleanupOnEnd = (anchor, toCleanup) => {
|
||||||
|
onceEnded(anchor, () => toCleanup.forEach((n) => releaseAudioNode(n)));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||||
import { registerSound, registerWaveTable } from './index.mjs';
|
import { registerSound, registerWaveTable } from './index.mjs';
|
||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
|
import {
|
||||||
|
getADSRValues,
|
||||||
|
getParamADSR,
|
||||||
|
getPitchEnvelope,
|
||||||
|
getVibratoOscillator,
|
||||||
|
onceEnded,
|
||||||
|
releaseAudioNode,
|
||||||
|
} from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
||||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||||
@@ -236,12 +243,6 @@ export async function fetchSampleMap(url) {
|
|||||||
* sd: '808sd/SD0010.WAV'
|
* sd: '808sd/SD0010.WAV'
|
||||||
* }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
|
* }, 'https://raw.githubusercontent.com/tidalcycles/Dirt-Samples/master/');
|
||||||
* s("[bd ~]*2, [~ hh]*2, ~ sd")
|
* s("[bd ~]*2, [~ hh]*2, ~ sd")
|
||||||
* @example
|
|
||||||
* samples('shabda:noise,chimp:2')
|
|
||||||
* s("noise <chimp:0*2 chimp:1>")
|
|
||||||
* @example
|
|
||||||
* samples('shabda/speech/fr-FR/f:chocolat')
|
|
||||||
* s("chocolat*4")
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const samples = async (sampleMap, baseUrl = sampleMap._base || '', options = {}) => {
|
export const samples = async (sampleMap, baseUrl = sampleMap._base || '', options = {}) => {
|
||||||
@@ -286,17 +287,19 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
|||||||
|
|
||||||
const { bufferSource, sliceDuration, offset } = await getSampleBufferSource(value, bank, resolveUrl);
|
const { bufferSource, sliceDuration, offset } = await getSampleBufferSource(value, bank, resolveUrl);
|
||||||
|
|
||||||
// asny stuff above took too long?
|
|
||||||
if (ac.currentTime > t) {
|
|
||||||
logger(`[sampler] still loading sound "${s}:${n}"`, 'highlight');
|
|
||||||
// console.warn('sample still loading:', s, n);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!bufferSource) {
|
if (!bufferSource) {
|
||||||
logger(`[sampler] could not load "${s}:${n}"`, 'error');
|
logger(`[sampler] could not load "${s}:${n}"`, 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// async stuff above took too long?
|
||||||
|
if (ac.currentTime > t) {
|
||||||
|
logger(`[sampler] loading sound "${s}:${n}" took too long`, 'highlight');
|
||||||
|
// AudioBufferSourceNode will never be used. discard it
|
||||||
|
releaseAudioNode(bufferSource);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// vibrato
|
// vibrato
|
||||||
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
|
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
|
||||||
|
|
||||||
@@ -319,13 +322,13 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
|||||||
|
|
||||||
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
|
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
|
||||||
node.connect(out);
|
node.connect(out);
|
||||||
bufferSource.onended = function () {
|
onceEnded(bufferSource, function () {
|
||||||
bufferSource.disconnect();
|
bufferSource.disconnect();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
node.disconnect();
|
node.disconnect();
|
||||||
out.disconnect();
|
out.disconnect();
|
||||||
onended();
|
onended();
|
||||||
};
|
});
|
||||||
let envEnd = holdEnd + release + 0.01;
|
let envEnd = holdEnd + release + 0.01;
|
||||||
bufferSource.stop(envEnd);
|
bufferSource.stop(envEnd);
|
||||||
const stop = (endTime) => {
|
const stop = (endTime) => {
|
||||||
|
|||||||
@@ -290,8 +290,8 @@ function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000
|
|||||||
const lfo = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
|
const lfo = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
|
||||||
|
|
||||||
//filters
|
//filters
|
||||||
const numStages = 2; //num of filters in series
|
const numStages = 1; //num of filters in series
|
||||||
let fOffset = 0;
|
let fOffset = 282; //for backward compat in #1800
|
||||||
const filterChain = [];
|
const filterChain = [];
|
||||||
for (let i = 0; i < numStages; i++) {
|
for (let i = 0; i < numStages; i++) {
|
||||||
const filter = ac.createBiquadFilter();
|
const filter = ac.createBiquadFilter();
|
||||||
@@ -302,12 +302,9 @@ function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000
|
|||||||
|
|
||||||
lfo.connect(filter.detune);
|
lfo.connect(filter.detune);
|
||||||
fOffset += 282;
|
fOffset += 282;
|
||||||
if (i > 0) {
|
|
||||||
filterChain[i - 1].connect(filter);
|
|
||||||
}
|
|
||||||
filterChain.push(filter);
|
filterChain.push(filter);
|
||||||
}
|
}
|
||||||
return { phaser: filterChain[filterChain.length - 1], lfo };
|
return { filterChain, lfo };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFilterType(ftype) {
|
function getFilterType(ftype) {
|
||||||
@@ -451,6 +448,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
compressorKnee,
|
compressorKnee,
|
||||||
compressorAttack,
|
compressorAttack,
|
||||||
compressorRelease,
|
compressorRelease,
|
||||||
|
transient,
|
||||||
|
transsustain,
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
||||||
@@ -532,6 +531,23 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
chain.push(sourceNode);
|
chain.push(sourceNode);
|
||||||
stretch !== undefined && chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
stretch !== undefined && chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
||||||
|
|
||||||
|
transient !== undefined &&
|
||||||
|
chain.push(
|
||||||
|
getWorklet(
|
||||||
|
ac,
|
||||||
|
'transient-processor',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
processorOptions: {
|
||||||
|
attack: transient,
|
||||||
|
sustain: transsustain,
|
||||||
|
begin: t,
|
||||||
|
end: endWithRelease,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// gain stage
|
// gain stage
|
||||||
chain.push(gainNode(gain));
|
chain.push(gainNode(gain));
|
||||||
|
|
||||||
@@ -697,9 +713,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
}
|
}
|
||||||
// phaser
|
// phaser
|
||||||
if (phaserrate !== undefined && phaserdepth > 0) {
|
if (phaserrate !== undefined && phaserdepth > 0) {
|
||||||
const { phaser, lfo } = getPhaser(t, endWithRelease, phaserrate, phaserdepth, phasercenter, phasersweep);
|
const { filterChain, lfo } = getPhaser(t, endWithRelease, phaserrate, phaserdepth, phasercenter, phasersweep);
|
||||||
audioNodes.push(lfo);
|
audioNodes.push(lfo);
|
||||||
chain.push(phaser);
|
chain.push(...filterChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// last gain
|
// last gain
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { registerSound, soundMap } from './superdough.mjs';
|
|||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import {
|
import {
|
||||||
applyFM,
|
applyFM,
|
||||||
destroyAudioWorkletNode,
|
|
||||||
gainNode,
|
gainNode,
|
||||||
getADSRValues,
|
getADSRValues,
|
||||||
getFrequencyFromValue,
|
getFrequencyFromValue,
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
getWorklet,
|
getWorklet,
|
||||||
noises,
|
noises,
|
||||||
|
releaseAudioNode,
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
@@ -192,8 +192,7 @@ export function registerSynthSounds() {
|
|||||||
let timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
releaseAudioNode(o);
|
||||||
envGain.disconnect();
|
|
||||||
onended();
|
onended();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
@@ -270,8 +269,7 @@ export function registerSynthSounds() {
|
|||||||
let timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
releaseAudioNode(o);
|
||||||
envGain.disconnect();
|
|
||||||
onended();
|
onended();
|
||||||
},
|
},
|
||||||
begin,
|
begin,
|
||||||
@@ -344,9 +342,8 @@ export function registerSynthSounds() {
|
|||||||
let timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
releaseAudioNode(o);
|
||||||
destroyAudioWorkletNode(lfo);
|
releaseAudioNode(lfo);
|
||||||
envGain.disconnect();
|
|
||||||
onended();
|
onended();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
|||||||
import {
|
import {
|
||||||
applyFM,
|
applyFM,
|
||||||
applyParameterModulators,
|
applyParameterModulators,
|
||||||
destroyAudioWorkletNode,
|
|
||||||
getADSRValues,
|
getADSRValues,
|
||||||
getFrequencyFromValue,
|
getFrequencyFromValue,
|
||||||
getParamADSR,
|
getParamADSR,
|
||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
getWorklet,
|
getWorklet,
|
||||||
|
releaseAudioNode,
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
@@ -319,10 +319,9 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
const timeoutNode = webAudioTimeout(
|
const timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(source);
|
releaseAudioNode(source);
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
node.disconnect();
|
|
||||||
wtPosModulators?.disconnect();
|
wtPosModulators?.disconnect();
|
||||||
wtWarpModulators?.disconnect();
|
wtWarpModulators?.disconnect();
|
||||||
onended();
|
onended();
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ const PI = Math.PI;
|
|||||||
const TWO_PI = 2 * PI;
|
const TWO_PI = 2 * PI;
|
||||||
const INVSR = 1 / sampleRate;
|
const INVSR = 1 / sampleRate;
|
||||||
|
|
||||||
|
const timeToCoeff = (t) => 1 - Math.exp(-INVSR / t);
|
||||||
|
const dbToLin = (db) => Math.pow(10, db / 20);
|
||||||
|
|
||||||
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;
|
||||||
const lerp = (a, b, n) => n * (b - a) + a;
|
const lerp = (a, b, n) => n * (b - a) + a;
|
||||||
@@ -508,13 +511,14 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
process(_input, outputs, params) {
|
process(_input, outputs, params) {
|
||||||
if (currentTime <= params.begin[0]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (currentTime >= params.end[0]) {
|
if (currentTime >= params.end[0]) {
|
||||||
// this.port.postMessage({ type: 'onended' });
|
// should terminate
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (currentTime <= params.begin[0]) {
|
||||||
|
// keep alive
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
const voices = params.voices[0]; // k-rate
|
const voices = params.voices[0]; // k-rate
|
||||||
for (let i = 0; i < output[0].length; i++) {
|
for (let i = 0; i < output[0].length; i++) {
|
||||||
@@ -1383,3 +1387,82 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerProcessor('wavetable-oscillator-processor', WavetableOscillatorProcessor);
|
registerProcessor('wavetable-oscillator-processor', WavetableOscillatorProcessor);
|
||||||
|
|
||||||
|
class TransientProcessor extends AudioWorkletProcessor {
|
||||||
|
static get parameterDescriptors() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
this.gainCoeff = timeToCoeff(0.2);
|
||||||
|
this.avgGain = 1;
|
||||||
|
let {
|
||||||
|
attackTime = 0.003,
|
||||||
|
sustainTime = 0.08,
|
||||||
|
attack = 0,
|
||||||
|
sustain = 0,
|
||||||
|
sensitivity = 0.1,
|
||||||
|
mix = 1,
|
||||||
|
begin = 0,
|
||||||
|
end = 0,
|
||||||
|
} = options.processorOptions;
|
||||||
|
attackTime = clamp(attackTime, 0.0005, 0.05);
|
||||||
|
sustainTime = clamp(sustainTime, 0.01, 0.5);
|
||||||
|
this.attackCoeff = timeToCoeff(attackTime);
|
||||||
|
this.sustainCoeff = timeToCoeff(sustainTime);
|
||||||
|
this.attackAmt = clamp(attack, -1, 1);
|
||||||
|
this.sustainAmt = clamp(sustain, -1, 1);
|
||||||
|
this.scaling = 0.5 + 5 * clamp(sensitivity, 0, 1);
|
||||||
|
this.mix = clamp(mix, 0, 1);
|
||||||
|
this.begin = begin;
|
||||||
|
this.end = end;
|
||||||
|
this.attackEnv = new Float32Array(2); // assume stereo
|
||||||
|
this.sustainEnv = new Float32Array(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
process(inputs, outputs, _params) {
|
||||||
|
const input = inputs[0];
|
||||||
|
const output = outputs[0];
|
||||||
|
if (currentTime >= this.end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentTime <= this.begin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const channels = input.length;
|
||||||
|
if (channels > this.attackEnv.length) {
|
||||||
|
this.attackEnv = new Float32Array(channels);
|
||||||
|
this.sustainEnv = new Float32Array(channels);
|
||||||
|
}
|
||||||
|
let avgGain = this.avgGain;
|
||||||
|
for (let ch = 0; ch < channels; ch++) {
|
||||||
|
let attEnv = this.attackEnv[ch];
|
||||||
|
let susEnv = this.sustainEnv[ch];
|
||||||
|
for (let n = 0; n < blockSize; n++) {
|
||||||
|
const sample = input[ch][n];
|
||||||
|
const x = Math.abs(sample);
|
||||||
|
attEnv = lerp(attEnv, x, this.attackCoeff);
|
||||||
|
susEnv = lerp(susEnv, x, this.sustainCoeff);
|
||||||
|
const peakiness = clamp((this.scaling * (attEnv - susEnv)) / (susEnv + 1e-6), -1.5, 1.5);
|
||||||
|
const attScale = peakiness > 0 ? peakiness : 0;
|
||||||
|
const susScale = peakiness < 0 ? -peakiness : 0;
|
||||||
|
const attackGain = dbToLin(this.attackAmt * attScale * 18);
|
||||||
|
const sustainGain = dbToLin(this.sustainAmt * susScale * 36);
|
||||||
|
const gain = clamp(attackGain * sustainGain, 0, 8);
|
||||||
|
avgGain = lerp(avgGain, gain, this.gainCoeff);
|
||||||
|
const makeup = avgGain > 1e-3 ? 1 / avgGain : 1;
|
||||||
|
const wet = sample * gain * makeup;
|
||||||
|
let y = lerp(sample, wet, this.mix);
|
||||||
|
y /= 1 + Math.abs(y); // soft clip
|
||||||
|
output[ch][n] = y;
|
||||||
|
}
|
||||||
|
this.attackEnv[ch] = attEnv;
|
||||||
|
this.sustainEnv[ch] = susEnv;
|
||||||
|
}
|
||||||
|
this.avgGain = avgGain;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProcessor('transient-processor', TransientProcessor);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { midiToFreq, noteToMidi } from './util.mjs';
|
|||||||
import { registerSound } from './superdough.mjs';
|
import { registerSound } from './superdough.mjs';
|
||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import { buildSamples } from './zzfx_fork.mjs';
|
import { buildSamples } from './zzfx_fork.mjs';
|
||||||
|
import { onceEnded, releaseAudioNode } from './helpers.mjs';
|
||||||
|
|
||||||
export const getZZFX = (value, t) => {
|
export const getZZFX = (value, t) => {
|
||||||
let {
|
let {
|
||||||
@@ -83,10 +84,10 @@ export function registerZZFXSounds() {
|
|||||||
wave,
|
wave,
|
||||||
(t, value, onended) => {
|
(t, value, onended) => {
|
||||||
const { node: o } = getZZFX({ s: wave, ...value }, t);
|
const { node: o } = getZZFX({ s: wave, ...value }, t);
|
||||||
o.onended = () => {
|
onceEnded(o, () => {
|
||||||
o.disconnect();
|
releaseAudioNode(o);
|
||||||
onended();
|
onended();
|
||||||
};
|
});
|
||||||
return {
|
return {
|
||||||
node: o,
|
node: o,
|
||||||
stop: () => {},
|
stop: () => {},
|
||||||
|
|||||||
@@ -4043,6 +4043,70 @@ exports[`runs examples > example "filter" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "filterHaps" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 1/8 → 1/4 | s:bd velocity:0.6852155700325966 ]",
|
||||||
|
"[ 1/4 → 3/8 | s:bd velocity:0.36975969187915325 ]",
|
||||||
|
"[ 3/8 → 1/2 | s:bd velocity:0.40139251574873924 ]",
|
||||||
|
"[ 1/1 → 9/8 | s:bd velocity:0.5195421651005745 ]",
|
||||||
|
"[ 9/8 → 5/4 | s:bd velocity:0.6724895145744085 ]",
|
||||||
|
"[ 5/4 → 11/8 | s:bd velocity:0.7287282031029463 ]",
|
||||||
|
"[ 3/2 → 13/8 | s:bd velocity:0.6084080748260021 ]",
|
||||||
|
"[ 2/1 → 17/8 | s:bd velocity:0.9595271199941635 ]",
|
||||||
|
"[ 17/8 → 9/4 | s:bd velocity:0.9033902939409018 ]",
|
||||||
|
"[ 9/4 → 19/8 | s:bd velocity:0.34548251144587994 ]",
|
||||||
|
"[ 19/8 → 5/2 | s:bd velocity:0.8365066405385733 ]",
|
||||||
|
"[ 3/1 → 25/8 | s:bd velocity:0.21728911064565182 ]",
|
||||||
|
"[ 25/8 → 13/4 | s:bd velocity:0.34324387833476067 ]",
|
||||||
|
"[ 13/4 → 27/8 | s:bd velocity:0.9923650715500116 ]",
|
||||||
|
"[ 27/8 → 7/2 | s:bd velocity:0.40869180113077164 ]",
|
||||||
|
"[ 29/8 → 15/4 | s:bd velocity:0.9391485787928104 ]",
|
||||||
|
"[ 15/4 → 31/8 | s:bd velocity:0.8121673222631216 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "filterValues" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 1/4 → 1/2 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 1/2 → 3/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 3/4 → 1/1 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 1/1 → 5/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 5/4 → 3/2 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 3/2 → 7/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 7/4 → 2/1 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 2/1 → 9/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 9/4 → 5/2 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 5/2 → 11/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 11/4 → 3/1 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 3/1 → 13/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 13/4 → 7/2 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 7/2 → 15/4 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
"[ 15/4 → 4/1 | s:saw note:G#1 cutoff:80 lpenv:4 orbit:2 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "filterWhen" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/4 | s:bd ]",
|
||||||
|
"[ 1/4 → 1/2 | s:bd ]",
|
||||||
|
"[ 1/2 → 3/4 | s:bd ]",
|
||||||
|
"[ 3/4 → 1/1 | s:bd ]",
|
||||||
|
"[ 1/1 → 5/4 | s:bd ]",
|
||||||
|
"[ 5/4 → 3/2 | s:bd ]",
|
||||||
|
"[ 3/2 → 7/4 | s:bd ]",
|
||||||
|
"[ 7/4 → 2/1 | s:bd ]",
|
||||||
|
"[ 2/1 → 9/4 | s:bd ]",
|
||||||
|
"[ 9/4 → 5/2 | s:bd ]",
|
||||||
|
"[ 5/2 → 11/4 | s:bd ]",
|
||||||
|
"[ 11/4 → 3/1 | s:bd ]",
|
||||||
|
"[ 3/1 → 13/4 | s:bd ]",
|
||||||
|
"[ 13/4 → 7/2 | s:bd ]",
|
||||||
|
"[ 7/2 → 15/4 | s:bd ]",
|
||||||
|
"[ 15/4 → 4/1 | s:bd ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "firstOf" example index 0 1`] = `
|
exports[`runs examples > example "firstOf" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:g3 ]",
|
"[ 0/1 → 1/4 | note:g3 ]",
|
||||||
@@ -4092,35 +4156,6 @@ exports[`runs examples > example "floor" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "fm" example index 0 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/6 | note:c fmi:0 ]",
|
|
||||||
"[ 1/6 → 1/3 | note:e fmi:0 ]",
|
|
||||||
"[ 1/3 → 1/2 | note:g fmi:0 ]",
|
|
||||||
"[ 1/2 → 2/3 | note:b fmi:0 ]",
|
|
||||||
"[ 2/3 → 5/6 | note:g fmi:0 ]",
|
|
||||||
"[ 5/6 → 1/1 | note:e fmi:0 ]",
|
|
||||||
"[ 1/1 → 7/6 | note:c fmi:1 ]",
|
|
||||||
"[ 7/6 → 4/3 | note:e fmi:1 ]",
|
|
||||||
"[ 4/3 → 3/2 | note:g fmi:1 ]",
|
|
||||||
"[ 3/2 → 5/3 | note:b fmi:1 ]",
|
|
||||||
"[ 5/3 → 11/6 | note:g fmi:1 ]",
|
|
||||||
"[ 11/6 → 2/1 | note:e fmi:1 ]",
|
|
||||||
"[ 2/1 → 13/6 | note:c fmi:2 ]",
|
|
||||||
"[ 13/6 → 7/3 | note:e fmi:2 ]",
|
|
||||||
"[ 7/3 → 5/2 | note:g fmi:2 ]",
|
|
||||||
"[ 5/2 → 8/3 | note:b fmi:2 ]",
|
|
||||||
"[ 8/3 → 17/6 | note:g fmi:2 ]",
|
|
||||||
"[ 17/6 → 3/1 | note:e fmi:2 ]",
|
|
||||||
"[ 3/1 → 19/6 | note:c fmi:8 ]",
|
|
||||||
"[ 19/6 → 10/3 | note:e fmi:8 ]",
|
|
||||||
"[ 10/3 → 7/2 | note:g fmi:8 ]",
|
|
||||||
"[ 7/2 → 11/3 | note:b fmi:8 ]",
|
|
||||||
"[ 11/3 → 23/6 | note:g fmi:8 ]",
|
|
||||||
"[ 23/6 → 4/1 | note:e fmi:8 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "fmattack" example index 0 1`] = `
|
exports[`runs examples > example "fmattack" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/6 | note:c fmi:4 fmattack:0 ]",
|
"[ 0/1 → 1/6 | note:c fmi:4 fmattack:0 ]",
|
||||||
@@ -4237,6 +4272,72 @@ exports[`runs examples > example "fmh" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "fmi" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/6 | note:c fmi:0 ]",
|
||||||
|
"[ 1/6 → 1/3 | note:e fmi:0 ]",
|
||||||
|
"[ 1/3 → 1/2 | note:g fmi:0 ]",
|
||||||
|
"[ 1/2 → 2/3 | note:b fmi:0 ]",
|
||||||
|
"[ 2/3 → 5/6 | note:g fmi:0 ]",
|
||||||
|
"[ 5/6 → 1/1 | note:e fmi:0 ]",
|
||||||
|
"[ 1/1 → 7/6 | note:c fmi:1 ]",
|
||||||
|
"[ 7/6 → 4/3 | note:e fmi:1 ]",
|
||||||
|
"[ 4/3 → 3/2 | note:g fmi:1 ]",
|
||||||
|
"[ 3/2 → 5/3 | note:b fmi:1 ]",
|
||||||
|
"[ 5/3 → 11/6 | note:g fmi:1 ]",
|
||||||
|
"[ 11/6 → 2/1 | note:e fmi:1 ]",
|
||||||
|
"[ 2/1 → 13/6 | note:c fmi:2 ]",
|
||||||
|
"[ 13/6 → 7/3 | note:e fmi:2 ]",
|
||||||
|
"[ 7/3 → 5/2 | note:g fmi:2 ]",
|
||||||
|
"[ 5/2 → 8/3 | note:b fmi:2 ]",
|
||||||
|
"[ 8/3 → 17/6 | note:g fmi:2 ]",
|
||||||
|
"[ 17/6 → 3/1 | note:e fmi:2 ]",
|
||||||
|
"[ 3/1 → 19/6 | note:c fmi:8 ]",
|
||||||
|
"[ 19/6 → 10/3 | note:e fmi:8 ]",
|
||||||
|
"[ 10/3 → 7/2 | note:g fmi:8 ]",
|
||||||
|
"[ 7/2 → 11/3 | note:b fmi:8 ]",
|
||||||
|
"[ 11/3 → 23/6 | note:g fmi:8 ]",
|
||||||
|
"[ 23/6 → 4/1 | note:e fmi:8 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "fmi" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:sine note:F1 fmi:4 fmi2:0 fmi3:0 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 1/8 → 1/4 | s:sine note:F1 fmi:4 fmi2:2.7408622801303864 fmi3:0.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 1/4 → 3/8 | s:sine note:F1 fmi:4 fmi2:1.479038767516613 fmi3:0.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 3/8 → 1/2 | s:sine note:F1 fmi:4 fmi2:1.605570062994957 fmi3:0.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 1/2 → 5/8 | s:sine note:F1 fmi:4 fmi2:1.041922464966774 fmi3:0.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 5/8 → 3/4 | s:sine note:F1 fmi:4 fmi2:0.5425434708595276 fmi3:0.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 3/4 → 7/8 | s:sine note:F1 fmi:4 fmi2:0.7833059206604958 fmi3:0.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 7/8 → 1/1 | s:sine note:F1 fmi:4 fmi2:1.590524323284626 fmi3:0.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 1/1 → 9/8 | s:sine note:F1 fmi:4 fmi2:2.078168660402298 fmi3:1 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 9/8 → 5/4 | s:sine note:F1 fmi:4 fmi2:2.689958058297634 fmi3:1.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 5/4 → 11/8 | s:sine note:F1 fmi:4 fmi2:2.914912812411785 fmi3:1.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 11/8 → 3/2 | s:sine note:F1 fmi:4 fmi2:0.7312150076031685 fmi3:1.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 3/2 → 13/8 | s:sine note:F1 fmi:4 fmi2:2.4336322993040085 fmi3:1.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 13/8 → 7/4 | s:sine note:F1 fmi:4 fmi2:1.987018845975399 fmi3:1.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 7/4 → 15/8 | s:sine note:F1 fmi:4 fmi2:0.8189515843987465 fmi3:1.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 15/8 → 2/1 | s:sine note:F1 fmi:4 fmi2:2.3782287165522575 fmi3:1.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 2/1 → 17/8 | s:sine note:F1 fmi:4 fmi2:3.838108479976654 fmi3:2 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 17/8 → 9/4 | s:sine note:F1 fmi:4 fmi2:3.613561175763607 fmi3:2.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 9/4 → 19/8 | s:sine note:F1 fmi:4 fmi2:1.3819300457835197 fmi3:2.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 19/8 → 5/2 | s:sine note:F1 fmi:4 fmi2:3.346026562154293 fmi3:2.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 5/2 → 21/8 | s:sine note:F1 fmi:4 fmi2:1.8344642966985703 fmi3:2.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 21/8 → 11/4 | s:sine note:F1 fmi:4 fmi2:0.6407739371061325 fmi3:2.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 11/4 → 23/8 | s:sine note:F1 fmi:4 fmi2:2.5329310819506645 fmi3:2.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 23/8 → 3/1 | s:sine note:F1 fmi:4 fmi2:0.06501668691635132 fmi3:2.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 3/1 → 25/8 | s:sine note:F1 fmi:4 fmi2:0.8691564425826073 fmi3:3 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 25/8 → 13/4 | s:sine note:F1 fmi:4 fmi2:1.3729755133390427 fmi3:3.125 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 13/4 → 27/8 | s:sine note:F1 fmi:4 fmi2:3.9694602862000465 fmi3:3.25 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 27/8 → 7/2 | s:sine note:F1 fmi:4 fmi2:1.6347672045230865 fmi3:3.375 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 7/2 → 29/8 | s:sine note:F1 fmi:4 fmi2:1.639795258641243 fmi3:3.5 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 29/8 → 15/4 | s:sine note:F1 fmi:4 fmi2:3.7565943151712418 fmi3:3.625 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 15/4 → 31/8 | s:sine note:F1 fmi:4 fmi2:3.2486692890524864 fmi3:3.75 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
"[ 31/8 → 4/1 | s:sine note:F1 fmi:4 fmi2:2.9446661546826363 fmi3:3.875 fmh:1.06 fmh2:10 fmh3:0.1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "fmsustain" example index 0 1`] = `
|
exports[`runs examples > example "fmsustain" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.1 fmsustain:1 ]",
|
||||||
@@ -6947,6 +7048,27 @@ exports[`runs examples > example "midin" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "midin" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/4 | note:c s:saw ]",
|
||||||
|
"[ 1/4 → 1/2 | note:a s:saw ]",
|
||||||
|
"[ 1/2 → 3/4 | note:f s:saw ]",
|
||||||
|
"[ 3/4 → 1/1 | note:e s:saw ]",
|
||||||
|
"[ 1/1 → 5/4 | note:c s:saw ]",
|
||||||
|
"[ 5/4 → 3/2 | note:a s:saw ]",
|
||||||
|
"[ 3/2 → 7/4 | note:f s:saw ]",
|
||||||
|
"[ 7/4 → 2/1 | note:e s:saw ]",
|
||||||
|
"[ 2/1 → 9/4 | note:c s:saw ]",
|
||||||
|
"[ 9/4 → 5/2 | note:a s:saw ]",
|
||||||
|
"[ 5/2 → 11/4 | note:f s:saw ]",
|
||||||
|
"[ 11/4 → 3/1 | note:e s:saw ]",
|
||||||
|
"[ 3/1 → 13/4 | note:c s:saw ]",
|
||||||
|
"[ 13/4 → 7/2 | note:a s:saw ]",
|
||||||
|
"[ 7/2 → 15/4 | note:f s:saw ]",
|
||||||
|
"[ 15/4 → 4/1 | note:e s:saw ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "midiport" example index 0 1`] = `
|
exports[`runs examples > example "midiport" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c midiport:0 ]",
|
"[ 0/1 → 1/4 | note:c midiport:0 ]",
|
||||||
@@ -9064,6 +9186,43 @@ exports[`runs examples > example "ratio" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "register" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:saw velocity:0 cutoff:0 ]",
|
||||||
|
"[ 1/8 → 1/4 | s:saw velocity:0.6852155700325966 cutoff:548.1724560260773 ]",
|
||||||
|
"[ 1/4 → 3/8 | s:saw velocity:0.36975969187915325 cutoff:295.8077535033226 ]",
|
||||||
|
"[ 3/8 → 1/2 | s:saw velocity:0.40139251574873924 cutoff:321.1140125989914 ]",
|
||||||
|
"[ 1/2 → 5/8 | s:saw velocity:0.2604806162416935 cutoff:208.3844929933548 ]",
|
||||||
|
"[ 5/8 → 3/4 | s:saw velocity:0.1356358677148819 cutoff:108.50869417190552 ]",
|
||||||
|
"[ 3/4 → 7/8 | s:saw velocity:0.19582648016512394 cutoff:156.66118413209915 ]",
|
||||||
|
"[ 7/8 → 1/1 | s:saw velocity:0.3976310808211565 cutoff:318.1048646569252 ]",
|
||||||
|
"[ 1/1 → 9/8 | s:saw velocity:0.5195421651005745 cutoff:415.6337320804596 ]",
|
||||||
|
"[ 9/8 → 5/4 | s:saw velocity:0.6724895145744085 cutoff:537.9916116595268 ]",
|
||||||
|
"[ 5/4 → 11/8 | s:saw velocity:0.7287282031029463 cutoff:582.982562482357 ]",
|
||||||
|
"[ 11/8 → 3/2 | s:saw velocity:0.18280375190079212 cutoff:146.2430015206337 ]",
|
||||||
|
"[ 3/2 → 13/8 | s:saw velocity:0.6084080748260021 cutoff:486.7264598608017 ]",
|
||||||
|
"[ 13/8 → 7/4 | s:saw velocity:0.49675471149384975 cutoff:397.4037691950798 ]",
|
||||||
|
"[ 7/4 → 15/8 | s:saw velocity:0.20473789609968662 cutoff:163.7903168797493 ]",
|
||||||
|
"[ 15/8 → 2/1 | s:saw velocity:0.5945571791380644 cutoff:475.6457433104515 ]",
|
||||||
|
"[ 2/1 → 17/8 | s:saw velocity:0.9595271199941635 cutoff:767.6216959953308 ]",
|
||||||
|
"[ 17/8 → 9/4 | s:saw velocity:0.9033902939409018 cutoff:722.7122351527214 ]",
|
||||||
|
"[ 9/4 → 19/8 | s:saw velocity:0.34548251144587994 cutoff:276.38600915670395 ]",
|
||||||
|
"[ 19/8 → 5/2 | s:saw velocity:0.8365066405385733 cutoff:669.2053124308586 ]",
|
||||||
|
"[ 5/2 → 21/8 | s:saw velocity:0.45861607417464256 cutoff:366.89285933971405 ]",
|
||||||
|
"[ 21/8 → 11/4 | s:saw velocity:0.16019348427653313 cutoff:128.1547874212265 ]",
|
||||||
|
"[ 11/4 → 23/8 | s:saw velocity:0.6332327704876661 cutoff:506.5862163901329 ]",
|
||||||
|
"[ 23/8 → 3/1 | s:saw velocity:0.01625417172908783 cutoff:13.003337383270264 ]",
|
||||||
|
"[ 3/1 → 25/8 | s:saw velocity:0.21728911064565182 cutoff:173.83128851652145 ]",
|
||||||
|
"[ 25/8 → 13/4 | s:saw velocity:0.34324387833476067 cutoff:274.59510266780853 ]",
|
||||||
|
"[ 13/4 → 27/8 | s:saw velocity:0.9923650715500116 cutoff:793.8920572400093 ]",
|
||||||
|
"[ 27/8 → 7/2 | s:saw velocity:0.40869180113077164 cutoff:326.9534409046173 ]",
|
||||||
|
"[ 7/2 → 29/8 | s:saw velocity:0.40994881466031075 cutoff:327.9590517282486 ]",
|
||||||
|
"[ 29/8 → 15/4 | s:saw velocity:0.9391485787928104 cutoff:751.3188630342484 ]",
|
||||||
|
"[ 15/4 → 31/8 | s:saw velocity:0.8121673222631216 cutoff:649.7338578104973 ]",
|
||||||
|
"[ 31/8 → 4/1 | s:saw velocity:0.7361665386706591 cutoff:588.9332309365273 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "release" example index 0 1`] = `
|
exports[`runs examples > example "release" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c3 release:0 ]",
|
"[ 0/1 → 1/4 | note:c3 release:0 ]",
|
||||||
@@ -9685,42 +9844,6 @@ exports[`runs examples > example "samples" example index 1 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "samples" example index 2 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/2 | s:noise ]",
|
|
||||||
"[ 1/2 → 3/4 | s:chimp n:0 ]",
|
|
||||||
"[ 3/4 → 1/1 | s:chimp n:0 ]",
|
|
||||||
"[ 1/1 → 3/2 | s:noise ]",
|
|
||||||
"[ 3/2 → 2/1 | s:chimp n:1 ]",
|
|
||||||
"[ 2/1 → 5/2 | s:noise ]",
|
|
||||||
"[ 5/2 → 11/4 | s:chimp n:0 ]",
|
|
||||||
"[ 11/4 → 3/1 | s:chimp n:0 ]",
|
|
||||||
"[ 3/1 → 7/2 | s:noise ]",
|
|
||||||
"[ 7/2 → 4/1 | s:chimp n:1 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "samples" example index 3 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/4 | s:chocolat ]",
|
|
||||||
"[ 1/4 → 1/2 | s:chocolat ]",
|
|
||||||
"[ 1/2 → 3/4 | s:chocolat ]",
|
|
||||||
"[ 3/4 → 1/1 | s:chocolat ]",
|
|
||||||
"[ 1/1 → 5/4 | s:chocolat ]",
|
|
||||||
"[ 5/4 → 3/2 | s:chocolat ]",
|
|
||||||
"[ 3/2 → 7/4 | s:chocolat ]",
|
|
||||||
"[ 7/4 → 2/1 | s:chocolat ]",
|
|
||||||
"[ 2/1 → 9/4 | s:chocolat ]",
|
|
||||||
"[ 9/4 → 5/2 | s:chocolat ]",
|
|
||||||
"[ 5/2 → 11/4 | s:chocolat ]",
|
|
||||||
"[ 11/4 → 3/1 | s:chocolat ]",
|
|
||||||
"[ 3/1 → 13/4 | s:chocolat ]",
|
|
||||||
"[ 13/4 → 7/2 | s:chocolat ]",
|
|
||||||
"[ 7/2 → 15/4 | s:chocolat ]",
|
|
||||||
"[ 15/4 → 4/1 | s:chocolat ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "saw" example index 0 1`] = `
|
exports[`runs examples > example "saw" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | note:c3 clip:0 ]",
|
"[ 0/1 → 1/8 | note:c3 clip:0 ]",
|
||||||
@@ -11725,6 +11848,75 @@ exports[`runs examples > example "sysexid" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "tag" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | s:square note:F1 cutoff:40 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 1/16 → 1/8 | s:square note:F1 cutoff:41.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 1/8 → 3/16 | s:square note:F1 cutoff:42.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 3/16 → 1/4 | s:square note:F1 cutoff:43.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 1/4 → 5/16 | s:square note:F1 cutoff:45 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 5/16 → 3/8 | s:square note:F1 cutoff:46.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 3/8 → 7/16 | s:square note:F1 cutoff:47.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 7/16 → 1/2 | s:square note:F1 cutoff:48.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 1/2 → 9/16 | s:square note:F1 cutoff:50 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 9/16 → 5/8 | s:square note:F1 cutoff:51.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 5/8 → 11/16 | s:square note:F1 cutoff:52.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 11/16 → 3/4 | s:square note:F1 cutoff:53.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 3/4 → 13/16 | s:square note:F1 cutoff:55 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 13/16 → 7/8 | s:square note:F1 cutoff:56.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 7/8 → 15/16 | s:square note:F1 cutoff:57.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 15/16 → 1/1 | s:square note:F1 cutoff:58.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 1/1 → 17/16 | s:saw note:F2 cutoff:60 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 17/16 → 9/8 | s:saw note:F2 cutoff:61.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 9/8 → 19/16 | s:saw note:F2 cutoff:62.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 19/16 → 5/4 | s:saw note:F2 cutoff:63.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 5/4 → 21/16 | s:saw note:F2 cutoff:65 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 21/16 → 11/8 | s:saw note:F2 cutoff:66.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 11/8 → 23/16 | s:saw note:F2 cutoff:67.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 23/16 → 3/2 | s:saw note:F2 cutoff:68.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 3/2 → 25/16 | s:saw note:F2 cutoff:70 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 25/16 → 13/8 | s:saw note:F2 cutoff:71.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 13/8 → 27/16 | s:saw note:F2 cutoff:72.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 27/16 → 7/4 | s:saw note:F2 cutoff:73.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 7/4 → 29/16 | s:saw note:F2 cutoff:75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 29/16 → 15/8 | s:saw note:F2 cutoff:76.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 15/8 → 31/16 | s:saw note:F2 cutoff:77.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 31/16 → 2/1 | s:saw note:F2 cutoff:78.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 2/1 → 33/16 | s:saw note:F1 cutoff:80 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 33/16 → 17/8 | s:saw note:F1 cutoff:78.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 17/8 → 35/16 | s:saw note:F1 cutoff:77.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 35/16 → 9/4 | s:saw note:F1 cutoff:76.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 9/4 → 37/16 | s:saw note:F1 cutoff:75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 37/16 → 19/8 | s:saw note:F1 cutoff:73.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 19/8 → 39/16 | s:saw note:F1 cutoff:72.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 39/16 → 5/2 | s:saw note:F1 cutoff:71.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 5/2 → 41/16 | s:saw note:F1 cutoff:70 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 41/16 → 21/8 | s:saw note:F1 cutoff:68.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 21/8 → 43/16 | s:saw note:F1 cutoff:67.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 43/16 → 11/4 | s:saw note:F1 cutoff:66.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 11/4 → 45/16 | s:saw note:F1 cutoff:65 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 45/16 → 23/8 | s:saw note:F1 cutoff:63.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 23/8 → 47/16 | s:saw note:F1 cutoff:62.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 47/16 → 3/1 | s:saw note:F1 cutoff:61.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 3/1 → 49/16 | s:square note:F1 cutoff:60 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 49/16 → 25/8 | s:square note:F1 cutoff:58.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 25/8 → 51/16 | s:square note:F1 cutoff:57.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 51/16 → 13/4 | s:square note:F1 cutoff:56.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 13/4 → 53/16 | s:square note:F1 cutoff:55 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 53/16 → 27/8 | s:square note:F1 cutoff:53.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 27/8 → 55/16 | s:square note:F1 cutoff:52.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 55/16 → 7/2 | s:square note:F1 cutoff:51.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 7/2 → 57/16 | s:square note:F1 cutoff:50 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 57/16 → 29/8 | s:square note:F1 cutoff:48.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 29/8 → 59/16 | s:square note:F1 cutoff:47.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 59/16 → 15/4 | s:square note:F1 cutoff:46.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 15/4 → 61/16 | s:square note:F1 cutoff:45 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 61/16 → 31/8 | s:square note:F1 cutoff:43.75 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 31/8 → 63/16 | s:square note:F1 cutoff:42.5 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
"[ 63/16 → 4/1 | s:square note:F1 cutoff:41.25 lpenv:5 resonance:4 lpdecay:0.15 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "take" example index 0 1`] = `
|
exports[`runs examples > example "take" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/2 | s:bd ]",
|
"[ 0/1 → 1/2 | s:bd ]",
|
||||||
@@ -11837,6 +12029,84 @@ exports[`runs examples > example "tour" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "transient" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/1 | s:bd transient:-1 ]",
|
||||||
|
"[ 1/1 → 2/1 | s:bd transient:-0.5 ]",
|
||||||
|
"[ 2/1 → 3/1 | s:bd transient:0 ]",
|
||||||
|
"[ 3/1 → 4/1 | s:bd transient:0.5 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "transient" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/16 → 1/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/8 → 3/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/16 → 1/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/4 → 5/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 5/16 → 3/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/8 → 7/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 7/16 → 1/2 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/2 → 9/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 9/16 → 5/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 5/8 → 11/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 11/16 → 3/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/4 → 13/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 13/16 → 7/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 7/8 → 15/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 15/16 → 1/1 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 1/1 → 17/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 17/16 → 9/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 9/8 → 19/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 19/16 → 5/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 5/4 → 21/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 21/16 → 11/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 11/8 → 23/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 23/16 → 3/2 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 3/2 → 25/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 25/16 → 13/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 13/8 → 27/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 27/16 → 7/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 7/4 → 29/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 29/16 → 15/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 15/8 → 31/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 31/16 → 2/1 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 2/1 → 33/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 33/16 → 17/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 17/8 → 35/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 35/16 → 9/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 9/4 → 37/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 37/16 → 19/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 19/8 → 39/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 39/16 → 5/2 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 5/2 → 41/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 41/16 → 21/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 21/8 → 43/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 43/16 → 11/4 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 11/4 → 45/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 45/16 → 23/8 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 23/8 → 47/16 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 47/16 → 3/1 | s:hh bank:tr909 transient:-1 transsustain:1 ]",
|
||||||
|
"[ 3/1 → 49/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 49/16 → 25/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 25/8 → 51/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 51/16 → 13/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 13/4 → 53/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 53/16 → 27/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 27/8 → 55/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 55/16 → 7/2 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 7/2 → 57/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 57/16 → 29/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 29/8 → 59/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 59/16 → 15/4 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 15/4 → 61/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 61/16 → 31/8 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 31/8 → 63/16 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
"[ 63/16 → 4/1 | s:hh bank:tr909 transient:1 transsustain:-1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "transpose" example index 0 1`] = `
|
exports[`runs examples > example "transpose" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:C2 ]",
|
"[ 0/1 → 1/4 | note:C2 ]",
|
||||||
|
|||||||
Reference in New Issue
Block a user