diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 1ca49bb6a..a03658a8b 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -380,6 +380,62 @@ const generic_params = [ * */ ['coarse'], + + ['phaserrate', 'phasr'], // superdirt only + + /** + * Phaser audio effect that approximates popular guitar pedals. + * + * @name phaser + * @synonyms ph + * @param {number | Pattern} speed speed of modulation + * @example + * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) + * .phaser("<1 2 4 8>") + * + */ + [['phaser', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph'], + + /** + * The frequency sweep range of the lfo for the phaser effect. Defaults to 2000 + * + * @name phasersweep + * @synonyms phs + * @param {number | Pattern} phasersweep most useful values are between 0 and 4000 + * @example + * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) + * .phaser(2).phasersweep("<800 2000 4000>") + * + */ + ['phasersweep', 'phs'], + + /** + * The center frequency of the phaser in HZ. Defaults to 1000 + * + * @name phasercenter + * @synonyms phc + * @param {number | Pattern} centerfrequency in HZ + * @example + * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) + * .phaser(2).phasercenter("<800 2000 4000>") + * + */ + + ['phasercenter', 'phc'], + + /** + * The amount the signal is affected by the phaser effect. Defaults to 0.75 + * + * @name phaserdepth + * @synonyms phd + * @param {number | Pattern} depth number between 0 and 1 + * @example + * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) + * .phaser(2).phaserdepth("<0 .5 .75 1>") + * + */ + ['phaserdepth', 'phd', 'phasdp'], // also a superdirt control + /** * choose the channel the pattern is sent to in superdirt * @@ -867,7 +923,12 @@ const generic_params = [ * */ ['lsize'], - // label for pianoroll + /** + * Sets the displayed text for an event on the pianoroll + * + * @name label + * @param {string} label text to display + */ ['activeLabel'], [['label', 'activeLabel']], // ['lfo'], @@ -1031,7 +1092,8 @@ const generic_params = [ */ ['roomfade', 'rfade'], /** - * Sets the sample to use as an impulse response for the reverb. * * @name iresponse + * Sets the sample to use as an impulse response for the reverb. + * @name iresponse * @param {string | Pattern} sample to use as an impulse response * @synonyms ir * @example @@ -1169,9 +1231,6 @@ const generic_params = [ */ ['tremolodepth', 'tremdp'], ['tremolorate', 'tremr'], - // TODO: doesn't seem to do anything - ['phaserdepth', 'phasdp'], - ['phaserrate', 'phasr'], ['fshift'], ['fshiftnote'], diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index dcf5da1b5..f30d21c81 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2191,6 +2191,9 @@ export const duration = register('duration', function (value, pat) { /** * Sets the color of the hap in visualizations like pianoroll or highlighting. + * @name color + * @synonyms colour + * @param {string} color Hexadecimal or CSS color name */ // TODO: move this to controls https://github.com/tidalcycles/strudel/issues/288 export const { color, colour } = register(['color', 'colour'], function (color, pat) { @@ -2392,3 +2395,29 @@ export const ref = (accessor) => pure(1) .withValue(() => reify(accessor())) .innerJoin(); + +let fadeGain = (p) => (p < 0.5 ? 1 : 1 - (p - 0.5) / 0.5); + +/** + * Cross-fades between left and right from 0 to 1: + * - 0 = (full left, no right) + * - .5 = (both equal) + * - 1 = (no left, full right) + * + * @name xfade + * @example + * xfade(s("bd*2"), "<0 .25 .5 .75 1>", s("hh*8")) + */ +export let xfade = (a, pos, b) => { + pos = reify(pos); + a = reify(a); + b = reify(b); + let gaina = pos.fmap((v) => ({ gain: fadeGain(v) })); + let gainb = pos.fmap((v) => ({ gain: fadeGain(1 - v) })); + return stack(a.mul(gaina), b.mul(gainb)); +}; + +// the prototype version is actually flipped so left/right makes sense +Pattern.prototype.xfade = function (pos, b) { + return xfade(this, pos, b); +}; diff --git a/packages/core/pianoroll.mjs b/packages/core/pianoroll.mjs index 96126b175..01561cb24 100644 --- a/packages/core/pianoroll.mjs +++ b/packages/core/pianoroll.mjs @@ -56,6 +56,40 @@ Pattern.prototype.pianoroll = function (options = {}) { // this function allows drawing a pianoroll without ties to Pattern.prototype // it will probably replace the above in the future + +/** + * Displays a midi-style piano roll + * + * @name pianoroll + * @param {Object} options Object containing all the optional following parameters as key value pairs: + * @param {integer} cycles number of cycles to be displayed at the same time - defaults to 4 + * @param {number} playhead location of the active notes on the time axis - 0 to 1, defaults to 0.5 + * @param {boolean} vertical displays the roll vertically - 0 by default + * @param {boolean} labels displays labels on individual notes (see the label function) - 0 by default + * @param {boolean} flipTime reverse the direction of the roll - 0 by default + * @param {boolean} flipValues reverse the relative location of notes on the value axis - 0 by default + * @param {number} overscan lookup X cycles outside of the cycles window to display notes in advance - 1 by default + * @param {boolean} hideNegative hide notes with negative time (before starting playing the pattern) - 0 by default + * @param {boolean} smear notes leave a solid trace - 0 by default + * @param {boolean} fold notes takes the full value axis width - 0 by default + * @param {string} active hexadecimal or CSS color of the active notes - defaults to #FFCA28 + * @param {string} inactive hexadecimal or CSS color of the inactive notes - defaults to #7491D2 + * @param {string} background hexadecimal or CSS color of the background - defaults to transparent + * @param {string} playheadColor hexadecimal or CSS color of the line representing the play head - defaults to white + * @param {boolean} fill notes are filled with color (otherwise only the label is displayed) - 0 by default + * @param {boolean} fillActive active notes are filled with color - 0 by default + * @param {boolean} stroke notes are shown with colored borders - 0 by default + * @param {boolean} strokeActive active notes are shown with colored borders - 0 by default + * @param {boolean} hideInactive only active notes are shown - 0 by default + * @param {boolean} colorizeInactive use note color for inactive notes - 1 by default + * @param {string} fontFamily define the font used by notes labels - defaults to 'monospace' + * @param {integer} minMidi minimum note value to display on the value axis - defaults to 10 + * @param {integer} maxMidi maximum note value to display on the value axis - defaults to 90 + * @param {boolean} autorange automatically calculate the minMidi and maxMidi parameters - 0 by default + * + * @example + * note("C2 A2 G2").euclid(5,8).s('piano').clip(1).color('salmon').pianoroll({vertical:1, labels:1}) + */ export function pianoroll({ time, haps, diff --git a/packages/csound/index.mjs b/packages/csound/index.mjs index 31ffa83a5..3063febc1 100644 --- a/packages/csound/index.mjs +++ b/packages/csound/index.mjs @@ -137,7 +137,7 @@ export async function loadOrc(url) { export const csoundm = register('csoundm', (instrument, pat) => { let p1 = instrument; if (typeof instrument === 'string') { - p1 = `"{instrument}"`; + p1 = `"${instrument}"`; } init(); // not async to support csound inside other patterns + to be able to call pattern methods after it return pat.onTrigger((tidal_time, hap) => { diff --git a/packages/react/src/components/Autocomplete.jsx b/packages/react/src/components/Autocomplete.jsx index 7ec81d2d1..677baa6e7 100644 --- a/packages/react/src/components/Autocomplete.jsx +++ b/packages/react/src/components/Autocomplete.jsx @@ -26,7 +26,6 @@ export function Autocomplete({ doc }) {
 {
-                console.log('ola!');
                 navigator.clipboard.writeText(example);
                 e.stopPropagation();
               }}
