diff --git a/.eslintignore b/.eslintignore
index 13e635f3..58d3643d 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -18,4 +18,5 @@ vite.config.js
**/*.json
**/dev-dist
**/dist
-/src-tauri/target/**/*
\ No newline at end of file
+/src-tauri/target/**/*
+reverbGen.mjs
\ No newline at end of file
diff --git a/packages/codemirror/index.mjs b/packages/codemirror/index.mjs
index bf7ce971..c847c32c 100644
--- a/packages/codemirror/index.mjs
+++ b/packages/codemirror/index.mjs
@@ -1,3 +1,4 @@
export * from './codemirror.mjs';
export * from './highlight.mjs';
export * from './flash.mjs';
+export * from './slider.mjs';
diff --git a/packages/codemirror/slider.mjs b/packages/codemirror/slider.mjs
new file mode 100644
index 00000000..519e5610
--- /dev/null
+++ b/packages/codemirror/slider.mjs
@@ -0,0 +1,135 @@
+import { ref, pure } from '@strudel.cycles/core';
+import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view';
+import { StateEffect, StateField } from '@codemirror/state';
+
+export let sliderValues = {};
+const getSliderID = (from) => `slider_${from}`;
+
+export class SliderWidget extends WidgetType {
+ constructor(value, min, max, from, to, step, view) {
+ super();
+ this.value = value;
+ this.min = min;
+ this.max = max;
+ this.from = from;
+ this.originalFrom = from;
+ this.to = to;
+ this.step = step;
+ this.view = view;
+ }
+
+ eq() {
+ return false;
+ }
+
+ toDOM() {
+ let wrap = document.createElement('span');
+ wrap.setAttribute('aria-hidden', 'true');
+ wrap.className = 'cm-slider'; // inline-flex items-center
+ let slider = wrap.appendChild(document.createElement('input'));
+ slider.type = 'range';
+ slider.min = this.min;
+ slider.max = this.max;
+ slider.step = this.step ?? (this.max - this.min) / 1000;
+ slider.originalValue = this.value;
+ // to make sure the code stays in sync, let's save the original value
+ // becuase .value automatically clamps values so it'll desync with the code
+ slider.value = slider.originalValue;
+ slider.from = this.from;
+ slider.originalFrom = this.originalFrom;
+ slider.to = this.to;
+ slider.style = 'width:64px;margin-right:4px;transform:translateY(4px)';
+ this.slider = slider;
+ slider.addEventListener('input', (e) => {
+ const next = e.target.value;
+ let insert = next;
+ //let insert = next.toFixed(2);
+ const to = slider.from + slider.originalValue.length;
+ let change = { from: slider.from, to, insert };
+ slider.originalValue = insert;
+ slider.value = insert;
+ this.view.dispatch({ changes: change });
+ const id = getSliderID(slider.originalFrom); // matches id generated in transpiler
+ window.postMessage({ type: 'cm-slider', value: Number(next), id });
+ });
+ return wrap;
+ }
+
+ ignoreEvent(e) {
+ return true;
+ }
+}
+
+export const setWidgets = StateEffect.define();
+
+export const updateWidgets = (view, widgets) => {
+ view.dispatch({ effects: setWidgets.of(widgets) });
+};
+
+function getWidgets(widgetConfigs, view) {
+ return widgetConfigs.map(({ from, to, value, min, max, step }) => {
+ return Decoration.widget({
+ widget: new SliderWidget(value, min, max, from, to, step, view),
+ side: 0,
+ }).range(from /* , to */);
+ });
+}
+
+export const sliderPlugin = ViewPlugin.fromClass(
+ class {
+ decorations; //: DecorationSet
+
+ constructor(view /* : EditorView */) {
+ this.decorations = Decoration.set([]);
+ }
+
+ update(update /* : ViewUpdate */) {
+ update.transactions.forEach((tr) => {
+ if (tr.docChanged) {
+ this.decorations = this.decorations.map(tr.changes);
+ const iterator = this.decorations.iter();
+ while (iterator.value) {
+ // when the widgets are moved, we need to tell the dom node the current position
+ // this is important because the updateSliderValue function has to work with the dom node
+ if (iterator.value?.widget?.slider) {
+ iterator.value.widget.slider.from = iterator.from;
+ iterator.value.widget.slider.to = iterator.to;
+ }
+ iterator.next();
+ }
+ }
+ for (let e of tr.effects) {
+ if (e.is(setWidgets)) {
+ this.decorations = Decoration.set(getWidgets(e.value, update.view));
+ }
+ }
+ });
+ }
+ },
+ {
+ decorations: (v) => v.decorations,
+ },
+);
+
+export let slider = (value) => {
+ console.warn('slider will only work when the transpiler is used... passing value as is');
+ return pure(value);
+};
+// function transpiled from slider = (value, min, max)
+export let sliderWithID = (id, value, min, max) => {
+ sliderValues[id] = value; // sync state at eval time (code -> state)
+ return ref(() => sliderValues[id]); // use state at query time
+};
+// update state when sliders are moved
+if (typeof window !== 'undefined') {
+ window.addEventListener('message', (e) => {
+ if (e.data.type === 'cm-slider') {
+ if (sliderValues[e.data.id] !== undefined) {
+ // update state when slider is moved
+ sliderValues[e.data.id] = e.data.value;
+ } else {
+ console.warn(`slider with id "${e.data.id}" is not registered. Only ${Object.keys(sliderValues)}`);
+ }
+ }
+ });
+}
diff --git a/packages/mini/krill-parser.js b/packages/mini/krill-parser.js
index e85e3363..7831087b 100644
--- a/packages/mini/krill-parser.js
+++ b/packages/mini/krill-parser.js
@@ -200,20 +200,21 @@ function peg$parse(input, options) {
var peg$c23 = "*";
var peg$c24 = "?";
var peg$c25 = ":";
- var peg$c26 = "struct";
- var peg$c27 = "target";
- var peg$c28 = "euclid";
- var peg$c29 = "slow";
- var peg$c30 = "rotL";
- var peg$c31 = "rotR";
- var peg$c32 = "fast";
- var peg$c33 = "scale";
- var peg$c34 = "//";
- var peg$c35 = "cat";
- var peg$c36 = "$";
- var peg$c37 = "setcps";
- var peg$c38 = "setbpm";
- var peg$c39 = "hush";
+ var peg$c26 = "..";
+ var peg$c27 = "struct";
+ var peg$c28 = "target";
+ var peg$c29 = "euclid";
+ var peg$c30 = "slow";
+ var peg$c31 = "rotL";
+ var peg$c32 = "rotR";
+ var peg$c33 = "fast";
+ var peg$c34 = "scale";
+ var peg$c35 = "//";
+ var peg$c36 = "cat";
+ var peg$c37 = "$";
+ var peg$c38 = "setcps";
+ var peg$c39 = "setbpm";
+ var peg$c40 = "hush";
var peg$r0 = /^[1-9]/;
var peg$r1 = /^[eE]/;
@@ -255,64 +256,67 @@ function peg$parse(input, options) {
var peg$e30 = peg$literalExpectation("*", false);
var peg$e31 = peg$literalExpectation("?", false);
var peg$e32 = peg$literalExpectation(":", false);
- var peg$e33 = peg$literalExpectation("struct", false);
- var peg$e34 = peg$literalExpectation("target", false);
- var peg$e35 = peg$literalExpectation("euclid", false);
- var peg$e36 = peg$literalExpectation("slow", false);
- var peg$e37 = peg$literalExpectation("rotL", false);
- var peg$e38 = peg$literalExpectation("rotR", false);
- var peg$e39 = peg$literalExpectation("fast", false);
- var peg$e40 = peg$literalExpectation("scale", false);
- var peg$e41 = peg$literalExpectation("//", false);
- var peg$e42 = peg$classExpectation(["\n"], true, false);
- var peg$e43 = peg$literalExpectation("cat", false);
- var peg$e44 = peg$literalExpectation("$", false);
- var peg$e45 = peg$literalExpectation("setcps", false);
- var peg$e46 = peg$literalExpectation("setbpm", false);
- var peg$e47 = peg$literalExpectation("hush", false);
+ var peg$e33 = peg$literalExpectation("..", false);
+ var peg$e34 = peg$literalExpectation("struct", false);
+ var peg$e35 = peg$literalExpectation("target", false);
+ var peg$e36 = peg$literalExpectation("euclid", false);
+ var peg$e37 = peg$literalExpectation("slow", false);
+ var peg$e38 = peg$literalExpectation("rotL", false);
+ var peg$e39 = peg$literalExpectation("rotR", false);
+ var peg$e40 = peg$literalExpectation("fast", false);
+ var peg$e41 = peg$literalExpectation("scale", false);
+ var peg$e42 = peg$literalExpectation("//", false);
+ var peg$e43 = peg$classExpectation(["\n"], true, false);
+ var peg$e44 = peg$literalExpectation("cat", false);
+ var peg$e45 = peg$literalExpectation("$", false);
+ var peg$e46 = peg$literalExpectation("setcps", false);
+ var peg$e47 = peg$literalExpectation("setbpm", false);
+ var peg$e48 = peg$literalExpectation("hush", false);
var peg$f0 = function() { return parseFloat(text()); };
- var peg$f1 = function(chars) { return new AtomStub(chars.join("")) };
- var peg$f2 = function(s) { return s };
- var peg$f3 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
- var peg$f4 = function(a) { return a };
- var peg$f5 = function(s) { s.arguments_.alignment = 'slowcat'; return s; };
- var peg$f6 = function(a) { return x => x.options_['weight'] = a };
- var peg$f7 = function(a) { return x => x.options_['reps'] = a };
- var peg$f8 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
- var peg$f9 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) };
- var peg$f10 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) };
- var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) };
- var peg$f12 = function(s) { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) };
- var peg$f13 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
+ var peg$f1 = function() { return parseInt(text()); };
+ var peg$f2 = function(chars) { return new AtomStub(chars.join("")) };
+ var peg$f3 = function(s) { return s };
+ var peg$f4 = function(s, stepsPerCycle) { s.arguments_.stepsPerCycle = stepsPerCycle ; return s; };
+ var peg$f5 = function(a) { return a };
+ var peg$f6 = function(s) { s.arguments_.alignment = 'slowcat'; return s; };
+ var peg$f7 = function(a) { return x => x.options_['weight'] = a };
+ var peg$f8 = function(a) { return x => x.options_['reps'] = a };
+ var peg$f9 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) };
+ var peg$f10 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) };
+ var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) };
+ var peg$f12 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) };
+ var peg$f13 = function(s) { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) };
+ var peg$f14 = function(s) { return x => x.options_['ops'].push({ type_: "range", arguments_ :{ element:s } }) };
+ var peg$f15 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
for (const op of ops) {
op(result);
}
return result;
};
- var peg$f14 = function(s) { return new PatternStub(s, 'fastcat'); };
- var peg$f15 = function(tail) { return { alignment: 'stack', list: tail }; };
- var peg$f16 = function(tail) { return { alignment: 'rand', list: tail, seed: seed++ }; };
- var peg$f17 = function(head, tail) { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } };
- var peg$f18 = function(head, tail) { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); };
- var peg$f19 = function(sc) { return sc; };
- var peg$f20 = function(s) { return { name: "struct", args: { mini:s }}};
- var peg$f21 = function(s) { return { name: "target", args : { name:s}}};
- var peg$f22 = function(p, s, r) { return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}};
- var peg$f23 = function(a) { return { name: "stretch", args :{ amount: a}}};
- var peg$f24 = function(a) { return { name: "shift", args :{ amount: "-"+a}}};
- var peg$f25 = function(a) { return { name: "shift", args :{ amount: a}}};
- var peg$f26 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}};
- var peg$f27 = function(s) { return { name: "scale", args :{ scale: s.join("")}}};
- var peg$f28 = function(s, v) { return v};
- var peg$f29 = function(s, ss) { ss.unshift(s); return new PatternStub(ss, 'slowcat'); };
- var peg$f30 = function(sg) {return sg};
- var peg$f31 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)};
- var peg$f32 = function(sc) { return sc };
- var peg$f33 = function(c) { return c };
- var peg$f34 = function(v) { return new CommandStub("setcps", { value: v})};
- var peg$f35 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})};
- var peg$f36 = function() { return new CommandStub("hush")};
+ var peg$f16 = function(s) { return new PatternStub(s, 'fastcat'); };
+ var peg$f17 = function(tail) { return { alignment: 'stack', list: tail }; };
+ var peg$f18 = function(tail) { return { alignment: 'rand', list: tail, seed: seed++ }; };
+ var peg$f19 = function(head, tail) { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } };
+ var peg$f20 = function(head, tail) { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); };
+ var peg$f21 = function(sc) { return sc; };
+ var peg$f22 = function(s) { return { name: "struct", args: { mini:s }}};
+ var peg$f23 = function(s) { return { name: "target", args : { name:s}}};
+ var peg$f24 = function(p, s, r) { return { name: "bjorklund", args :{ pulse: p, step:parseInt(s) }}};
+ var peg$f25 = function(a) { return { name: "stretch", args :{ amount: a}}};
+ var peg$f26 = function(a) { return { name: "shift", args :{ amount: "-"+a}}};
+ var peg$f27 = function(a) { return { name: "shift", args :{ amount: a}}};
+ var peg$f28 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}};
+ var peg$f29 = function(s) { return { name: "scale", args :{ scale: s.join("")}}};
+ var peg$f30 = function(s, v) { return v};
+ var peg$f31 = function(s, ss) { ss.unshift(s); return new PatternStub(ss, 'slowcat'); };
+ var peg$f32 = function(sg) {return sg};
+ var peg$f33 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)};
+ var peg$f34 = function(sc) { return sc };
+ var peg$f35 = function(c) { return c };
+ var peg$f36 = function(v) { return new CommandStub("setcps", { value: v})};
+ var peg$f37 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})};
+ var peg$f38 = function() { return new CommandStub("hush")};
var peg$currPos = 0;
var peg$savedPos = 0;
var peg$posDetailsCache = [{ line: 1, column: 1 }];
@@ -651,6 +655,26 @@ function peg$parse(input, options) {
return s0;
}
+ function peg$parseintneg() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ s1 = peg$parseminus();
+ if (s1 === peg$FAILED) {
+ s1 = null;
+ }
+ s2 = peg$parseint();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f1();
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
function peg$parseminus() {
var s0;
@@ -884,7 +908,7 @@ function peg$parse(input, options) {
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
peg$savedPos = s0;
- s0 = peg$f1(s2);
+ s0 = peg$f2(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -920,7 +944,7 @@ function peg$parse(input, options) {
if (s6 !== peg$FAILED) {
s7 = peg$parsews();
peg$savedPos = s0;
- s0 = peg$f2(s4);
+ s0 = peg$f3(s4);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -968,7 +992,7 @@ function peg$parse(input, options) {
}
s8 = peg$parsews();
peg$savedPos = s0;
- s0 = peg$f3(s4, s7);
+ s0 = peg$f4(s4, s7);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1000,7 +1024,7 @@ function peg$parse(input, options) {
s2 = peg$parseslice();
if (s2 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f4(s2);
+ s0 = peg$f5(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1040,7 +1064,7 @@ function peg$parse(input, options) {
if (s6 !== peg$FAILED) {
s7 = peg$parsews();
peg$savedPos = s0;
- s0 = peg$f5(s4);
+ s0 = peg$f6(s4);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1090,6 +1114,9 @@ function peg$parse(input, options) {
s0 = peg$parseop_degrade();
if (s0 === peg$FAILED) {
s0 = peg$parseop_tail();
+ if (s0 === peg$FAILED) {
+ s0 = peg$parseop_range();
+ }
}
}
}
@@ -1115,7 +1142,7 @@ function peg$parse(input, options) {
s2 = peg$parsenumber();
if (s2 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f6(s2);
+ s0 = peg$f7(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1143,7 +1170,7 @@ function peg$parse(input, options) {
s2 = peg$parsenumber();
if (s2 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f7(s2);
+ s0 = peg$f8(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1197,7 +1224,7 @@ function peg$parse(input, options) {
}
if (s13 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f8(s3, s7, s11);
+ s0 = peg$f9(s3, s7, s11);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1237,7 +1264,7 @@ function peg$parse(input, options) {
s2 = peg$parseslice();
if (s2 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f9(s2);
+ s0 = peg$f10(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1265,7 +1292,7 @@ function peg$parse(input, options) {
s2 = peg$parseslice();
if (s2 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f10(s2);
+ s0 = peg$f11(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1295,7 +1322,7 @@ function peg$parse(input, options) {
s2 = null;
}
peg$savedPos = s0;
- s0 = peg$f11(s2);
+ s0 = peg$f12(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1319,7 +1346,35 @@ function peg$parse(input, options) {
s2 = peg$parseslice();
if (s2 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f12(s2);
+ s0 = peg$f13(s2);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parseop_range() {
+ var s0, s1, s2;
+
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 2) === peg$c26) {
+ s1 = peg$c26;
+ peg$currPos += 2;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e33); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parseslice();
+ if (s2 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f14(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1345,7 +1400,7 @@ function peg$parse(input, options) {
s3 = peg$parseslice_op();
}
peg$savedPos = s0;
- s0 = peg$f13(s1, s2);
+ s0 = peg$f15(s1, s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1370,7 +1425,7 @@ function peg$parse(input, options) {
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
- s1 = peg$f14(s1);
+ s1 = peg$f16(s1);
}
s0 = s1;
@@ -1419,7 +1474,7 @@ function peg$parse(input, options) {
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
- s1 = peg$f15(s1);
+ s1 = peg$f17(s1);
}
s0 = s1;
@@ -1468,7 +1523,7 @@ function peg$parse(input, options) {
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
- s1 = peg$f16(s1);
+ s1 = peg$f18(s1);
}
s0 = s1;
@@ -1489,7 +1544,7 @@ function peg$parse(input, options) {
s2 = null;
}
peg$savedPos = s0;
- s0 = peg$f17(s1, s2);
+ s0 = peg$f19(s1, s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1509,7 +1564,7 @@ function peg$parse(input, options) {
s2 = null;
}
peg$savedPos = s0;
- s0 = peg$f18(s1, s2);
+ s0 = peg$f20(s1, s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1532,7 +1587,7 @@ function peg$parse(input, options) {
s6 = peg$parsequote();
if (s6 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f19(s4);
+ s0 = peg$f21(s4);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1582,19 +1637,19 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
- if (input.substr(peg$currPos, 6) === peg$c26) {
- s1 = peg$c26;
+ if (input.substr(peg$currPos, 6) === peg$c27) {
+ s1 = peg$c27;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e33); }
+ if (peg$silentFails === 0) { peg$fail(peg$e34); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
s3 = peg$parsemini_or_operator();
if (s3 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f20(s3);
+ s0 = peg$f22(s3);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1611,12 +1666,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3, s4, s5;
s0 = peg$currPos;
- if (input.substr(peg$currPos, 6) === peg$c27) {
- s1 = peg$c27;
+ if (input.substr(peg$currPos, 6) === peg$c28) {
+ s1 = peg$c28;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e34); }
+ if (peg$silentFails === 0) { peg$fail(peg$e35); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@@ -1627,7 +1682,7 @@ function peg$parse(input, options) {
s5 = peg$parsequote();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f21(s4);
+ s0 = peg$f23(s4);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1652,12 +1707,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3, s4, s5, s6, s7;
s0 = peg$currPos;
- if (input.substr(peg$currPos, 6) === peg$c28) {
- s1 = peg$c28;
+ if (input.substr(peg$currPos, 6) === peg$c29) {
+ s1 = peg$c29;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e35); }
+ if (peg$silentFails === 0) { peg$fail(peg$e36); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@@ -1672,7 +1727,7 @@ function peg$parse(input, options) {
s7 = null;
}
peg$savedPos = s0;
- s0 = peg$f22(s3, s5, s7);
+ s0 = peg$f24(s3, s5, s7);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1692,35 +1747,6 @@ function peg$parse(input, options) {
function peg$parseslow() {
var s0, s1, s2, s3;
- s0 = peg$currPos;
- if (input.substr(peg$currPos, 4) === peg$c29) {
- s1 = peg$c29;
- peg$currPos += 4;
- } else {
- s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e36); }
- }
- if (s1 !== peg$FAILED) {
- s2 = peg$parsews();
- s3 = peg$parsenumber();
- if (s3 !== peg$FAILED) {
- peg$savedPos = s0;
- s0 = peg$f23(s3);
- } else {
- peg$currPos = s0;
- s0 = peg$FAILED;
- }
- } else {
- peg$currPos = s0;
- s0 = peg$FAILED;
- }
-
- return s0;
- }
-
- function peg$parserotL() {
- var s0, s1, s2, s3;
-
s0 = peg$currPos;
if (input.substr(peg$currPos, 4) === peg$c30) {
s1 = peg$c30;
@@ -1729,35 +1755,6 @@ function peg$parse(input, options) {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e37); }
}
- if (s1 !== peg$FAILED) {
- s2 = peg$parsews();
- s3 = peg$parsenumber();
- if (s3 !== peg$FAILED) {
- peg$savedPos = s0;
- s0 = peg$f24(s3);
- } else {
- peg$currPos = s0;
- s0 = peg$FAILED;
- }
- } else {
- peg$currPos = s0;
- s0 = peg$FAILED;
- }
-
- return s0;
- }
-
- function peg$parserotR() {
- var s0, s1, s2, s3;
-
- s0 = peg$currPos;
- if (input.substr(peg$currPos, 4) === peg$c31) {
- s1 = peg$c31;
- peg$currPos += 4;
- } else {
- s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e38); }
- }
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
s3 = peg$parsenumber();
@@ -1776,16 +1773,16 @@ function peg$parse(input, options) {
return s0;
}
- function peg$parsefast() {
+ function peg$parserotL() {
var s0, s1, s2, s3;
s0 = peg$currPos;
- if (input.substr(peg$currPos, 4) === peg$c32) {
- s1 = peg$c32;
+ if (input.substr(peg$currPos, 4) === peg$c31) {
+ s1 = peg$c31;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e39); }
+ if (peg$silentFails === 0) { peg$fail(peg$e38); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@@ -1805,16 +1802,74 @@ function peg$parse(input, options) {
return s0;
}
+ function peg$parserotR() {
+ var s0, s1, s2, s3;
+
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 4) === peg$c32) {
+ s1 = peg$c32;
+ peg$currPos += 4;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e39); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parsews();
+ s3 = peg$parsenumber();
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f27(s3);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
+ function peg$parsefast() {
+ var s0, s1, s2, s3;
+
+ s0 = peg$currPos;
+ if (input.substr(peg$currPos, 4) === peg$c33) {
+ s1 = peg$c33;
+ peg$currPos += 4;
+ } else {
+ s1 = peg$FAILED;
+ if (peg$silentFails === 0) { peg$fail(peg$e40); }
+ }
+ if (s1 !== peg$FAILED) {
+ s2 = peg$parsews();
+ s3 = peg$parsenumber();
+ if (s3 !== peg$FAILED) {
+ peg$savedPos = s0;
+ s0 = peg$f28(s3);
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+ } else {
+ peg$currPos = s0;
+ s0 = peg$FAILED;
+ }
+
+ return s0;
+ }
+
function peg$parsescale() {
var s0, s1, s2, s3, s4, s5;
s0 = peg$currPos;
- if (input.substr(peg$currPos, 5) === peg$c33) {
- s1 = peg$c33;
+ if (input.substr(peg$currPos, 5) === peg$c34) {
+ s1 = peg$c34;
peg$currPos += 5;
} else {
s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e40); }
+ if (peg$silentFails === 0) { peg$fail(peg$e41); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@@ -1834,7 +1889,7 @@ function peg$parse(input, options) {
s5 = peg$parsequote();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f27(s4);
+ s0 = peg$f29(s4);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -1859,12 +1914,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
- if (input.substr(peg$currPos, 2) === peg$c34) {
- s1 = peg$c34;
+ if (input.substr(peg$currPos, 2) === peg$c35) {
+ s1 = peg$c35;
peg$currPos += 2;
} else {
s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e41); }
+ if (peg$silentFails === 0) { peg$fail(peg$e42); }
}
if (s1 !== peg$FAILED) {
s2 = [];
@@ -1873,7 +1928,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s3 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e42); }
+ if (peg$silentFails === 0) { peg$fail(peg$e43); }
}
while (s3 !== peg$FAILED) {
s2.push(s3);
@@ -1882,7 +1937,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s3 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e42); }
+ if (peg$silentFails === 0) { peg$fail(peg$e43); }
}
}
s1 = [s1, s2];
@@ -1899,12 +1954,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9;
s0 = peg$currPos;
- if (input.substr(peg$currPos, 3) === peg$c35) {
- s1 = peg$c35;
+ if (input.substr(peg$currPos, 3) === peg$c36) {
+ s1 = peg$c36;
peg$currPos += 3;
} else {
s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e43); }
+ if (peg$silentFails === 0) { peg$fail(peg$e44); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@@ -1926,7 +1981,7 @@ function peg$parse(input, options) {
s9 = peg$parsemini_or_operator();
if (s9 !== peg$FAILED) {
peg$savedPos = s7;
- s7 = peg$f28(s5, s9);
+ s7 = peg$f30(s5, s9);
} else {
peg$currPos = s7;
s7 = peg$FAILED;
@@ -1943,7 +1998,7 @@ function peg$parse(input, options) {
s9 = peg$parsemini_or_operator();
if (s9 !== peg$FAILED) {
peg$savedPos = s7;
- s7 = peg$f28(s5, s9);
+ s7 = peg$f30(s5, s9);
} else {
peg$currPos = s7;
s7 = peg$FAILED;
@@ -1963,7 +2018,7 @@ function peg$parse(input, options) {
}
if (s8 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f29(s5, s6);
+ s0 = peg$f31(s5, s6);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -2009,7 +2064,7 @@ function peg$parse(input, options) {
s4 = peg$parsecomment();
}
peg$savedPos = s0;
- s0 = peg$f30(s1);
+ s0 = peg$f32(s1);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -2020,18 +2075,18 @@ function peg$parse(input, options) {
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 36) {
- s3 = peg$c36;
+ s3 = peg$c37;
peg$currPos++;
} else {
s3 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e44); }
+ if (peg$silentFails === 0) { peg$fail(peg$e45); }
}
if (s3 !== peg$FAILED) {
s4 = peg$parsews();
s5 = peg$parsemini_or_operator();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f31(s1, s5);
+ s0 = peg$f33(s1, s5);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -2056,7 +2111,7 @@ function peg$parse(input, options) {
s1 = peg$parsemini_or_operator();
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
- s1 = peg$f32(s1);
+ s1 = peg$f34(s1);
}
s0 = s1;
if (s0 === peg$FAILED) {
@@ -2089,7 +2144,7 @@ function peg$parse(input, options) {
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
peg$savedPos = s0;
- s0 = peg$f33(s2);
+ s0 = peg$f35(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -2102,19 +2157,19 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
- if (input.substr(peg$currPos, 6) === peg$c37) {
- s1 = peg$c37;
+ if (input.substr(peg$currPos, 6) === peg$c38) {
+ s1 = peg$c38;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e45); }
+ if (peg$silentFails === 0) { peg$fail(peg$e46); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
s3 = peg$parsenumber();
if (s3 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f34(s3);
+ s0 = peg$f36(s3);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -2131,19 +2186,19 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
- if (input.substr(peg$currPos, 6) === peg$c38) {
- s1 = peg$c38;
+ if (input.substr(peg$currPos, 6) === peg$c39) {
+ s1 = peg$c39;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e46); }
+ if (peg$silentFails === 0) { peg$fail(peg$e47); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
s3 = peg$parsenumber();
if (s3 !== peg$FAILED) {
peg$savedPos = s0;
- s0 = peg$f35(s3);
+ s0 = peg$f37(s3);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@@ -2160,16 +2215,16 @@ function peg$parse(input, options) {
var s0, s1;
s0 = peg$currPos;
- if (input.substr(peg$currPos, 4) === peg$c39) {
- s1 = peg$c39;
+ if (input.substr(peg$currPos, 4) === peg$c40) {
+ s1 = peg$c40;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
- if (peg$silentFails === 0) { peg$fail(peg$e47); }
+ if (peg$silentFails === 0) { peg$fail(peg$e48); }
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
- s1 = peg$f36();
+ s1 = peg$f38();
}
s0 = s1;
diff --git a/packages/mini/krill.pegjs b/packages/mini/krill.pegjs
index 72b453be..f52743bf 100644
--- a/packages/mini/krill.pegjs
+++ b/packages/mini/krill.pegjs
@@ -79,6 +79,9 @@ frac
int
= zero / (digit1_9 DIGIT*)
+intneg
+ = minus? int { return parseInt(text()); }
+
minus
= "-"
@@ -123,7 +126,7 @@ slice = step / sub_cycle / polymeter / slow_sequence
// slice modifier affects the timing/size of a slice (e.g. [a b c]@3)
// at this point, we assume we can represent them as regular sequence operators
-slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade / op_tail
+slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade / op_tail / op_range
op_weight = "@" a:number
{ return x => x.options_['weight'] = a }
@@ -146,6 +149,9 @@ op_degrade = "?"a:number?
op_tail = ":" s:slice
{ return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) }
+op_range = ".." s:slice
+ { return x => x.options_['ops'].push({ type_: "range", arguments_ :{ element:s } }) }
+
// a slice with an modifier applied i.e [bd@4 sd@3]@2 hh]
slice_with_ops = s:slice ops:slice_op*
{ const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
diff --git a/packages/mini/mini.mjs b/packages/mini/mini.mjs
index 3321e7bc..8e6b844f 100644
--- a/packages/mini/mini.mjs
+++ b/packages/mini/mini.mjs
@@ -45,6 +45,17 @@ const applyOptions = (parent, enter) => (pat, i) => {
pat = pat.fmap((a) => (b) => Array.isArray(a) ? [...a, b] : [a, b]).appLeft(friend);
break;
}
+ case 'range': {
+ const friend = enter(op.arguments_.element);
+ pat = strudel.reify(pat);
+ const arrayRange = (start, stop, step = 1) =>
+ Array.from({ length: Math.abs(stop - start) / step + 1 }, (value, index) =>
+ start < stop ? start + index * step : start - index * step,
+ );
+ let range = (apat, bpat) => apat.squeezeBind((a) => bpat.bind((b) => strudel.fastcat(...arrayRange(a, b))));
+ pat = range(pat, friend);
+ break;
+ }
default: {
console.warn(`operator "${op.type_}" not implemented`);
}
diff --git a/packages/mini/test/mini.test.mjs b/packages/mini/test/mini.test.mjs
index 0c7f381e..6d9ac367 100644
--- a/packages/mini/test/mini.test.mjs
+++ b/packages/mini/test/mini.test.mjs
@@ -184,6 +184,12 @@ describe('mini', () => {
it('supports lists', () => {
expect(minV('a:b c:d:[e:f] g')).toEqual([['a', 'b'], ['c', 'd', ['e', 'f']], 'g']);
});
+ it('supports ranges', () => {
+ expect(minV('0 .. 4')).toEqual([0, 1, 2, 3, 4]);
+ });
+ it('supports patterned ranges', () => {
+ expect(minS('[<0 1> .. <2 4>]*2')).toEqual(minS('[0 1 2] [1 2 3 4]'));
+ });
});
describe('getLeafLocation', () => {
diff --git a/packages/react/src/components/CodeMirror6.jsx b/packages/react/src/components/CodeMirror6.jsx
index 0f1b2274..a5af5312 100644
--- a/packages/react/src/components/CodeMirror6.jsx
+++ b/packages/react/src/components/CodeMirror6.jsx
@@ -15,10 +15,11 @@ import {
updateMiniLocations,
} from '@strudel/codemirror';
import './style.css';
+import { sliderPlugin } from '@strudel/codemirror/slider.mjs';
export { flash, highlightMiniLocations, updateMiniLocations };
-const staticExtensions = [javascript(), flashField, highlightExtension];
+const staticExtensions = [javascript(), flashField, highlightExtension, sliderPlugin];
export default function CodeMirror({
value,
diff --git a/packages/react/src/hooks/useWidgets.mjs b/packages/react/src/hooks/useWidgets.mjs
new file mode 100644
index 00000000..e7ca136a
--- /dev/null
+++ b/packages/react/src/hooks/useWidgets.mjs
@@ -0,0 +1,13 @@
+import { useEffect, useState } from 'react';
+import { updateWidgets } from '@strudel/codemirror';
+
+// i know this is ugly.. in the future, repl needs to run without react
+export function useWidgets(view) {
+ const [widgets, setWidgets] = useState([]);
+ useEffect(() => {
+ if (view) {
+ updateWidgets(view, widgets);
+ }
+ }, [view, widgets]);
+ return { widgets, setWidgets };
+}
diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs
index 32ca0bb2..576ec3f1 100644
--- a/packages/superdough/helpers.mjs
+++ b/packages/superdough/helpers.mjs
@@ -112,3 +112,25 @@ export function createFilter(
return filter;
}
+
+// stays 1 until .5, then fades out
+let wetfade = (d) => (d < 0.5 ? 1 : 1 - (d - 0.5) / 0.5);
+
+// mix together dry and wet nodes. 0 = only dry 1 = only wet
+// still not too sure about how this could be used more generally...
+export function drywet(dry, wet, wetAmount = 0) {
+ const ac = getAudioContext();
+ if (!wetAmount) {
+ return dry;
+ }
+ let dry_gain = ac.createGain();
+ let wet_gain = ac.createGain();
+ dry.connect(dry_gain);
+ wet.connect(wet_gain);
+ dry_gain.gain.value = wetfade(wetAmount);
+ wet_gain.gain.value = wetfade(1 - wetAmount);
+ let mix = ac.createGain();
+ dry_gain.connect(mix);
+ wet_gain.connect(mix);
+ return mix;
+}
diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs
new file mode 100644
index 00000000..2c8c1d4a
--- /dev/null
+++ b/packages/superdough/noise.mjs
@@ -0,0 +1,63 @@
+import { drywet } from './helpers.mjs';
+import { getAudioContext } from './superdough.mjs';
+
+let noiseCache = {};
+
+// lazy generates noise buffers and keeps them forever
+function getNoiseBuffer(type) {
+ const ac = getAudioContext();
+ if (noiseCache[type]) {
+ return noiseCache[type];
+ }
+ const bufferSize = 2 * ac.sampleRate;
+ const noiseBuffer = ac.createBuffer(1, bufferSize, ac.sampleRate);
+ const output = noiseBuffer.getChannelData(0);
+ let lastOut = 0;
+ let b0, b1, b2, b3, b4, b5, b6;
+ b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0;
+
+ for (let i = 0; i < bufferSize; i++) {
+ if (type === 'white') {
+ output[i] = Math.random() * 2 - 1;
+ } else if (type === 'brown') {
+ let white = Math.random() * 2 - 1;
+ output[i] = (lastOut + 0.02 * white) / 1.02;
+ lastOut = output[i];
+ } else if (type === 'pink') {
+ let white = Math.random() * 2 - 1;
+ b0 = 0.99886 * b0 + white * 0.0555179;
+ b1 = 0.99332 * b1 + white * 0.0750759;
+ b2 = 0.969 * b2 + white * 0.153852;
+ b3 = 0.8665 * b3 + white * 0.3104856;
+ b4 = 0.55 * b4 + white * 0.5329522;
+ b5 = -0.7616 * b5 - white * 0.016898;
+ output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
+ output[i] *= 0.11;
+ b6 = white * 0.115926;
+ }
+ }
+ noiseCache[type] = noiseBuffer;
+ return noiseBuffer;
+}
+
+// expects one of noises as type
+export function getNoiseOscillator(type = 'white', t) {
+ const ac = getAudioContext();
+ const o = ac.createBufferSource();
+ o.buffer = getNoiseBuffer(type);
+ o.loop = true;
+ o.start(t);
+ return {
+ node: o,
+ stop: (time) => o.stop(time),
+ };
+}
+
+export function getNoiseMix(inputNode, wet, t) {
+ const noiseOscillator = getNoiseOscillator('pink', t);
+ const noiseMix = drywet(inputNode, noiseOscillator.node, wet);
+ return {
+ node: noiseMix,
+ stop: (time) => noiseOscillator?.stop(time),
+ };
+}
diff --git a/packages/superdough/reverbGen.mjs b/packages/superdough/reverbGen.mjs
index d8024ccb..4fd76570 100644
--- a/packages/superdough/reverbGen.mjs
+++ b/packages/superdough/reverbGen.mjs
@@ -48,6 +48,7 @@ reverbGen.generateReverb = function (params, callback) {
/** Creates a canvas element showing a graph of the given data.
+
@param {!Float32Array} data An array of numbers, or a Float32Array.
@param {number} width Width in pixels of the canvas.
@param {number} height Height in pixels of the canvas.
diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs
index d7f0b14b..d74e3182 100644
--- a/packages/superdough/superdough.mjs
+++ b/packages/superdough/superdough.mjs
@@ -114,6 +114,7 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
let reverbs = {};
+
function getReverb(orbit, duration = 2, fade, lp, dim, ir) {
if (!reverbs[orbit]) {
const ac = getAudioContext();
diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs
index 633d0113..24d1d5ef 100644
--- a/packages/superdough/synth.mjs
+++ b/packages/superdough/synth.mjs
@@ -1,6 +1,7 @@
import { midiToFreq, noteToMidi } from './util.mjs';
import { registerSound, getAudioContext } from './superdough.mjs';
import { gainNode, getEnvelope, getExpEnvelope } from './helpers.mjs';
+import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
const mod = (freq, range = 1, type = 'sine') => {
const ctx = getAudioContext();
@@ -20,75 +21,26 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => {
return mod(modfreq, modgain, wave);
};
+const waveforms = ['sine', 'square', 'triangle', 'sawtooth'];
+const noises = ['pink', 'white', 'brown'];
+
export function registerSynthSounds() {
- ['sine', 'square', 'triangle', 'sawtooth'].forEach((wave) => {
+ [...waveforms, ...noises].forEach((s) => {
registerSound(
- wave,
+ s,
(t, value, onended) => {
// destructure adsr here, because the default should be different for synths and samples
- let {
- attack = 0.001,
- decay = 0.05,
- sustain = 0.6,
- release = 0.01,
- fmh: fmHarmonicity = 1,
- fmi: fmModulationIndex,
- fmenv: fmEnvelopeType = 'lin',
- fmattack: fmAttack,
- fmdecay: fmDecay,
- fmsustain: fmSustain,
- fmrelease: fmRelease,
- fmvelocity: fmVelocity,
- fmwave: fmWaveform = 'sine',
- vib = 0,
- vibmod = 0.5,
- } = value;
- let { n, note, freq } = value;
- // with synths, n and note are the same thing
- note = note || 36;
- if (typeof note === 'string') {
- note = noteToMidi(note); // e.g. c3 => 48
- }
- // get frequency
- if (!freq && typeof note === 'number') {
- freq = midiToFreq(note); // + 48);
- }
- // maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default)
- // make oscillator
- const { node: o, stop } = getOscillator({
- t,
- s: wave,
- freq,
- vib,
- vibmod,
- partials: n,
- });
+ let { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = value;
- // FM + FM envelope
- let stopFm, fmEnvelope;
- if (fmModulationIndex) {
- const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform);
- if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
- // no envelope by default
- modulator.connect(o.frequency);
- } else {
- fmAttack = fmAttack ?? 0.001;
- fmDecay = fmDecay ?? 0.001;
- fmSustain = fmSustain ?? 1;
- fmRelease = fmRelease ?? 0.001;
- fmVelocity = fmVelocity ?? 1;
- fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
- if (fmEnvelopeType === 'exp') {
- fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
- fmEnvelope.node.maxValue = fmModulationIndex * 2;
- fmEnvelope.node.minValue = 0.00001;
- }
- modulator.connect(fmEnvelope.node);
- fmEnvelope.node.connect(o.frequency);
- }
- stopFm = stop;
+ let sound;
+ if (waveforms.includes(s)) {
+ sound = getOscillator(s, t, value);
+ } else {
+ sound = getNoiseOscillator(s, t);
}
+ let { node: o, stop, triggerRelease } = sound;
+
// turn down
const g = gainNode(0.3);
@@ -104,10 +56,9 @@ export function registerSynthSounds() {
node: o.connect(g).connect(envelope),
stop: (releaseTime) => {
releaseEnvelope(releaseTime);
- fmEnvelope?.stop(releaseTime);
+ triggerRelease?.(releaseTime);
let end = releaseTime + release;
stop(end);
- stopFm?.(end);
},
};
},
@@ -146,36 +97,108 @@ export function waveformN(partials, type) {
return osc;
}
-export function getOscillator({ s, freq, t, vib, vibmod, partials }) {
- // Make oscillator with partial count
+// expects one of waveforms as s
+export function getOscillator(
+ s,
+ t,
+ {
+ n: partials,
+ note,
+ freq,
+ vib = 0,
+ vibmod = 0.5,
+ noise = 0,
+ // fm
+ fmh: fmHarmonicity = 1,
+ fmi: fmModulationIndex,
+ fmenv: fmEnvelopeType = 'lin',
+ fmattack: fmAttack,
+ fmdecay: fmDecay,
+ fmsustain: fmSustain,
+ fmrelease: fmRelease,
+ fmvelocity: fmVelocity,
+ fmwave: fmWaveform = 'sine',
+ },
+) {
+ let ac = getAudioContext();
let o;
+ // If no partials are given, use stock waveforms
if (!partials || s === 'sine') {
o = getAudioContext().createOscillator();
o.type = s || 'triangle';
- } else {
+ }
+ // generate custom waveform if partials are given
+ else {
o = waveformN(partials, s);
}
+
+ // get frequency from note...
+ note = note || 36;
+ if (typeof note === 'string') {
+ note = noteToMidi(note); // e.g. c3 => 48
+ }
+ // get frequency
+ if (!freq && typeof note === 'number') {
+ freq = midiToFreq(note); // + 48);
+ }
+
+ // set frequency
o.frequency.value = Number(freq);
o.start(t);
+ // FM
+ let stopFm, fmEnvelope;
+ if (fmModulationIndex) {
+ const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform);
+ if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
+ // no envelope by default
+ modulator.connect(o.frequency);
+ } else {
+ fmAttack = fmAttack ?? 0.001;
+ fmDecay = fmDecay ?? 0.001;
+ fmSustain = fmSustain ?? 1;
+ fmRelease = fmRelease ?? 0.001;
+ fmVelocity = fmVelocity ?? 1;
+ fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
+ if (fmEnvelopeType === 'exp') {
+ fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t);
+ fmEnvelope.node.maxValue = fmModulationIndex * 2;
+ fmEnvelope.node.minValue = 0.00001;
+ }
+ modulator.connect(fmEnvelope.node);
+ fmEnvelope.node.connect(o.frequency);
+ }
+ stopFm = stop;
+ }
+
// Additional oscillator for vibrato effect
- let vibrato_oscillator;
+ let vibratoOscillator;
if (vib > 0) {
- vibrato_oscillator = getAudioContext().createOscillator();
- vibrato_oscillator.frequency.value = vib;
+ vibratoOscillator = getAudioContext().createOscillator();
+ vibratoOscillator.frequency.value = vib;
const gain = getAudioContext().createGain();
// Vibmod is the amount of vibrato, in semitones
gain.gain.value = vibmod * 100;
- vibrato_oscillator.connect(gain);
+ vibratoOscillator.connect(gain);
gain.connect(o.detune);
- vibrato_oscillator.start(t);
+ vibratoOscillator.start(t);
+ }
+
+ let noiseMix;
+ if (noise) {
+ noiseMix = getNoiseMix(o, noise, t);
}
return {
- node: o,
+ node: noiseMix?.node || o,
stop: (time) => {
- vibrato_oscillator?.stop(time);
+ vibratoOscillator?.stop(time);
+ noiseMix?.stop(time);
+ stopFm?.(time);
o.stop(time);
},
+ triggerRelease: (time) => {
+ fmEnvelope?.stop(time);
+ },
};
}
diff --git a/packages/superdough/zzfx.mjs b/packages/superdough/zzfx.mjs
index da505d74..a6af8260 100644
--- a/packages/superdough/zzfx.mjs
+++ b/packages/superdough/zzfx.mjs
@@ -20,7 +20,7 @@ export const getZZFX = (value, t) => {
pitchJump = 0,
pitchJumpTime = 0,
lfo = 0,
- noise = 0,
+ znoise = 0,
zmod = 0,
zcrush = 0,
zdelay = 0,
@@ -54,7 +54,7 @@ export const getZZFX = (value, t) => {
pitchJump,
pitchJumpTime,
lfo,
- noise,
+ znoise,
zmod,
zcrush,
zdelay,
diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs
index 78aae9f7..256be1d2 100644
--- a/packages/transpiler/transpiler.mjs
+++ b/packages/transpiler/transpiler.mjs
@@ -5,7 +5,7 @@ import { isNoteWithOctave } from '@strudel.cycles/core';
import { getLeafLocations } from '@strudel.cycles/mini';
export function transpiler(input, options = {}) {
- const { wrapAsync = false, addReturn = true, emitMiniLocations = true } = options;
+ const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options;
let ast = parse(input, {
ecmaVersion: 2022,
@@ -16,9 +16,9 @@ export function transpiler(input, options = {}) {
let miniLocations = [];
const collectMiniLocations = (value, node) => {
const leafLocs = getLeafLocations(`"${value}"`, node.start); // stimmt!
- //const withOffset = leafLocs.map((offsets) => offsets.map((o) => o + node.start));
miniLocations = miniLocations.concat(leafLocs);
};
+ let widgets = [];
walk(ast, {
enter(node, parent /* , prop, index */) {
@@ -35,6 +35,18 @@ export function transpiler(input, options = {}) {
emitMiniLocations && collectMiniLocations(value, node);
return this.replace(miniWithLocation(value, node));
}
+ if (isWidgetFunction(node)) {
+ emitWidgets &&
+ widgets.push({
+ from: node.arguments[0].start,
+ to: node.arguments[0].end,
+ value: node.arguments[0].raw, // don't use value!
+ min: node.arguments[1]?.value ?? 0,
+ max: node.arguments[2]?.value ?? 1,
+ step: node.arguments[3]?.value,
+ });
+ return this.replace(widgetWithLocation(node));
+ }
// TODO: remove pseudo note variables?
if (node.type === 'Identifier' && isNoteWithOctave(node.name)) {
this.skip();
@@ -64,15 +76,14 @@ export function transpiler(input, options = {}) {
if (!emitMiniLocations) {
return { output };
}
- return { output, miniLocations };
+ return { output, miniLocations, widgets };
}
function isStringWithDoubleQuotes(node, locations, code) {
- const { raw, type } = node;
- if (type !== 'Literal') {
+ if (node.type !== 'Literal') {
return false;
}
- return raw[0] === '"';
+ return node.raw[0] === '"';
}
function isBackTickString(node, parent) {
@@ -94,3 +105,22 @@ function miniWithLocation(value, node) {
optional: false,
};
}
+
+// these functions are connected to @strudel/codemirror -> slider.mjs
+// maybe someday there will be pluggable transpiler functions, then move this there
+function isWidgetFunction(node) {
+ return node.type === 'CallExpression' && node.callee.name === 'slider';
+}
+
+function widgetWithLocation(node) {
+ const id = 'slider_' + node.arguments[0].start; // use loc of first arg for id
+ // add loc as identifier to first argument
+ // the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?)
+ node.arguments.unshift({
+ type: 'Literal',
+ value: id,
+ raw: id,
+ });
+ node.callee.name = 'sliderWithID';
+ return node;
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8b48a93f..9f222a20 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -588,6 +588,9 @@ importers:
'@strudel.cycles/xen':
specifier: workspace:*
version: link:../packages/xen
+ '@strudel/codemirror':
+ specifier: workspace:*
+ version: link:../packages/codemirror
'@strudel/desktopbridge':
specifier: workspace:*
version: link:../packages/desktopbridge
@@ -1426,6 +1429,7 @@ packages:
/@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.21.5):
resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1441,6 +1445,7 @@ packages:
/@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.21.5):
resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1454,6 +1459,7 @@ packages:
/@babel/plugin-proposal-class-static-block@7.20.7(@babel/core@7.21.5):
resolution: {integrity: sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead.
peerDependencies:
'@babel/core': ^7.12.0
dependencies:
@@ -1468,6 +1474,7 @@ packages:
/@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.21.5):
resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1479,6 +1486,7 @@ packages:
/@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.21.5):
resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1490,6 +1498,7 @@ packages:
/@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.21.5):
resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1501,6 +1510,7 @@ packages:
/@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.21.5):
resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1512,6 +1522,7 @@ packages:
/@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.21.5):
resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1523,6 +1534,7 @@ packages:
/@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.21.5):
resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1534,6 +1546,7 @@ packages:
/@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.21.5):
resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1548,6 +1561,7 @@ packages:
/@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.21.5):
resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1559,6 +1573,7 @@ packages:
/@babel/plugin-proposal-optional-chaining@7.20.7(@babel/core@7.21.5):
resolution: {integrity: sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1571,6 +1586,7 @@ packages:
/@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.21.5):
resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1584,6 +1600,7 @@ packages:
/@babel/plugin-proposal-private-property-in-object@7.20.5(@babel/core@7.21.5):
resolution: {integrity: sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==}
engines: {node: '>=6.9.0'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
@@ -1599,6 +1616,7 @@ packages:
/@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.21.5):
resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==}
engines: {node: '>=4'}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap
index e026f9c4..f2bc7039 100644
--- a/test/__snapshots__/examples.test.mjs.snap
+++ b/test/__snapshots__/examples.test.mjs.snap
@@ -2959,6 +2959,15 @@ exports[`runs examples > example "never" example index 0 1`] = `
]
`;
+exports[`runs examples > example "noise" example index 0 1`] = `
+[
+ "[ (0/1 → 1/1) ⇝ 2/1 | s:white ]",
+ "[ 0/1 ⇜ (1/1 → 2/1) | s:white ]",
+ "[ (2/1 → 3/1) ⇝ 4/1 | s:pink ]",
+ "[ 2/1 ⇜ (3/1 → 4/1) | s:pink ]",
+]
+`;
+
exports[`runs examples > example "note" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c ]",
@@ -3654,16 +3663,107 @@ exports[`runs examples > example "room" example index 1 1`] = `
]
`;
+exports[`runs examples > example "roomdim" example index 0 1`] = `
+[
+ "[ 0/1 → 1/2 | s:bd room:0.5 roomlp:10000 roomdim:8000 ]",
+ "[ 1/2 → 1/1 | s:sd room:0.5 roomlp:10000 roomdim:8000 ]",
+ "[ 1/1 → 3/2 | s:bd room:0.5 roomlp:10000 roomdim:8000 ]",
+ "[ 3/2 → 2/1 | s:sd room:0.5 roomlp:10000 roomdim:8000 ]",
+ "[ 2/1 → 5/2 | s:bd room:0.5 roomlp:10000 roomdim:8000 ]",
+ "[ 5/2 → 3/1 | s:sd room:0.5 roomlp:10000 roomdim:8000 ]",
+ "[ 3/1 → 7/2 | s:bd room:0.5 roomlp:10000 roomdim:8000 ]",
+ "[ 7/2 → 4/1 | s:sd room:0.5 roomlp:10000 roomdim:8000 ]",
+]
+`;
+
+exports[`runs examples > example "roomdim" example index 1 1`] = `
+[
+ "[ 0/1 → 1/2 | s:bd room:0.5 roomlp:5000 roomdim:400 ]",
+ "[ 1/2 → 1/1 | s:sd room:0.5 roomlp:5000 roomdim:400 ]",
+ "[ 1/1 → 3/2 | s:bd room:0.5 roomlp:5000 roomdim:400 ]",
+ "[ 3/2 → 2/1 | s:sd room:0.5 roomlp:5000 roomdim:400 ]",
+ "[ 2/1 → 5/2 | s:bd room:0.5 roomlp:5000 roomdim:400 ]",
+ "[ 5/2 → 3/1 | s:sd room:0.5 roomlp:5000 roomdim:400 ]",
+ "[ 3/1 → 7/2 | s:bd room:0.5 roomlp:5000 roomdim:400 ]",
+ "[ 7/2 → 4/1 | s:sd room:0.5 roomlp:5000 roomdim:400 ]",
+]
+`;
+
+exports[`runs examples > example "roomfade" example index 0 1`] = `
+[
+ "[ 0/1 → 1/2 | s:bd room:0.5 roomlp:10000 roomfade:0.5 ]",
+ "[ 1/2 → 1/1 | s:sd room:0.5 roomlp:10000 roomfade:0.5 ]",
+ "[ 1/1 → 3/2 | s:bd room:0.5 roomlp:10000 roomfade:0.5 ]",
+ "[ 3/2 → 2/1 | s:sd room:0.5 roomlp:10000 roomfade:0.5 ]",
+ "[ 2/1 → 5/2 | s:bd room:0.5 roomlp:10000 roomfade:0.5 ]",
+ "[ 5/2 → 3/1 | s:sd room:0.5 roomlp:10000 roomfade:0.5 ]",
+ "[ 3/1 → 7/2 | s:bd room:0.5 roomlp:10000 roomfade:0.5 ]",
+ "[ 7/2 → 4/1 | s:sd room:0.5 roomlp:10000 roomfade:0.5 ]",
+]
+`;
+
+exports[`runs examples > example "roomfade" example index 1 1`] = `
+[
+ "[ 0/1 → 1/2 | s:bd room:0.5 roomlp:5000 roomfade:4 ]",
+ "[ 1/2 → 1/1 | s:sd room:0.5 roomlp:5000 roomfade:4 ]",
+ "[ 1/1 → 3/2 | s:bd room:0.5 roomlp:5000 roomfade:4 ]",
+ "[ 3/2 → 2/1 | s:sd room:0.5 roomlp:5000 roomfade:4 ]",
+ "[ 2/1 → 5/2 | s:bd room:0.5 roomlp:5000 roomfade:4 ]",
+ "[ 5/2 → 3/1 | s:sd room:0.5 roomlp:5000 roomfade:4 ]",
+ "[ 3/1 → 7/2 | s:bd room:0.5 roomlp:5000 roomfade:4 ]",
+ "[ 7/2 → 4/1 | s:sd room:0.5 roomlp:5000 roomfade:4 ]",
+]
+`;
+
+exports[`runs examples > example "roomlp" example index 0 1`] = `
+[
+ "[ 0/1 → 1/2 | s:bd room:0.5 roomlp:10000 ]",
+ "[ 1/2 → 1/1 | s:sd room:0.5 roomlp:10000 ]",
+ "[ 1/1 → 3/2 | s:bd room:0.5 roomlp:10000 ]",
+ "[ 3/2 → 2/1 | s:sd room:0.5 roomlp:10000 ]",
+ "[ 2/1 → 5/2 | s:bd room:0.5 roomlp:10000 ]",
+ "[ 5/2 → 3/1 | s:sd room:0.5 roomlp:10000 ]",
+ "[ 3/1 → 7/2 | s:bd room:0.5 roomlp:10000 ]",
+ "[ 7/2 → 4/1 | s:sd room:0.5 roomlp:10000 ]",
+]
+`;
+
+exports[`runs examples > example "roomlp" example index 1 1`] = `
+[
+ "[ 0/1 → 1/2 | s:bd room:0.5 roomlp:5000 ]",
+ "[ 1/2 → 1/1 | s:sd room:0.5 roomlp:5000 ]",
+ "[ 1/1 → 3/2 | s:bd room:0.5 roomlp:5000 ]",
+ "[ 3/2 → 2/1 | s:sd room:0.5 roomlp:5000 ]",
+ "[ 2/1 → 5/2 | s:bd room:0.5 roomlp:5000 ]",
+ "[ 5/2 → 3/1 | s:sd room:0.5 roomlp:5000 ]",
+ "[ 3/1 → 7/2 | s:bd room:0.5 roomlp:5000 ]",
+ "[ 7/2 → 4/1 | s:sd room:0.5 roomlp:5000 ]",
+]
+`;
+
exports[`runs examples > example "roomsize" example index 0 1`] = `
[
- "[ 0/1 → 1/2 | s:bd room:0.8 size:0 ]",
- "[ 1/2 → 1/1 | s:sd room:0.8 size:0 ]",
- "[ 1/1 → 3/2 | s:bd room:0.8 size:1 ]",
- "[ 3/2 → 2/1 | s:sd room:0.8 size:1 ]",
- "[ 2/1 → 5/2 | s:bd room:0.8 size:2 ]",
- "[ 5/2 → 3/1 | s:sd room:0.8 size:2 ]",
- "[ 3/1 → 7/2 | s:bd room:0.8 size:4 ]",
- "[ 7/2 → 4/1 | s:sd room:0.8 size:4 ]",
+ "[ 0/1 → 1/2 | s:bd room:0.8 roomsize:1 ]",
+ "[ 1/2 → 1/1 | s:sd room:0.8 roomsize:1 ]",
+ "[ 1/1 → 3/2 | s:bd room:0.8 roomsize:1 ]",
+ "[ 3/2 → 2/1 | s:sd room:0.8 roomsize:1 ]",
+ "[ 2/1 → 5/2 | s:bd room:0.8 roomsize:1 ]",
+ "[ 5/2 → 3/1 | s:sd room:0.8 roomsize:1 ]",
+ "[ 3/1 → 7/2 | s:bd room:0.8 roomsize:1 ]",
+ "[ 7/2 → 4/1 | s:sd room:0.8 roomsize:1 ]",
+]
+`;
+
+exports[`runs examples > example "roomsize" example index 1 1`] = `
+[
+ "[ 0/1 → 1/2 | s:bd room:0.8 roomsize:4 ]",
+ "[ 1/2 → 1/1 | s:sd room:0.8 roomsize:4 ]",
+ "[ 1/1 → 3/2 | s:bd room:0.8 roomsize:4 ]",
+ "[ 3/2 → 2/1 | s:sd room:0.8 roomsize:4 ]",
+ "[ 2/1 → 5/2 | s:bd room:0.8 roomsize:4 ]",
+ "[ 5/2 → 3/1 | s:sd room:0.8 roomsize:4 ]",
+ "[ 3/1 → 7/2 | s:bd room:0.8 roomsize:4 ]",
+ "[ 7/2 → 4/1 | s:sd room:0.8 roomsize:4 ]",
]
`;
diff --git a/website/package.json b/website/package.json
index 6ac799d3..c8fec20c 100644
--- a/website/package.json
+++ b/website/package.json
@@ -28,6 +28,7 @@
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/osc": "workspace:*",
"@strudel.cycles/react": "workspace:*",
+ "@strudel/codemirror": "workspace:*",
"@strudel.cycles/serial": "workspace:*",
"@strudel.cycles/soundfonts": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx
index 35052788..e2a0de8b 100644
--- a/website/src/pages/learn/effects.mdx
+++ b/website/src/pages/learn/effects.mdx
@@ -219,6 +219,7 @@ global effects use the same chain for all events of the same orbit:
+
### iresponse
diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx
index 9f21204f..432276ef 100644
--- a/website/src/pages/learn/synths.mdx
+++ b/website/src/pages/learn/synths.mdx
@@ -23,6 +23,25 @@ The basic waveforms are `sine`, `sawtooth`, `square` and `triangle`, which can b
If you don't set a `sound` but a `note` the default value for `sound` is `triangle`!
+## Noise
+
+You can also use noise as a source by setting the waveform to: `white`, `pink` or `brown`. These are different
+flavours of noise, here written from hard to soft.
+
+/2").scope()`} />
+
+Here's a more musical example of how to use noise for hihats:
+
+*8")
+.decay(.04).sustain(0).scope()`}
+/>
+
+Some amount of pink noise can also be added to any oscillator by using the `noise` paremeter:
+
+").scope()`} />
+
### Additive Synthesis
To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform:
diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx
index 4002eba8..8fe43c6d 100644
--- a/website/src/repl/Repl.jsx
+++ b/website/src/repl/Repl.jsx
@@ -22,6 +22,7 @@ import Loader from './Loader';
import { settingPatterns } from '../settings.mjs';
import { code2hash, hash2code } from './helpers.mjs';
import { isTauri } from '../tauri.mjs';
+import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs';
const { latestCode } = settingsMap.get();
@@ -39,6 +40,7 @@ let modules = [
import('@strudel.cycles/mini'),
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
+ import('@strudel/codemirror'),
import('@strudel.cycles/serial'),
import('@strudel.cycles/soundfonts'),
@@ -128,7 +130,7 @@ export function Repl({ embedded = false }) {
} = useSettings();
const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]);
-
+ const { setWidgets } = useWidgets(view);
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
useStrudel({
initialCode: '// LOADING...',
@@ -142,6 +144,7 @@ export function Repl({ embedded = false }) {
},
afterEval: ({ code, meta }) => {
setMiniLocations(meta.miniLocations);
+ setWidgets(meta.widgets);
setPending(false);
setLatestCode(code);
window.location.hash = '#' + code2hash(code);
@@ -219,7 +222,7 @@ export function Repl({ embedded = false }) {
const handleChangeCode = useCallback(
(c) => {
setCode(c);
- started && logger('[edit] code changed. hit ctrl+enter to update');
+ //started && logger('[edit] code changed. hit ctrl+enter to update');
},
[started],
);