diff --git a/packages/react/src/components/CodeMirror6.jsx b/packages/react/src/components/CodeMirror6.jsx
index 520d8b76a..ab59ab9b3 100644
--- a/packages/react/src/components/CodeMirror6.jsx
+++ b/packages/react/src/components/CodeMirror6.jsx
@@ -9,6 +9,7 @@ import _CodeMirror from '@uiw/react-codemirror';
 import React, { useCallback, useMemo } from 'react';
 import strudelTheme from '../themes/strudel-theme';
 import { strudelAutocomplete } from './Autocomplete';
+import { strudelTooltip } from './Tooltip';
 import {
   highlightExtension,
   flashField,
@@ -37,6 +38,7 @@ export default function CodeMirror({
   keybindings,
   isLineNumbersDisplayed,
   isAutoCompletionEnabled,
+  isTooltipEnabled,
   isLineWrappingEnabled,
   fontSize = 18,
   fontFamily = 'monospace',
@@ -97,6 +99,14 @@ export default function CodeMirror({
     } else {
       _extensions.push(autocompletion({ override: [] }));
     }
+    if (isTooltipEnabled) {
+      _extensions.push(strudelTooltip);
+    }
+    //_extensions.push([keymap.of({})]);
+
+    if (isLineWrappingEnabled) {
+      _extensions.push(EditorView.lineWrapping);
+    }
 
     _extensions.push(
       keymap.of([
@@ -130,12 +140,17 @@ export default function CodeMirror({
       ]),
     );
 
-    if (isLineWrappingEnabled) {
-      _extensions.push(EditorView.lineWrapping);
-    }
-
     return _extensions;
-  }, [keybindings, isAutoCompletionEnabled, isLineWrappingEnabled, onEvaluate, onReEvaluate, onStop, onPanic]);
+  }, [
+    keybindings,
+    isAutoCompletionEnabled,
+    isTooltipEnabled,
+    isLineWrappingEnabled,
+    onEvaluate,
+    onReEvaluate,
+    onStop,
+    onPanic,
+  ]);
 
   const basicSetup = useMemo(() => ({ lineNumbers: isLineNumbersDisplayed }), [isLineNumbersDisplayed]);
 
diff --git a/packages/react/src/components/Tooltip.jsx b/packages/react/src/components/Tooltip.jsx
new file mode 100644
index 000000000..a443c1231
--- /dev/null
+++ b/packages/react/src/components/Tooltip.jsx
@@ -0,0 +1,69 @@
+import { createRoot } from 'react-dom/client';
+import { hoverTooltip } from '@codemirror/view';
+import jsdoc from '../../../../doc.json';
+import { Autocomplete } from './Autocomplete';
+
+const getDocLabel = (doc) => doc.name || doc.longname;
+
+let ctrlDown = false;
+
+// Record Control key event to trigger or block the tooltip depending on the state
+window.addEventListener(
+  'keyup',
+  function (e) {
+    if (e.key == 'Control') {
+      ctrlDown = false;
+    }
+  },
+  true,
+);
+
+window.addEventListener(
+  'keydown',
+  function (e) {
+    if (e.key == 'Control') {
+      ctrlDown = true;
+    }
+  },
+  true,
+);
+
+export const strudelTooltip = hoverTooltip(
+  (view, pos, side) => {
+    // Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips
+    let { from, to, text } = view.state.doc.lineAt(pos);
+    let start = pos,
+      end = pos;
+    while (start > from && /\w/.test(text[start - from - 1])) {
+      start--;
+    }
+    while (end < to && /\w/.test(text[end - from])) {
+      end++;
+    }
+    if ((start == pos && side < 0) || (end == pos && side > 0)) {
+      return null;
+    }
+    let word = text.slice(start - from, end - from);
+    // Get entry from Strudel documentation
+    let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0];
+    if (!entry) {
+      return null;
+    }
+    if (!ctrlDown) {
+      return null;
+    }
+    return {
+      pos: start,
+      end,
+      above: false,
+      arrow: true,
+      create(view) {
+        let dom = document.createElement('div');
+        dom.className = 'strudel-tooltip';
+        createRoot(dom).render();
+        return { dom };
+      },
+    };
+  },
+  { hoverTime: 10 },
+);
diff --git a/packages/react/src/components/style.css b/packages/react/src/components/style.css
index d61c66190..6336bba32 100644
--- a/packages/react/src/components/style.css
+++ b/packages/react/src/components/style.css
@@ -28,3 +28,7 @@
 footer {
   z-index: 0 !important;
 }
+
+.strudel-tooltip {
+  padding: 5px;
+}
diff --git a/packages/serial/serial.mjs b/packages/serial/serial.mjs
index c4e52d4d2..652b60546 100644
--- a/packages/serial/serial.mjs
+++ b/packages/serial/serial.mjs
@@ -6,23 +6,23 @@ This program is free software: you can redistribute it and/or modify it under th
 
 import { Pattern, isPattern } from '@strudel.cycles/core';
 
-var writeMessage;
+var writeMessagers = {};
 var choosing = false;
 
-export async function getWriter(br = 38400) {
+export async function getWriter(name, br) {
   if (choosing) {
     return;
   }
   choosing = true;
-  if (writeMessage) {
-    return writeMessage;
+  if (name in writeMessagers) {
+    return writeMessagers[name];
   }
   if ('serial' in navigator) {
     const port = await navigator.serial.requestPort();
     await port.open({ baudRate: br });
     const encoder = new TextEncoder();
     const writer = port.writable.getWriter();
-    writeMessage = function (message, chk) {
+    writeMessagers[name] = function (message, chk) {
       const encoded = encoder.encode(message);
       if (!chk) {
         writer.write(encoded);
@@ -63,10 +63,10 @@ function crc16(data) {
   return crc & 0xffff;
 }
 
-Pattern.prototype.serial = function (br = 38400, sendcrc = false, singlecharids = false) {
+Pattern.prototype.serial = function (br = 115200, sendcrc = false, singlecharids = false, name = 'default') {
   return this.withHap((hap) => {
-    if (!writeMessage) {
-      getWriter(br);
+    if (!(name in writeMessagers)) {
+      getWriter(name, br);
     }
     const onTrigger = (time, hap, currentTime) => {
       var message = '';
@@ -108,7 +108,7 @@ Pattern.prototype.serial = function (br = 38400, sendcrc = false, singlecharids
       const offset = (time - currentTime + latency) * 1000;
 
       window.setTimeout(function () {
-        writeMessage(message, chk);
+        writeMessagers[name](message, chk);
       }, offset);
     };
     return hap.setContext({ ...hap.context, onTrigger, dominantTrigger: true });
diff --git a/packages/superdough/README.md b/packages/superdough/README.md
index c5950dbfa..f32aa32d4 100644
--- a/packages/superdough/README.md
+++ b/packages/superdough/README.md
@@ -67,6 +67,10 @@ superdough({ s: 'bd', delay: 0.5 }, 0, 1);
   - `crush`: amplitude bit crusher using given number of bits
   - `shape`: distortion effect from 0 (none) to 1 (full). might get loud!
   - `pan`: stereo panning from 0 (left) to 1 (right)
+  - `phaser`: sets the speed of the modulation
+  - `phaserdepth`: the amount the signal is affected by the phaser effect.
+  - `phasersweep`: the frequency sweep range of the lfo for the phaser effect.
+  - `phasercenter`: the amount the signal is affected by the phaser effect.
   - `vowel`: vowel filter. possible values: "a", "e", "i", "o", "u"
   - `delay`: delay mix
   - `delayfeedback`: delay feedback
diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs
index d0733b148..b8f10d5da 100644
--- a/packages/superdough/sampler.mjs
+++ b/packages/superdough/sampler.mjs
@@ -57,7 +57,6 @@ export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resol
   const bufferSource = ac.createBufferSource();
   bufferSource.buffer = buffer;
   const playbackRate = 1.0 * Math.pow(2, transpose / 12);
-  // bufferSource.playbackRate.value = Math.pow(2, transpose / 12);
   bufferSource.playbackRate.value = playbackRate;
   return bufferSource;
 };
@@ -162,9 +161,17 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
     if (handler) {
       return handler(sampleMap);
     }
+    if (sampleMap.startsWith('bubo:')) {
+      const [_, repo] = sampleMap.split(':');
+      sampleMap = `github:Bubobubobubobubo/dough-${repo}`;
+    }
     if (sampleMap.startsWith('github:')) {
       let [_, path] = sampleMap.split('github:');
       path = path.endsWith('/') ? path.slice(0, -1) : path;
+      if (path.split('/').length === 2) {
+        // assume main as default branch if none set
+        path += '/main';
+      }
       sampleMap = `https://raw.githubusercontent.com/${path}/strudel.json`;
     }
     if (sampleMap.startsWith('shabda:')) {
@@ -232,6 +239,8 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
     begin = 0,
     loopEnd = 1,
     end = 1,
+    vib,
+    vibmod = 0.5,
   } = value;
   // load sample
   if (speed === 0) {
@@ -247,6 +256,19 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
 
   const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
 
+  // vibrato
+  let vibratoOscillator;
+  if (vib > 0) {
+    vibratoOscillator = getAudioContext().createOscillator();
+    vibratoOscillator.frequency.value = vib;
+    const gain = getAudioContext().createGain();
+    // Vibmod is the amount of vibrato, in semitones
+    gain.gain.value = vibmod * 100;
+    vibratoOscillator.connect(gain);
+    gain.connect(bufferSource.detune);
+    vibratoOscillator.start(0);
+  }
+
   // asny stuff above took too long?
   if (ac.currentTime > t) {
     logger(`[sampler] still loading sound "${s}:${n}"`, 'highlight');
@@ -278,6 +300,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
   envelope.connect(out);
   bufferSource.onended = function () {
     bufferSource.disconnect();
+    vibratoOscillator?.stop();
     envelope.disconnect();
     out.disconnect();
     onended();
diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs
index e3033afe1..00e2f42ca 100644
--- a/packages/superdough/superdough.mjs
+++ b/packages/superdough/superdough.mjs
@@ -112,6 +112,48 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
   return delays[orbit];
 }
 
+// each orbit will have its own lfo
+const phaserLFOs = {};
+function getPhaser(orbit, t, speed = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
+  //gain
+  const ac = getAudioContext();
+  const lfoGain = ac.createGain();
+  lfoGain.gain.value = sweep;
+
+  //LFO
+  if (phaserLFOs[orbit] == null) {
+    phaserLFOs[orbit] = ac.createOscillator();
+    phaserLFOs[orbit].frequency.value = speed;
+    phaserLFOs[orbit].type = 'sine';
+    phaserLFOs[orbit].start();
+  }
+
+  phaserLFOs[orbit].connect(lfoGain);
+  if (phaserLFOs[orbit].frequency.value != speed) {
+    phaserLFOs[orbit].frequency.setValueAtTime(speed, t);
+  }
+
+  //filters
+  const numStages = 2; //num of filters in series
+  let fOffset = 0;
+  const filterChain = [];
+  for (let i = 0; i < numStages; i++) {
+    const filter = ac.createBiquadFilter();
+    filter.type = 'notch';
+    filter.gain.value = 1;
+    filter.frequency.value = centerFrequency + fOffset;
+    filter.Q.value = 2 - Math.min(Math.max(depth * 2, 0), 1.9);
+
+    lfoGain.connect(filter.detune);
+    fOffset += 282;
+    if (i > 0) {
+      filterChain[i - 1].connect(filter);
+    }
+    filterChain.push(filter);
+  }
+  return filterChain[filterChain.length - 1];
+}
+
 let reverbs = {};
 
 let hasChanged = (now, before) => now !== undefined && now !== before;
@@ -226,6 +268,12 @@ export const superdough = async (value, deadline, hapDuration) => {
     bpsustain = 1,
     bprelease = 0.01,
     bandq = 1,
+
+    //phaser
+    phaser,
+    phaserdepth = 0.75,
+    phasersweep,
+    phasercenter,
     //
     coarse,
     crush,
@@ -260,6 +308,7 @@ export const superdough = async (value, deadline, hapDuration) => {
   if (bank && s) {
     s = `${bank}_${s}`;
   }
+
   // get source AudioNode
   let sourceNode;
   if (source) {
@@ -377,6 +426,11 @@ export const superdough = async (value, deadline, hapDuration) => {
     panner.pan.value = 2 * pan - 1;
     chain.push(panner);
   }
+  // phaser
+  if (phaser !== undefined && phaserdepth > 0) {
+    const phaserFX = getPhaser(orbit, t, phaser, phaserdepth, phasercenter, phasersweep);
+    chain.push(phaserFX);
+  }
 
   // last gain
   const post = gainNode(postgain);
diff --git a/packages/webaudio/scope.mjs b/packages/webaudio/scope.mjs
index 9f052e8ee..1affd2624 100644
--- a/packages/webaudio/scope.mjs
+++ b/packages/webaudio/scope.mjs
@@ -3,7 +3,7 @@ import { analyser, getAnalyzerData } from 'superdough';
 
 export function drawTimeScope(
   analyser,
-  { align = true, color = 'white', thickness = 3, scale = 0.25, pos = 0.75, next = 1, trigger = 0 } = {},
+  { align = true, color = 'white', thickness = 3, scale = 0.25, pos = 0.75, trigger = 0 } = {},
 ) {
   const ctx = getDrawContext();
   const dataArray = getAnalyzerData('time');
@@ -22,10 +22,9 @@ export function drawTimeScope(
 
   const sliceWidth = (canvas.width * 1.0) / bufferSize;
   let x = 0;
-
   for (let i = triggerIndex; i < bufferSize; i++) {
     const v = dataArray[i] + 1;
-    const y = (1 - (scale * (v - 1) + pos)) * canvas.height;
+    const y = (pos - scale * (v - 1)) * canvas.height;
 
     if (i === 0) {
       ctx.moveTo(x, y);
@@ -71,6 +70,18 @@ function clearScreen(smear = 0, smearRGB = `0,0,0`) {
   }
 }
 
+/**
+ * Renders an oscilloscope for the frequency domain of the audio signal.
+ * @name fscope
+ * @param {string} color line color as hex or color name. defaults to white.
+ * @param {number} scale scales the y-axis. Defaults to 0.25
+ * @param {number} pos y-position relative to screen height. 0 = top, 1 = bottom of screen
+ * @param {number} lean y-axis alignment where 0 = top and 1 = bottom
+ * @param {number} min min value
+ * @param {number} max max value
+ * @example
+ * s("sawtooth").fscope()
+ */
 Pattern.prototype.fscope = function (config = {}) {
   return this.analyze(1).draw(() => {
     clearScreen(config.smear);
@@ -78,6 +89,20 @@ Pattern.prototype.fscope = function (config = {}) {
   });
 };
 
+/**
+ * Renders an oscilloscope for the time domain of the audio signal.
+ * @name scope
+ * @synonyms tscope
+ * @param {object} config optional config with options:
+ * @param {boolean} align if 1, the scope will be aligned to the first zero crossing. defaults to 1
+ * @param {string} color line color as hex or color name. defaults to white.
+ * @param {number} thickness line thickness. defaults to 3
+ * @param {number} scale scales the y-axis. Defaults to 0.25
+ * @param {number} pos y-position relative to screen height. 0 = top, 1 = bottom of screen
+ * @param {number} trigger amplitude value that is used to align the scope. defaults to 0.
+ * @example
+ * s("sawtooth").scope()
+ */
 Pattern.prototype.tscope = function (config = {}) {
   return this.analyze(1).draw(() => {
     clearScreen(config.smear);
diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap
index 0963638f8..a12fcb655 100644
--- a/test/__snapshots__/examples.test.mjs.snap
+++ b/test/__snapshots__/examples.test.mjs.snap
@@ -2143,6 +2143,15 @@ exports[`runs examples > example "freq" example index 1 1`] = `
 ]
 `;
 
+exports[`runs examples > example "fscope" example index 0 1`] = `
+[
+  "[ 0/1 → 1/1 | s:sawtooth analyze:1 ]",
+  "[ 1/1 → 2/1 | s:sawtooth analyze:1 ]",
+  "[ 2/1 → 3/1 | s:sawtooth analyze:1 ]",
+  "[ 3/1 → 4/1 | s:sawtooth analyze:1 ]",
+]
+`;
+
 exports[`runs examples > example "ftype" example index 0 1`] = `
 [
   "[ 0/1 → 1/1 | note:c2 s:sawtooth cutoff:500 bpenv:4 ftype:12db ]",
@@ -2430,6 +2439,19 @@ exports[`runs examples > example "irand" example index 0 1`] = `
 ]
 `;
 
+exports[`runs examples > example "iresponse" example index 0 1`] = `
+[
+  "[ 0/1 → 1/2 | s:bd room:0.8 ir:shaker_large i:0 ]",
+  "[ 1/2 → 1/1 | s:sd room:0.8 ir:shaker_large i:0 ]",
+  "[ 1/1 → 3/2 | s:bd room:0.8 ir:shaker_large i:2 ]",
+  "[ 3/2 → 2/1 | s:sd room:0.8 ir:shaker_large i:2 ]",
+  "[ 2/1 → 5/2 | s:bd room:0.8 ir:shaker_large i:0 ]",
+  "[ 5/2 → 3/1 | s:sd room:0.8 ir:shaker_large i:0 ]",
+  "[ 3/1 → 7/2 | s:bd room:0.8 ir:shaker_large i:2 ]",
+  "[ 7/2 → 4/1 | s:sd room:0.8 ir:shaker_large i:2 ]",
+]
+`;
+
 exports[`runs examples > example "iter" example index 0 1`] = `
 [
   "[ 0/1 → 1/4 | note:A3 ]",
@@ -3297,6 +3319,187 @@ exports[`runs examples > example "perlin" example index 0 1`] = `
 ]
 `;
 
+exports[`runs examples > example "phaser" example index 0 1`] = `
+[
+  "[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:1 ]",
+  "[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:1 ]",
+  "[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:1 ]",
+  "[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:1 ]",
+  "[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:1 ]",
+  "[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:1 ]",
+  "[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:1 ]",
+  "[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:1 ]",
+  "[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 ]",
+  "[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 ]",
+  "[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 ]",
+  "[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 ]",
+  "[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 ]",
+  "[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 ]",
+  "[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 ]",
+  "[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 ]",
+  "[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:4 ]",
+  "[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:4 ]",
+  "[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:4 ]",
+  "[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:4 ]",
+  "[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:4 ]",
+  "[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:4 ]",
+  "[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:4 ]",
+  "[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:4 ]",
+  "[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:8 ]",
+  "[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:8 ]",
+  "[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:8 ]",
+  "[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:8 ]",
+  "[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:8 ]",
+  "[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:8 ]",
+  "[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:8 ]",
+  "[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:8 ]",
+]
+`;
+
+exports[`runs examples > example "phasercenter" example index 0 1`] = `
+[
+  "[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
+  "[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
+  "[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
+  "[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
+  "[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
+  "[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
+  "[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
+  "[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]",
+  "[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
+  "[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
+  "[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
+  "[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
+  "[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
+  "[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
+  "[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
+  "[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]",
+  "[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+  "[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]",
+]
+`;
+
+exports[`runs examples > example "phaserdepth" example index 0 1`] = `
+[
+  "[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
+  "[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
+  "[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
+  "[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
+  "[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
+  "[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
+  "[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
+  "[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]",
+  "[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
+  "[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
+  "[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
+  "[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
+  "[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
+  "[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
+  "[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
+  "[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]",
+  "[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
+  "[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
+  "[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
+  "[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
+  "[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
+  "[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
+  "[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
+  "[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]",
+  "[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
+  "[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
+  "[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
+  "[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
+  "[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
+  "[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
+  "[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
+  "[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]",
+]
+`;
+
+exports[`runs examples > example "phasersweep" example index 0 1`] = `
+[
+  "[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
+  "[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
+  "[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
+  "[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
+  "[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
+  "[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
+  "[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
+  "[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]",
+  "[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
+  "[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
+  "[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
+  "[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
+  "[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
+  "[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
+  "[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
+  "[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]",
+  "[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+  "[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]",
+]
+`;
+
+exports[`runs examples > example "pianoroll" example index 0 1`] = `
+[
+  "[ 0/1 → 1/8 | note:C2 s:piano clip:1 ]",
+  "[ (1/4 → 1/3) ⇝ 3/8 | note:C2 s:piano clip:1 ]",
+  "[ 1/4 ⇜ (1/3 → 3/8) | note:A2 s:piano clip:1 ]",
+  "[ 3/8 → 1/2 | note:A2 s:piano clip:1 ]",
+  "[ (5/8 → 2/3) ⇝ 3/4 | note:A2 s:piano clip:1 ]",
+  "[ 5/8 ⇜ (2/3 → 3/4) | note:G2 s:piano clip:1 ]",
+  "[ 3/4 → 7/8 | note:G2 s:piano clip:1 ]",
+  "[ 1/1 → 9/8 | note:C2 s:piano clip:1 ]",
+  "[ (5/4 → 4/3) ⇝ 11/8 | note:C2 s:piano clip:1 ]",
+  "[ 5/4 ⇜ (4/3 → 11/8) | note:A2 s:piano clip:1 ]",
+  "[ 11/8 → 3/2 | note:A2 s:piano clip:1 ]",
+  "[ (13/8 → 5/3) ⇝ 7/4 | note:A2 s:piano clip:1 ]",
+  "[ 13/8 ⇜ (5/3 → 7/4) | note:G2 s:piano clip:1 ]",
+  "[ 7/4 → 15/8 | note:G2 s:piano clip:1 ]",
+  "[ 2/1 → 17/8 | note:C2 s:piano clip:1 ]",
+  "[ (9/4 → 7/3) ⇝ 19/8 | note:C2 s:piano clip:1 ]",
+  "[ 9/4 ⇜ (7/3 → 19/8) | note:A2 s:piano clip:1 ]",
+  "[ 19/8 → 5/2 | note:A2 s:piano clip:1 ]",
+  "[ (21/8 → 8/3) ⇝ 11/4 | note:A2 s:piano clip:1 ]",
+  "[ 21/8 ⇜ (8/3 → 11/4) | note:G2 s:piano clip:1 ]",
+  "[ 11/4 → 23/8 | note:G2 s:piano clip:1 ]",
+  "[ 3/1 → 25/8 | note:C2 s:piano clip:1 ]",
+  "[ (13/4 → 10/3) ⇝ 27/8 | note:C2 s:piano clip:1 ]",
+  "[ 13/4 ⇜ (10/3 → 27/8) | note:A2 s:piano clip:1 ]",
+  "[ 27/8 → 7/2 | note:A2 s:piano clip:1 ]",
+  "[ (29/8 → 11/3) ⇝ 15/4 | note:A2 s:piano clip:1 ]",
+  "[ 29/8 ⇜ (11/3 → 15/4) | note:G2 s:piano clip:1 ]",
+  "[ 15/4 → 31/8 | note:G2 s:piano clip:1 ]",
+]
+`;
+
 exports[`runs examples > example "pick" example index 0 1`] = `
 [
   "[ 0/1 → 1/2 | note:g ]",
@@ -4235,6 +4438,15 @@ exports[`runs examples > example "scaleTranspose" example index 0 1`] = `
 ]
 `;
 
+exports[`runs examples > example "scope" example index 0 1`] = `
+[
+  "[ 0/1 → 1/1 | s:sawtooth analyze:1 ]",
+  "[ 1/1 → 2/1 | s:sawtooth analyze:1 ]",
+  "[ 2/1 → 3/1 | s:sawtooth analyze:1 ]",
+  "[ 3/1 → 4/1 | s:sawtooth analyze:1 ]",
+]
+`;
+
 exports[`runs examples > example "segment" example index 0 1`] = `
 [
   "[ 0/1 → 1/24 | note:40.25 ]",
@@ -5235,6 +5447,51 @@ exports[`runs examples > example "withValue" example index 0 1`] = `
 ]
 `;
 
+exports[`runs examples > example "xfade" example index 0 1`] = `
+[
+  "[ 0/1 → 1/8 | s:hh gain:0 ]",
+  "[ 0/1 → 1/2 | s:bd gain:1 ]",
+  "[ 1/8 → 1/4 | s:hh gain:0 ]",
+  "[ 1/4 → 3/8 | s:hh gain:0 ]",
+  "[ 3/8 → 1/2 | s:hh gain:0 ]",
+  "[ 1/2 → 5/8 | s:hh gain:0 ]",
+  "[ 1/2 → 1/1 | s:bd gain:1 ]",
+  "[ 5/8 → 3/4 | s:hh gain:0 ]",
+  "[ 3/4 → 7/8 | s:hh gain:0 ]",
+  "[ 7/8 → 1/1 | s:hh gain:0 ]",
+  "[ 1/1 → 9/8 | s:hh gain:0.5 ]",
+  "[ 1/1 → 3/2 | s:bd gain:1 ]",
+  "[ 9/8 → 5/4 | s:hh gain:0.5 ]",
+  "[ 5/4 → 11/8 | s:hh gain:0.5 ]",
+  "[ 11/8 → 3/2 | s:hh gain:0.5 ]",
+  "[ 3/2 → 13/8 | s:hh gain:0.5 ]",
+  "[ 3/2 → 2/1 | s:bd gain:1 ]",
+  "[ 13/8 → 7/4 | s:hh gain:0.5 ]",
+  "[ 7/4 → 15/8 | s:hh gain:0.5 ]",
+  "[ 15/8 → 2/1 | s:hh gain:0.5 ]",
+  "[ 2/1 → 17/8 | s:hh gain:1 ]",
+  "[ 2/1 → 5/2 | s:bd gain:1 ]",
+  "[ 17/8 → 9/4 | s:hh gain:1 ]",
+  "[ 9/4 → 19/8 | s:hh gain:1 ]",
+  "[ 19/8 → 5/2 | s:hh gain:1 ]",
+  "[ 5/2 → 21/8 | s:hh gain:1 ]",
+  "[ 5/2 → 3/1 | s:bd gain:1 ]",
+  "[ 21/8 → 11/4 | s:hh gain:1 ]",
+  "[ 11/4 → 23/8 | s:hh gain:1 ]",
+  "[ 23/8 → 3/1 | s:hh gain:1 ]",
+  "[ 3/1 → 25/8 | s:hh gain:1 ]",
+  "[ 3/1 → 7/2 | s:bd gain:0.5 ]",
+  "[ 25/8 → 13/4 | s:hh gain:1 ]",
+  "[ 13/4 → 27/8 | s:hh gain:1 ]",
+  "[ 27/8 → 7/2 | s:hh gain:1 ]",
+  "[ 7/2 → 29/8 | s:hh gain:1 ]",
+  "[ 7/2 → 4/1 | s:bd gain:0.5 ]",
+  "[ 29/8 → 15/4 | s:hh gain:1 ]",
+  "[ 15/4 → 31/8 | s:hh gain:1 ]",
+  "[ 31/8 → 4/1 | s:hh gain:1 ]",
+]
+`;
+
 exports[`runs examples > example "zoom" example index 0 1`] = `
 [
   "[ 0/1 → 1/6 | s:hh ]",
diff --git a/website/src/docs/MiniRepl.css b/website/src/docs/MiniRepl.css
index 84927a883..c46110b79 100644
--- a/website/src/docs/MiniRepl.css
+++ b/website/src/docs/MiniRepl.css
@@ -1,26 +1,26 @@
-.cm-activeLine,
-.cm-activeLineGutter {
+.mini-repl .cm-activeLine,
+.mini-repl .cm-activeLineGutter {
   background-color: transparent !important;
 }
 
-.cm-theme {
+.mini-repl .cm-theme {
   background-color: var(--background);
   border: 1px solid var(--lineHighlight);
   padding: 2px;
 }
 
-.cm-scroller {
+.mini-repl .cm-scroller {
   font-family: inherit !important;
 }
 
-.cm-gutters {
+.mini-repl .cm-gutters {
   display: none !important;
 }
 
-.cm-cursorLayer {
+.mini-repl .cm-cursorLayer {
   animation-name: inherit !important;
 }
 
-.cm-cursor {
+.mini-repl .cm-cursor {
   border-left: 2px solid currentcolor !important;
 }
diff --git a/website/src/docs/MiniRepl.jsx b/website/src/docs/MiniRepl.jsx
index 552e1027e..10af1d514 100644
--- a/website/src/docs/MiniRepl.jsx
+++ b/website/src/docs/MiniRepl.jsx
@@ -33,7 +33,7 @@ export function MiniRepl({
       .catch((err) => console.error(err));
   }, []);
   return Repl ? (
-    
+
@@ -42,9 +42,9 @@ lpf = **l**ow **p**ass **f**ilter - Füg noch mehr `lpf` Werte hinzu -- Das pattern in `lpf` ändert nicht den Rhythmus der Bassline +- Das Pattern in `lpf` ändert nicht den Rhythmus der Basslinie -Später sehen wir wie man mit Wellenformen Dinge automatisieren kann. +Später sehen wir, wie man mit Wellenformen Dinge automatisieren kann. @@ -73,7 +73,7 @@ Später sehen wir wie man mit Wellenformen Dinge automatisieren kann. Bei Rhythmen ist die Dynamik (= Veränderungen der Lautstärke) sehr wichtig. -- Entferne `.gain(...)` und achte darauf wie es viel flacher klingt. +- Entferne `.gain(...)` und achte darauf, wie es viel flacher klingt. - Mach es rückgängig (strg+z dann strg+enter) @@ -99,13 +99,13 @@ Lass uns die obigen Beispiele kombinieren: -Versuche die einzelnen Teile innerhalb `stack` zu erkennen, schau dir an wie die Kommas gesetzt sind. +Versuche die einzelnen Teile innerhalb von `stack` zu erkennen. Schau dir an wie die Kommas gesetzt sind. -Die 3 Teile (Drums, Bass, Akkorde) sind genau wie vorher, nur in einem `stack`, getrennt durch Kommas +Die 3 Teile (Drums, Bass, Akkorde) sind genau wie vorher, nur in einem `stack`, getrennt durch Kommas. -**Den Sound formen mit ADSR Hüllkurve** +**Den Sound formen mit ADSR-Hüllkurve** -Versuche herauszufinden was die Zahlen machen. Probier folgendes: +Versuche herauszufinden, was die Zahlen machen. Probier folgendes: - attack: `.5` vs `0` - decay: `.5` vs `0` - sustain: `1` vs `.25` vs `0` - release: `0` vs `.5` vs `1` -Kannst du erraten was die einzelnen Werte machen? +Kannst du erraten, was die einzelnen Werte machen? @@ -142,7 +142,7 @@ Kannst du erraten was die einzelnen Werte machen? -**adsr Kurznotation** +**adsr-Kurznotation** @@ -181,7 +181,7 @@ Was passiert wenn du `.delay(".8:.06:.8")` schreibst? Kannst du erraten was die - a: Lautstärke des Delays - b: Verzögerungszeit -- c: Feedback (je kleiner desto schneller verschwindet das Delay) +- c: Feedback (je kleiner, desto schneller verschwindet das Delay) @@ -203,7 +203,7 @@ Füg auch ein Delay hinzu! -**kleiner dub tune** +**kleiner Dub-Tune** -Füg `.hush()` ans ende eines Patterns im stack... +Füg `.hush()` ans Ende eines Patterns im stack... @@ -258,25 +258,25 @@ Füg `.hush()` ans ende eines Patterns im stack... **fast and slow = schnell und langsam** -Mit `fast` und `slow` kann man das tempo eines patterns außerhalb der Mini-Notation ändern: +Mit `fast` und `slow` kann man das Tempo eines Patterns außerhalb der Mini-Notation ändern: -Ändere den `slow` Wert. Tausche `slow` durch `fast`. +Ändere den `slow`-Wert. Ersetze `slow` durch `fast`. -Was passiert wenn du den Wert automatisierst? z.b. `.fast("<1 [2 4]>")` ? +Was passiert, wenn du den Wert automatisierst? z.b. `.fast("<1 [2 4]>")` ? -Übrigens, innerhalb der Mini-Notation, `fast` ist `*` und `slow` ist `/`. +Übrigens, innerhalb der Mini-Notation: `fast` ist `*` und `slow` ist `/`. ")`} /> ## Automation mit Signalen -Anstatt Werte schrittweise zu automatisieren können wir auch sogenannte Signale benutzen: +Anstatt Werte schrittweise zu automatisieren, können wir auch sogenannte Signale benutzen: @@ -296,7 +296,7 @@ Signale bewegen sich standardmäßig zwischen 0 und 1. Wir können das mit `rang -`range` ist nützlich wenn wir Funktionen mit einem anderen Wertebereich als 0 und 1 automatisieren wollen (z.b. lpf) +`range` ist nützlich wenn wir Funktionen mit einem anderen Wertebereich als 0 und 1 automatisieren wollen (z.b. `lpf`) @@ -322,7 +322,7 @@ Die ganze Automation braucht nun 8 cycle bis sie sich wiederholt. ## Rückblick -| name | example | +| Name | Beispiel | | ----- | -------------------------------------------------------------------------------------------------- | | lpf | ")`} /> | | vowel | ")`} /> | @@ -333,4 +333,4 @@ Die ganze Automation braucht nun 8 cycle bis sie sich wiederholt. | speed | ")`} /> | | range | | -Lass uns nun die für Tidal typischen [Pattern Effekte anschauen](/de/workshop/pattern-effects). +Lass uns nun die für Tidal typischen [Pattern-Effekte anschauen](/de/workshop/pattern-effects). diff --git a/website/src/pages/de/workshop/first-sounds.mdx b/website/src/pages/de/workshop/first-sounds.mdx index fea64cbd4..a689add80 100644 --- a/website/src/pages/de/workshop/first-sounds.mdx +++ b/website/src/pages/de/workshop/first-sounds.mdx @@ -277,7 +277,7 @@ Das haben wir bisher gelernt: | Schneller | \* | | | Parallel | , | | -Die mit Apostrophen umgebene Mini-Notation benutzt man normalerweise in eine sogenannten Funktion. +Die mit Apostrophen umgebene Mini-Notation benutzt man normalerweise in einer sogenannten Funktion. Die folgenden Funktionen haben wir bereits gesehen: | Name | Description | Example | diff --git a/website/src/pages/de/workshop/pattern-effects.mdx b/website/src/pages/de/workshop/pattern-effects.mdx index b701958a8..703358819 100644 --- a/website/src/pages/de/workshop/pattern-effects.mdx +++ b/website/src/pages/de/workshop/pattern-effects.mdx @@ -1,5 +1,5 @@ --- -title: Pattern Effekte +title: Pattern-Effekte layout: ../../../layouts/MainLayout.astro --- @@ -7,11 +7,11 @@ import { MiniRepl } from '@src/docs/MiniRepl'; import Box from '@components/Box.astro'; import QA from '@components/QA'; -# Pattern Effekte +# Pattern-Effekte -Bis jetzt sind die meisten Funktionen die wir kennengelernt haben ähnlich wie Funktionen in anderen Musik Programmen: Sequencing von Sounds, Noten und Effekten. +Bis jetzt sind die meisten Funktionen, die wir kennengelernt haben, ähnlich wie Funktionen in anderen Musik Programmen: Sequencing von Sounds, Noten und Effekten. -In diesem Kapitel beschäftigen wir uns mit Funktionen die weniger herkömmlich oder auch enzigartig sind. +In diesem Kapitel beschäftigen wir uns mit Funktionen die weniger herkömmlich oder auch einzigartig sind. **rev = rückwärts abspielen** @@ -21,7 +21,7 @@ In diesem Kapitel beschäftigen wir uns mit Funktionen die weniger herkömmlich -So würde man das ohne jux schreiben: +So würde man das ohne `jux` schreiben: -Lass uns visualisieren was hier passiert: +Lass uns visualisieren, was hier passiert: -Das hat den gleichen Effekt wie: +Das hat den gleichen Effekt, wie: "` -In der notation `x=>x.`, das `x` ist das Pattern das wir bearbeiten. +In der Notation `x=>x.`, ist `x` das Pattern, das wir bearbeiten. -`off` ist auch nützlich für sounds: +`off` ist auch nützlich für Sounds: x.`, das `x` ist das Pattern das wir bearbeiten. .off(1/8, x=>x.speed(1.5).gain(.25))`} /> -| name | description | example | +| Name | Beschreibung | Beispiel | | ---- | --------------------------------- | ---------------------------------------------------------------------------------------------- | | rev | rückwärts | | -| jux | ein stereo-kanal modifizieren | | -| add | addiert zahlen oder noten | ")).scale("C:minor")`} /> | -| ply | multipliziert jedes element x mal | ")`} /> | -| off | verzögert eine modifizierte kopie | x.speed(2))`} /> | +| jux | einen Stereo-Kanal modifizieren | | +| add | addiert Zahlen oder Noten | ")).scale("C:minor")`} /> | +| ply | multipliziert jedes Element x mal | ")`} /> | +| off | verzögert eine modifizierte Kopie | x.speed(2))`} /> | diff --git a/website/src/pages/de/workshop/recap.mdx b/website/src/pages/de/workshop/recap.mdx index db392b8b0..c0d577d16 100644 --- a/website/src/pages/de/workshop/recap.mdx +++ b/website/src/pages/de/workshop/recap.mdx @@ -7,19 +7,19 @@ import { MiniRepl } from '../../../docs/MiniRepl'; # Workshop Rückblick -Diese Seite ist eine Auflistung aller im Workshop enthaltenen Funktionen. +Diese Seite ist eine Auflistung aller im Workshop vorgestellten Funktionen. ## Mini Notation -| Concept | Syntax | Example | +| Konzept | Syntax | Beispiel | | --------------------- | -------- | -------------------------------------------------------------------------------- | -| Sequence | space | | -| Sample Nummer | :x | | +| Sequenz | space | | +| Sample-Nummer | :x | | | Pausen | ~ | | -| Unter-Sequences | \[\] | | -| Unter-Unter-Sequences | \[\[\]\] | | +| Unter-Sequenzen | \[\] | | +| Unter-Unter-Sequenzen | \[\[\]\] | | | Schneller | \* | | -| Slow down | \/ | | +| Verlangsamen | \/ | | | Parallel | , | | | Alternieren | \<\> | ")`} /> | | Verlängern | @ | | @@ -27,23 +27,23 @@ Diese Seite ist eine Auflistung aller im Workshop enthaltenen Funktionen. ## Sounds -| Name | Description | Example | +| Name | Beschreibung | Beispiel | | ----- | -------------------------- | ---------------------------------------------------------------------------------- | -| sound | spielt den sound mit namen | | -| bank | wählt die soundbank | | -| n | wählt sample mit nummer | | +| sound | spielt den Sound mit Namen | | +| bank | wählt die Soundbank | | +| n | wählt Sample mit Nummer | | -## Notes +## Noten -| Name | Description | Example | +| Name | Beschreibung | Beispiel | | --------- | ---------------------------------- | -------------------------------------------------------------------------------------------- | -| note | wählt note per zahl oder buchstabe | | -| n + scale | wählt note n in skala | | -| stack | spielt mehrere patterns parallel | | +| note | wählt Note per Zahl oder Buchstabe | | +| n + scale | wählt Note n in Skala | | +| stack | spielt mehrere Patterns parallel | | -## Audio Effekte +## Audio-Effekte -| name | example | +| Name | Beispiele | | ----- | -------------------------------------------------------------------------------------------------- | | lpf | ")`} /> | | vowel | ")`} /> | @@ -54,15 +54,15 @@ Diese Seite ist eine Auflistung aller im Workshop enthaltenen Funktionen. | speed | ")`} /> | | range | | -## Pattern Effects +## Pattern-Effekte -| name | description | example | +| Name | Beschreibung | Beispiel | | ---- | --------------------------------- | ---------------------------------------------------------------------------------------------- | -| cpm | tempo in cycles pro minute | | +| cpm | Tempo in Cycles pro Minute | | | fast | schneller | | | slow | langsamer | | | rev | rückwärts | | -| jux | ein stereo-kanal modifizieren | | -| add | addiert zahlen oder noten | ")).scale("C:minor")`} /> | -| ply | jedes element schneller machen | ")`} /> | -| off | verzögert eine modifizierte kopie | x.speed(2))`} /> | +| jux | einen Stereo-Kanal modifizieren | | +| add | addiert Zahlen oder Noten | ")).scale("C:minor")`} /> | +| ply | jedes Element schneller machen | ")`} /> | +| off | verzögert eine modifizierte Kopie | x.speed(2))`} /> | diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx index 308042409..a3ae91d79 100644 --- a/website/src/pages/learn/effects.mdx +++ b/website/src/pages/learn/effects.mdx @@ -156,6 +156,10 @@ There is one filter envelope for each filter type and thus one set of envelope f +## xfade + + + # Panning ## jux @@ -236,3 +240,21 @@ global effects use the same chain for all events of the same orbit: Next, we'll look at strudel's support for [Csound](/learn/csound). + +## Phaser + +### phaser + + + +### phaserdepth + + + +### phasercenter + + + +### phasersweep + + diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index 8423315fe..bea5af25a 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -387,6 +387,7 @@ function SettingsTab({ scheduler }) { isAutoCompletionEnabled, isFlashEnabled, isPatternHighlightingEnabled, + isTooltipEnabled, isLineWrappingEnabled, fontSize, fontFamily, @@ -459,6 +460,11 @@ function SettingsTab({ scheduler }) { onChange={(cbEvent) => settingsMap.setKey('isAutoCompletionEnabled', cbEvent.target.checked)} value={isAutoCompletionEnabled} /> + settingsMap.setKey('isTooltipEnabled', cbEvent.target.checked)} + value={isTooltipEnabled} + /> settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)} diff --git a/website/src/repl/Reference.jsx b/website/src/repl/Reference.jsx index de52982e9..cf6fd5b12 100644 --- a/website/src/repl/Reference.jsx +++ b/website/src/repl/Reference.jsx @@ -3,17 +3,31 @@ const visibleFunctions = jsdocJson.docs .filter(({ name, description }) => name && !name.startsWith('_') && !!description) .sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name)); +const getInnerText = (html) => { + var div = document.createElement('div'); + div.innerHTML = html; + return div.textContent || div.innerText || ''; +}; + export function Reference() { return (
-
+

API Reference

@@ -24,8 +38,14 @@ export function Reference() {

{entry.name}

{/* {entry.meta.filename} */} -

+
    + {entry.params?.map(({ name, type, description }, i) => ( +
  • + {name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)} : ''} +
  • + ))} +
{entry.examples?.map((example, j) => (
{example}
))} diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 02245e183..e43c2aa01 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -91,6 +91,7 @@ export function Repl({ embedded = false }) { fontFamily, isLineNumbersDisplayed, isAutoCompletionEnabled, + isTooltipEnabled, isLineWrappingEnabled, panelPosition, isZen, @@ -287,6 +288,7 @@ export function Repl({ embedded = false }) { keybindings={keybindings} isLineNumbersDisplayed={isLineNumbersDisplayed} isAutoCompletionEnabled={isAutoCompletionEnabled} + isTooltipEnabled={isTooltipEnabled} isLineWrappingEnabled={isLineWrappingEnabled} fontSize={fontSize} fontFamily={fontFamily} diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 2de6e4d79..c1872ec81 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -9,6 +9,7 @@ export const defaultSettings = { isAutoCompletionEnabled: false, isPatternHighlightingEnabled: true, isFlashEnabled: true, + isTooltipEnabled: false, isLineWrappingEnabled: false, theme: 'strudelTheme', fontFamily: 'monospace', @@ -28,6 +29,7 @@ export function useSettings() { isZen: [true, 'true'].includes(state.isZen) ? true : false, isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false, isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false, + isTooltipEnabled: [true, 'true'].includes(state.isTooltipEnabled) ? true : false, isLineWrappingEnabled: [true, 'true'].includes(state.isLineWrappingEnabled) ? true : false, isPatternHighlightingEnabled: [true, 'true'].includes(state.isPatternHighlightingEnabled) ? true : false, isFlashEnabled: [true, 'true'].includes(state.isFlashEnabled) ? true : false,