Compare commits

..

3 Commits

Author SHA1 Message Date
Felix Roos a2f3b85f38 remove notes 2023-06-11 11:20:28 +02:00
Felix Roos 8e6846a3aa add workshop file 2023-06-10 12:14:15 +02:00
Felix Roos 34ee2081d3 fix: typo 2023-06-09 23:02:48 +02:00
86 changed files with 3428 additions and 7929 deletions
-62
View File
@@ -1,62 +0,0 @@
name: Tauri Builder
on:
push:
tags:
- "v*"
workflow_dispatch:
jobs:
release:
strategy:
fail-fast: false
matrix:
platform: [macos-latest, ubuntu-latest, windows-latest]
include:
- os: ubuntu-latest
rust_target: x86_64-unknown-linux-gnu
- os: macos-latest
rust_target: x86_64-apple-darwin
- os: macos-latest
rust_target: aarch64-apple-darwin
- os: windows-latest
rust_target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8.6.2
- name: Node.js setup
uses: actions/setup-node@v3
with:
node-version: latest
# node-version-file: '.nvmrc'
- name: Install Rust (Stable)
run:
curl https://sh.rustup.rs -sSf | sh -s -- -y
- name: Install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
- name: Install app dependencies from lockfile and build web
run: pnpm install
- name: Build the app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# tauri-action replaces \_\_VERSION\_\_ with the app version
tagName: ${{ github.ref_name }}
releaseName: "Strudel v__VERSION__"
releaseBody: |
See the assets to download this version and install.
releaseDraft: true
prerelease: false
-1
View File
@@ -43,4 +43,3 @@ dev-dist
Dirt-Samples
tidal-drum-machines
webaudiofontdata
src-tauri/target
+1 -1
View File
@@ -31,7 +31,7 @@ Use one of the Communication Channels listed above.
## Improve the Docs
If you find some weak spots in the [docs](https://strudel.tidalcycles.org/workshop/getting-started/),
If you find some weak spots in the [docs](https://strudel.tidalcycles.org/learn/getting-started),
you can edit each file directly on github via the "Edit this page" link located in the right sidebar.
## Propose a Feature
+3 -4
View File
@@ -52,10 +52,10 @@
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*",
"acorn": "^8.8.1",
"dependency-tree": "^9.0.0"
"dependency-tree": "^9.0.0",
"vitest": "^0.28.0"
},
"devDependencies": {
"@tauri-apps/cli": "^1.4.0",
"@vitest/ui": "^0.28.0",
"canvas": "^2.11.2",
"eslint": "^8.39.0",
@@ -66,7 +66,6 @@
"jsdoc-to-markdown": "^8.0.0",
"lerna": "^6.6.1",
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.28.0"
"rollup-plugin-visualizer": "^5.8.1"
}
}
+1 -1
View File
@@ -133,7 +133,7 @@ export class StrudelMirror {
this.code = initialCode;
this.drawer = new Drawer((haps, time) => {
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.whole.end);
this.highlight(currentFrame);
onDraw?.(haps, time, currentFrame);
}, drawTime);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/codemirror",
"version": "0.8.4",
"version": "0.8.3",
"description": "Codemirror Extensions for Strudel",
"main": "codemirror.mjs",
"publishConfig": {
+4 -6
View File
@@ -222,7 +222,6 @@ const generic_params = [
*
* @name legato
* @param {number | Pattern} duration between 0 and 1, where 1 is the length of the whole hap time
* @noAutocomplete
* @example
* "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>")
*
@@ -750,14 +749,13 @@ const generic_params = [
['val'],
['cps'],
/**
* Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration.
* In tidal, this would be done with legato, [which has a complicated history in strudel](https://github.com/tidalcycles/strudel/issues/111).
* For now, if you're coming from tidal, just think clip = legato.
* If set to 1, samples will be cut to the duration of their event.
* In tidal, this would be done with legato, which [is about to land in strudel too](https://github.com/tidalcycles/strudel/issues/111)
*
* @name clip
* @param {number | Pattern} factor >= 0
* @param {number | Pattern} active 1 or 0
* @example
* note("c a f e").s("piano").clip("<.5 1 2>")
* note("c a f e ~").s("piano").clip(1)
*
*/
['clip'],
+1 -5
View File
@@ -32,11 +32,7 @@ export class Hap {
}
get duration() {
return this.whole.end.sub(this.whole.begin).mul(typeof this.value?.clip === 'number' ? this.value?.clip : 1);
}
get endClipped() {
return this.whole.begin.add(this.duration);
return this.whole.end.sub(this.whole.begin);
}
wholeOrPart() {
-1
View File
@@ -24,7 +24,6 @@ export * from './time.mjs';
export * from './draw.mjs';
export * from './animate.mjs';
export * from './pianoroll.mjs';
export * from './spiral.mjs';
export * from './ui.mjs';
export { default as drawLine } from './drawLine.mjs';
export { default as gist } from './gist.js';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/core",
"version": "0.8.2",
"version": "0.8.1",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
+5 -33
View File
@@ -1582,24 +1582,6 @@ export const range2 = register('range2', function (min, max, pat) {
return pat.fromBipolar()._range(min, max);
});
/**
* Allows dividing numbers via list notation using ":".
* Returns a new pattern with just numbers.
* @name ratio
* @memberof Pattern
* @returns Pattern
* @example
* ratio("1, 5:4, 3:2").mul(110).freq().s("piano").slow(2)
*/
export const ratio = register('ratio', (pat) =>
pat.fmap((v) => {
if (!Array.isArray(v)) {
return v;
}
return v.slice(1).reduce((acc, n) => acc / n, v[0]);
}),
);
//////////////////////////////////////////////////////////////////////
// Structural and temporal transformations
@@ -1695,9 +1677,6 @@ export const ply = register('ply', function (factor, pat) {
* s("<bd sd> hh").fast(2) // s("[<bd sd> hh]*2")
*/
export const { fast, density } = register(['fast', 'density'], function (factor, pat) {
if (factor === 0) {
return silence;
}
factor = Fraction(factor);
const fastQuery = pat.withQueryTime((t) => t.mul(factor));
return fastQuery.withHapTime((t) => t.div(factor));
@@ -1724,9 +1703,6 @@ export const hurry = register('hurry', function (r, pat) {
* s("<bd sd> hh").slow(2) // s("[<bd sd> hh]/2")
*/
export const { slow, sparsity } = register(['slow', 'sparsity'], function (factor, pat) {
if (factor === 0) {
return silence;
}
return pat._fast(Fraction(1).div(factor));
});
@@ -2053,7 +2029,7 @@ export const jux = register('jux', function (func, pat) {
* @example
* "<0 [2 4]>"
* .echoWith(4, 1/8, (p,n) => p.add(n*2))
* .scale('C minor').note().clip(.2)
* .scale('C minor').note().legato(.2)
*/
export const { echoWith, echowith, stutWith, stutwith } = register(
['echoWith', 'echowith', 'stutWith', 'stutwith'],
@@ -2203,7 +2179,6 @@ export const velocity = register('velocity', function (velocity, pat) {
/**
*
* Multiplies the hap duration with the given factor.
* With samples, `clip` might be a better function to use ([more info](https://github.com/tidalcycles/strudel/pull/598))
* @name legato
* @memberof Pattern
* @example
@@ -2276,7 +2251,7 @@ const _loopAt = function (factor, pat, cps = 1) {
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(1.5)
*/
export const slice = register(
const slice = register(
'slice',
function (npat, ipat, opat) {
return npat.innerBind((n) =>
@@ -2304,7 +2279,7 @@ export const slice = register(
* s("breaks165").splice(8, "0 1 [2 3 0]@2 3 0@2 7").hurry(0.65)
*/
export const splice = register(
const splice = register(
'splice',
function (npat, ipat, opat) {
const sliced = slice(npat, ipat, opat);
@@ -2321,10 +2296,7 @@ export const splice = register(
false, // turns off auto-patternification
);
// this function will be redefined in repl.mjs to use the correct cps value.
// It is still here to work in cases where repl.mjs is not used
export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
return _loopAt(factor, pat, 1);
});
@@ -2341,6 +2313,6 @@ export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (facto
* s("rhodes").loopAtCps(4,1.5).cps(1.5)
*/
// TODO - global cps clock
export const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
return _loopAt(factor, pat, cps);
});
+2 -2
View File
@@ -83,9 +83,9 @@ Pattern.prototype.pianoroll = function ({
ctx.fillRect(0, 0, w, h);
}
const inFrame = (event) =>
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= t + to && event.endClipped >= t + from;
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= t + to && event.whole.end >= t + from;
events.filter(inFrame).forEach((event) => {
const isActive = event.whole.begin <= t && event.endClipped > t;
const isActive = event.whole.begin <= t && event.whole.end > t;
ctx.fillStyle = event.context?.color || inactive;
ctx.strokeStyle = event.context?.color || active;
ctx.globalAlpha = event.context.velocity ?? event.value?.gain ?? 1;
-23
View File
@@ -3,7 +3,6 @@ import { evaluate as _evaluate } from './evaluate.mjs';
import { logger } from './logger.mjs';
import { setTime } from './time.mjs';
import { evalScope } from './evaluate.mjs';
import { register } from './pattern.mjs';
export function repl({
interval,
@@ -51,32 +50,10 @@ export function repl({
const start = () => scheduler.start();
const pause = () => scheduler.pause();
const setCps = (cps) => scheduler.setCps(cps);
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
// the following functions use the cps value, which is why they are defined here..
const loopAt = register('loopAt', (cycles, pat) => {
return pat.loopAtCps(cycles, scheduler.cps);
});
const fit = register('fit', (pat) =>
pat.withHap((hap) =>
hap.withValue((v) => ({
...v,
speed: scheduler.cps / hap.whole.duration, // overwrite speed completely?
unit: 'c',
})),
),
);
evalScope({
loopAt,
fit,
setCps,
setcps: setCps,
setCpm,
setcpm: setCpm,
});
return { scheduler, evaluate, start, stop, pause, setCps, setPattern };
}
+1 -1
View File
@@ -27,7 +27,7 @@ export const isaw2 = isaw.toBipolar();
*
* @return {Pattern}
* @example
* "c3 [eb3,g3] g2 [g3,bb3]".note().clip(saw.slow(4))
* "c3 [eb3,g3] g2 [g3,bb3]".legato(saw.slow(4)).note()
* @example
* saw.range(0,8).segment(8).scale('C major').slow(4).note()
*
-117
View File
@@ -1,117 +0,0 @@
import { Pattern } from './index.mjs';
// polar coords -> xy
function fromPolar(angle, radius, cx, cy) {
const radians = ((angle - 90) * Math.PI) / 180;
return [cx + Math.cos(radians) * radius, cy + Math.sin(radians) * radius];
}
const xyOnSpiral = (angle, margin, cx, cy, rotate = 0) => fromPolar((angle + rotate) * 360, margin * angle, cx, cy); // TODO: logSpiral
// draw spiral / segment of spiral
function spiralSegment(options) {
let {
ctx,
from = 0,
to = 3,
margin = 50,
cx = 100,
cy = 100,
rotate = 0,
thickness = margin / 2,
color = '#0000ff30',
cap = 'round',
stretch = 1,
fromOpacity = 1,
toOpacity = 1,
} = options;
from *= stretch;
to *= stretch;
rotate *= stretch;
ctx.lineWidth = thickness;
ctx.lineCap = cap;
ctx.strokeStyle = color;
ctx.globalAlpha = fromOpacity;
ctx.beginPath();
let [sx, sy] = xyOnSpiral(from, margin, cx, cy, rotate);
ctx.moveTo(sx, sy);
const increment = 1 / 60;
let angle = from;
while (angle <= to) {
const [x, y] = xyOnSpiral(angle, margin, cx, cy, rotate);
//ctx.lineWidth = angle*thickness;
ctx.globalAlpha = ((angle - from) / (to - from)) * toOpacity;
ctx.lineTo(x, y);
angle += increment;
}
ctx.stroke();
}
Pattern.prototype.spiral = function (options = {}) {
const {
stretch = 1,
size = 80,
thickness = size / 2,
cap = 'butt', // round butt squar,
inset = 3, // start angl,
playheadColor = '#ffffff90',
playheadLength = 0.02,
playheadThickness = thickness,
padding = 0,
steady = 1,
inactiveColor = '#ffffff20',
colorizeInactive = 0,
fade = true,
// logSpiral = true,
} = options;
function spiral({ ctx, time, haps, drawTime }) {
ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
const [cx, cy] = [ctx.canvas.width / 2, ctx.canvas.height / 2];
const settings = {
margin: size / stretch,
cx,
cy,
stretch,
cap,
thickness,
};
const playhead = {
...settings,
thickness: playheadThickness,
from: inset - playheadLength,
to: inset,
color: playheadColor,
};
const [min] = drawTime;
const rotate = steady * time;
haps.forEach((hap) => {
const isActive = hap.whole.begin <= time && hap.endClipped > time;
const from = hap.whole.begin - time + inset;
const to = hap.endClipped - time + inset - padding;
const { color } = hap.context;
const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1;
spiralSegment({
ctx,
...settings,
from,
to,
rotate,
color: colorizeInactive || isActive ? color : inactiveColor,
fromOpacity: opacity,
toOpacity: opacity,
});
});
spiralSegment({
ctx,
...playhead,
rotate,
});
}
return this.onPaint((ctx, time, haps, drawTime) => spiral({ ctx, time, haps, drawTime }));
};
-40
View File
@@ -1,40 +0,0 @@
/*test for issue 302 support alternative solmization types */
import { sol2note } from '../util.mjs';
import { test } from 'vitest';
import assert from 'assert';
test('solmization - letters', () => {
const result = sol2note(60, 'letters');
const expected = 'C4';
assert.equal(result, expected);
});
test('solmization - solfeggio', () => {
const result = sol2note(60, 'solfeggio');
const expected = 'Do4';
assert.equal(result, expected);
});
test('solmization - indian', () => {
const result = sol2note(60, 'indian');
const expected = 'Sa4';
assert.equal(result, expected);
});
test('solmization - german', () => {
const result = sol2note(60, 'german');
const expected = 'C4';
assert.equal(result, expected);
});
test('solmization - byzantine', () => {
const result = sol2note(60, 'byzantine');
const expected = 'Ni4';
assert.equal(result, expected);
});
test('solmization - japanese', () => {
const result = sol2note(60, 'japanese');
const expected = 'I4';
assert.equal(result, expected);
});
+4 -60
View File
@@ -6,12 +6,12 @@ This program is free software: you can redistribute it and/or modify it under th
// returns true if the given string is a note
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
export const isNote = (name) => /^[a-gA-G][#bsf]*[0-9]?$/.test(name);
export const isNote = (name) => /^[a-gA-G][#bs]*[0-9]?$/.test(name);
export const tokenizeNote = (note) => {
if (typeof note !== 'string') {
return [];
}
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)([0-9])?$/)?.slice(1) || [];
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bs]*)([0-9])?$/)?.slice(1) || [];
if (!pc) {
return [];
}
@@ -25,7 +25,7 @@ export const noteToMidi = (note) => {
throw new Error('not a note: "' + note + '"');
}
const chroma = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }[pc.toLowerCase()];
const offset = acc?.split('').reduce((o, char) => o + { '#': 1, b: -1, s: 1, f: -1 }[char], 0) || 0;
const offset = acc?.split('').reduce((o, char) => o + { '#': 1, b: -1, s: 1 }[char], 0) || 0;
return (Number(oct) + 1) * 12 + chroma + offset;
};
export const midiToFreq = (n) => {
@@ -69,7 +69,7 @@ export const getFreq = (noteOrMidi) => {
const pcs = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
/**
* @deprecated only used in workshop (first-notes)
* @deprecated does not appear to be referenced or invoked anywhere in the codebase
* @noAutocomplete
*/
export const midi2note = (n) => {
@@ -215,59 +215,3 @@ export const splitAt = function (index, value) {
export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i]));
export const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
/* solmization, not used yet */
const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/
const indian = [
'Sa',
'Re',
'Ga',
'Ma',
'Pa',
'Dha',
'Ni',
]; /*indian musical notes, seems like they do not use flats or sharps*/
const german = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Hb', 'H']; /*german & dutch musical notes*/
const byzantine = [
'Ni',
'Pab',
'Pa',
'Voub',
'Vou',
'Ga',
'Dib',
'Di',
'Keb',
'Ke',
'Zob',
'Zo',
]; /*byzantine musical notes*/
const japanese = [
'I',
'Ro',
'Ha',
'Ni',
'Ho',
'He',
'To',
]; /*traditional japanese musical notes, seems like they do not use falts or sharps*/
const english = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
export const sol2note = (n, notation = 'letters') => {
const pc =
notation === 'solfeggio'
? solfeggio /*check if its is any of the following*/
: notation === 'indian'
? indian
: notation === 'german'
? german
: notation === 'byzantine'
? byzantine
: notation === 'japanese'
? japanese
: english; /*if not use standard version*/
const note = pc[n % 12]; /*calculating the midi value to the note*/
const oct = Math.floor(n / 12) - 1;
return note + oct;
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/csound",
"version": "0.8.0",
"version": "0.7.1",
"description": "csound bindings for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/eval",
"version": "0.8.0",
"version": "0.7.1",
"description": "Code evaluator for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -105,7 +105,7 @@ Pattern.prototype.midi = function (output) {
hap.ensureObjectValue();
// calculate time
const timingOffset = WebMidi.time - getAudioContext().getOutputTimestamp().contextTime * 1000;
const timingOffset = WebMidi.time - getAudioContext().currentTime * 1000;
time = time * 1000 + timingOffset;
// destructure value
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/midi",
"version": "0.8.0",
"version": "0.7.1",
"description": "Midi API for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/mini",
"version": "0.8.2",
"version": "0.8.1",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/osc",
"version": "0.8.0",
"version": "0.7.1",
"description": "OSC messaging for strudel",
"main": "osc.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/react",
"version": "0.8.0",
"version": "0.7.1",
"description": "React components for strudel",
"main": "src/index.js",
"publishConfig": {
+13 -25
View File
@@ -2,12 +2,12 @@ import React, { useMemo } from 'react';
import _CodeMirror from '@uiw/react-codemirror';
import { EditorView, Decoration } from '@codemirror/view';
import { StateField, StateEffect } from '@codemirror/state';
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
import { javascript } from '@codemirror/lang-javascript';
import strudelTheme from '../themes/strudel-theme';
import './style.css';
import { useCallback } from 'react';
import { autocompletion } from '@codemirror/autocomplete';
import { strudelAutocomplete } from './Autocomplete';
//import { strudelAutocomplete } from './Autocomplete';
import { vim } from '@replit/codemirror-vim';
import { emacs } from '@replit/codemirror-emacs';
@@ -88,7 +88,14 @@ const highlightField = StateField.define({
provide: (f) => EditorView.decorations.from(f),
});
const staticExtensions = [javascript(), highlightField, flashField];
const staticExtensions = [
javascript(),
highlightField,
flashField,
// javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }),
// autocompletion({ override: [strudelAutocomplete] }),
autocompletion({ override: [] }), // wait for https://github.com/uiwjs/react-codemirror/pull/458
];
export default function CodeMirror({
value,
@@ -98,8 +105,6 @@ export default function CodeMirror({
theme,
keybindings,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isLineWrappingEnabled,
fontSize = 18,
fontFamily = 'monospace',
options,
@@ -111,14 +116,12 @@ export default function CodeMirror({
},
[onChange],
);
const handleOnCreateEditor = useCallback(
(view) => {
onViewChanged?.(view);
},
[onViewChanged],
);
const handleOnUpdate = useCallback(
(viewUpdate) => {
if (viewUpdate.selectionSet && onSelectionChange) {
@@ -127,31 +130,16 @@ export default function CodeMirror({
},
[onSelectionChange],
);
const extensions = useMemo(() => {
let _extensions = [...staticExtensions];
let bindings = {
vim,
emacs,
};
if (bindings[keybindings]) {
_extensions.push(bindings[keybindings]());
return [...staticExtensions, bindings[keybindings]()];
}
if (isAutoCompletionEnabled) {
_extensions.push(javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }));
} else {
_extensions.push(autocompletion({ override: [] }));
}
if (isLineWrappingEnabled) {
_extensions.push(EditorView.lineWrapping);
}
return _extensions;
}, [keybindings, isAutoCompletionEnabled, isLineWrappingEnabled]);
return staticExtensions;
}, [keybindings]);
return (
<div style={{ fontSize, fontFamily }} className="w-full">
<_CodeMirror
-4
View File
@@ -24,7 +24,3 @@
.cm-theme-light {
width: 100%;
}
footer {
z-index: 0 !important;
}
+1 -1
View File
@@ -17,7 +17,7 @@ function useHighlighting({ view, pattern, active, getTime }) {
const begin = Math.max(lastEnd.current ?? audioTime, audioTime - 1 / 10, -0.01); // negative time seems buggy
const span = [round(begin), round(audioTime + 1 / 60)];
lastEnd.current = span[1];
highlights.current = highlights.current.filter((hap) => hap.endClipped > audioTime); // keep only highlights that are still active
highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
highlights.current = highlights.current.concat(haps); // add potential new onsets
view.dispatch({ effects: setHighlights.of({ haps: highlights.current }) }); // highlight all still active + new active haps
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/serial",
"version": "0.8.0",
"version": "0.7.1",
"description": "Webserial API for strudel",
"main": "serial.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/soundfonts",
"version": "0.8.2",
"version": "0.8.1",
"description": "Soundsfont support for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tonal",
"version": "0.8.2",
"version": "0.8.1",
"description": "Tonal functions for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -145,7 +145,7 @@ export const scaleTranspose = register('scaleTranspose', function (offset /* : n
export const scale = register('scale', function (scale, pat) {
// Supports ':' list syntax in mininotation
if (Array.isArray(scale)) {
scale = scale.flat().join(' ');
scale = scale.join(' ');
}
return pat.withHap((hap) => {
const isObject = typeof hap.value === 'object';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tone",
"version": "0.8.0",
"version": "0.7.1",
"description": "Tone.js API for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/transpiler",
"version": "0.8.2",
"version": "0.8.1",
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
"main": "index.mjs",
"publishConfig": {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/web",
"version": "0.8.3",
"version": "0.8.2",
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
"main": "web.mjs",
"publishConfig": {
@@ -34,10 +34,10 @@
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/mini": "workspace:*",
"@strudel.cycles/tonal": "workspace:*",
"@strudel.cycles/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*"
"@strudel.cycles/transpiler": "workspace:*"
},
"devDependencies": {
"vite": "^4.3.3"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.8.2",
"version": "0.8.1",
"description": "Web Audio helpers for Strudel",
"main": "index.mjs",
"type": "module",
+33 -65
View File
@@ -21,7 +21,7 @@ function humanFileSize(bytes, si) {
return bytes.toFixed(1) + ' ' + units[u];
}
export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resolveUrl) => {
export const getSampleBufferSource = async (s, n, note, speed, freq, bank) => {
let transpose = 0;
if (freq !== undefined && note !== undefined) {
logger('[sampler] hap has note and freq. ignoring note', 'warning');
@@ -45,9 +45,6 @@ export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resol
transpose = -midiDiff(closest); // semitones to repitch
sampleUrl = bank[closest][n % bank[closest].length];
}
if (resolveUrl) {
sampleUrl = await resolveUrl(sampleUrl);
}
let buffer = await loadBuffer(sampleUrl, ac, s, n);
if (speed < 0) {
// should this be cached?
@@ -94,46 +91,6 @@ export const getLoadedBuffer = (url) => {
return bufferCache[url];
};
export const processSampleMap = (sampleMap, fn, baseUrl = sampleMap._base || '') => {
return Object.entries(sampleMap).forEach(([key, value]) => {
if (typeof value === 'string') {
value = [value];
}
if (typeof value !== 'object') {
throw new Error('wrong sample map format for ' + key);
}
baseUrl = value._base || baseUrl;
const replaceUrl = (v) => (baseUrl + v).replace('github:', 'https://raw.githubusercontent.com/');
if (Array.isArray(value)) {
//return [key, value.map(replaceUrl)];
value = value.map(replaceUrl);
} else {
// must be object
value = Object.fromEntries(
Object.entries(value).map(([note, samples]) => {
return [note, (typeof samples === 'string' ? [samples] : samples).map(replaceUrl)];
}),
);
}
fn(key, value);
});
};
// allows adding a custom url prefix handler
// for example, it is used by the desktop app to load samples starting with '~/music'
let resourcePrefixHandlers = {};
export function registerSamplesPrefix(prefix, resolve) {
resourcePrefixHandlers[prefix] = resolve;
}
// finds a prefix handler for the given url (if any)
function getSamplesPrefixHandler(url) {
const handler = Object.entries(resourcePrefixHandlers).find(([key]) => url.startsWith(key));
if (handler) {
return handler[1];
}
return;
}
/**
* Loads a collection of samples to use with `s`
* @example
@@ -150,11 +107,6 @@ function getSamplesPrefixHandler(url) {
export const samples = async (sampleMap, baseUrl = sampleMap._base || '', options = {}) => {
if (typeof sampleMap === 'string') {
// check if custom prefix handler
const handler = getSamplesPrefixHandler(sampleMap);
if (handler) {
return handler(sampleMap);
}
if (sampleMap.startsWith('github:')) {
let [_, path] = sampleMap.split('github:');
path = path.endsWith('/') ? path.slice(0, -1) : path;
@@ -178,23 +130,39 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
});
}
const { prebake, tag } = options;
processSampleMap(
sampleMap,
(key, value) =>
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), {
type: 'sample',
samples: value,
baseUrl,
prebake,
tag,
}),
baseUrl,
);
Object.entries(sampleMap).forEach(([key, value]) => {
if (typeof value === 'string') {
value = [value];
}
if (typeof value !== 'object') {
throw new Error('wrong sample map format for ' + key);
}
baseUrl = value._base || baseUrl;
const replaceUrl = (v) => (baseUrl + v).replace('github:', 'https://raw.githubusercontent.com/');
if (Array.isArray(value)) {
//return [key, value.map(replaceUrl)];
value = value.map(replaceUrl);
} else {
// must be object
value = Object.fromEntries(
Object.entries(value).map(([note, samples]) => {
return [note, (typeof samples === 'string' ? [samples] : samples).map(replaceUrl)];
}),
);
}
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), {
type: 'sample',
samples: value,
baseUrl,
prebake,
tag,
});
});
};
const cutGroups = [];
export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
export async function onTriggerSample(t, value, onended, bank) {
const {
s,
freq,
@@ -202,7 +170,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
nudge = 0, // TODO: is this in seconds?
cut,
loop,
clip = undefined, // if 1, samples will be cut off when the hap ends
clip = 0, // if 1, samples will be cut off when the hap ends
n = 0,
note,
speed = 1, // sample playback speed
@@ -220,7 +188,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
//const soundfont = getSoundfontKey(s);
const time = t + nudge;
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank);
// asny stuff above took too long?
if (ac.currentTime > t) {
@@ -264,7 +232,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
out.disconnect();
onended();
};
const stop = (endTime, playWholeBuffer = clip === undefined) => {
const stop = (endTime, playWholeBuffer = !clip) => {
let releaseTime = endTime;
if (playWholeBuffer) {
releaseTime = t + (end - begin) * bufferDuration;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webdirt",
"version": "0.8.0",
"version": "0.7.1",
"description": "WebDirt integration for Strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/xen",
"version": "0.8.0",
"version": "0.7.1",
"description": "Xenharmonic API for strudel",
"main": "index.mjs",
"publishConfig": {
+25 -215
View File
@@ -28,10 +28,10 @@ importers:
dependency-tree:
specifier: ^9.0.0
version: 9.0.0
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
devDependencies:
'@tauri-apps/cli':
specifier: ^1.4.0
version: 1.4.0
'@vitest/ui':
specifier: ^0.28.0
version: 0.28.0
@@ -65,9 +65,6 @@ importers:
rollup-plugin-visualizer:
specifier: ^5.8.1
version: 5.9.0
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
packages/codemirror:
dependencies:
@@ -95,7 +92,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/core:
dependencies:
@@ -105,7 +102,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -130,7 +127,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/core/examples/vite-vanilla-repl-cm6:
dependencies:
@@ -158,7 +155,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.2
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/csound:
dependencies:
@@ -174,7 +171,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/embed: {}
@@ -207,7 +204,7 @@ importers:
version: link:../mini
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -226,7 +223,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/mini:
dependencies:
@@ -239,7 +236,7 @@ importers:
version: 3.0.2
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -258,7 +255,7 @@ importers:
version: 5.8.1
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/react:
dependencies:
@@ -328,7 +325,7 @@ importers:
version: 3.3.2
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/react/examples/nano-repl:
dependencies:
@@ -383,7 +380,7 @@ importers:
version: 3.3.2
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/serial:
dependencies:
@@ -393,7 +390,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/soundfonts:
dependencies:
@@ -415,7 +412,7 @@ importers:
version: 3.3.1
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/tonal:
dependencies:
@@ -434,7 +431,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -450,7 +447,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -472,7 +469,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -497,7 +494,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/web/examples/repl-example:
dependencies:
@@ -507,7 +504,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.2
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/webaudio:
dependencies:
@@ -520,7 +517,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
packages/webdirt:
dependencies:
@@ -536,7 +533,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -549,7 +546,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
version: 4.3.3(@types/node@18.11.18)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -3540,7 +3537,6 @@ packages:
/@polka/url@1.0.0-next.21:
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
dev: true
/@proload/core@0.3.3:
resolution: {integrity: sha512-7dAFWsIK84C90AMl24+N/ProHKm4iw0akcnoKjRvbfHifJZBLhaDsDus1QJmhG12lXj4e/uB/8mB/0aduCW+NQ==}
@@ -3774,113 +3770,6 @@ packages:
tailwindcss: 3.3.2
dev: false
/@tauri-apps/cli-darwin-arm64@1.4.0:
resolution: {integrity: sha512-nA/ml0SfUt6/CYLVbHmT500Y+ijqsuv5+s9EBnVXYSLVg9kbPUZJJHluEYK+xKuOj6xzyuT/+rZFMRapmJD3jQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@tauri-apps/cli-darwin-x64@1.4.0:
resolution: {integrity: sha512-ov/F6Zr+dg9B0PtRu65stFo2G0ow2TUlneqYYrkj+vA3n+moWDHfVty0raDjMLQbQt3rv3uayFMXGPMgble9OA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@tauri-apps/cli-linux-arm-gnueabihf@1.4.0:
resolution: {integrity: sha512-zwjbiMncycXDV7doovymyKD7sCg53ouAmfgpUqEBOTY3vgBi9TwijyPhJOqoG5vUVWhouNBC08akGmE4dja15g==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@tauri-apps/cli-linux-arm64-gnu@1.4.0:
resolution: {integrity: sha512-5MCBcziqXC72mMXnkZU68mutXIR6zavDxopArE2gQtK841IlE06bIgtLi0kUUhlFJk2nhPRgiDgdLbrPlyt7fw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@tauri-apps/cli-linux-arm64-musl@1.4.0:
resolution: {integrity: sha512-7J3pRB6n6uNYgIfCeKt2Oz8J7oSaz2s8GGFRRH2HPxuTHrBNCinzVYm68UhVpJrL3bnGkU0ziVZLsW/iaOGfUg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@tauri-apps/cli-linux-x64-gnu@1.4.0:
resolution: {integrity: sha512-Zh5gfAJxOv5AVWxcwuueaQ2vIAhlg0d6nZui6nMyfIJ8dbf3aZQ5ZzP38sYow5h/fbvgL+3GSQxZRBIa3c2E1w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@tauri-apps/cli-linux-x64-musl@1.4.0:
resolution: {integrity: sha512-OLAYoICU3FaYiTdBsI+lQTKnDHeMmFMXIApN0M+xGiOkoIOQcV9CConMPjgmJQ867+NHRNgUGlvBEAh9CiJodQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@tauri-apps/cli-win32-arm64-msvc@1.4.0:
resolution: {integrity: sha512-gZ05GENFbI6CB5MlOUsLlU0kZ9UtHn9riYtSXKT6MYs8HSPRffPHaHSL0WxsJweWh9nR5Hgh/TUU8uW3sYCzCg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@tauri-apps/cli-win32-ia32-msvc@1.4.0:
resolution: {integrity: sha512-JsetT/lTx/Zq98eo8T5CiRyF1nKeX04RO8JlJrI3ZOYsZpp/A5RJvMd/szQ17iOzwiHdge+tx7k2jHysR6oBlQ==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@tauri-apps/cli-win32-x64-msvc@1.4.0:
resolution: {integrity: sha512-z8Olcnwp5aYhzqUAarFjqF+oELCjuYWnB2HAJHlfsYNfDCAORY5kct3Fklz8PSsubC3U2EugWn8n42DwnThurg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@tauri-apps/cli@1.4.0:
resolution: {integrity: sha512-VXYr2i2iVFl98etQSQsqLzXgX96bnWiNZd1YADgatqwy/qecbd6Kl5ZAPB5R4ynsgE8A1gU7Fbzh7dCEQYFfmA==}
engines: {node: '>= 10'}
hasBin: true
optionalDependencies:
'@tauri-apps/cli-darwin-arm64': 1.4.0
'@tauri-apps/cli-darwin-x64': 1.4.0
'@tauri-apps/cli-linux-arm-gnueabihf': 1.4.0
'@tauri-apps/cli-linux-arm64-gnu': 1.4.0
'@tauri-apps/cli-linux-arm64-musl': 1.4.0
'@tauri-apps/cli-linux-x64-gnu': 1.4.0
'@tauri-apps/cli-linux-x64-musl': 1.4.0
'@tauri-apps/cli-win32-arm64-msvc': 1.4.0
'@tauri-apps/cli-win32-ia32-msvc': 1.4.0
'@tauri-apps/cli-win32-x64-msvc': 1.4.0
dev: true
/@tonaljs/abc-notation@4.8.0:
resolution: {integrity: sha512-JggT/DW4rMxu+q1WkeACrg52is3acp9zaW4LJmCheFi3CmLa63sy7/6mgKnlScTOvcpAyTcSytu0VbQHRXyBDA==}
dependencies:
@@ -4095,11 +3984,9 @@ packages:
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
dependencies:
'@types/chai': 4.3.4
dev: true
/@types/chai@4.3.4:
resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==}
dev: true
/@types/debug@4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
@@ -4176,7 +4063,6 @@ packages:
/@types/node@18.11.18:
resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
dev: true
/@types/node@18.16.3:
resolution: {integrity: sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==}
@@ -4619,7 +4505,7 @@ packages:
'@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.5)
'@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.5)
react-refresh: 0.14.0
vite: 4.3.3
vite: 4.3.3(@types/node@18.11.18)
transitivePeerDependencies:
- supports-color
dev: true
@@ -4630,7 +4516,6 @@ packages:
'@vitest/spy': 0.28.0
'@vitest/utils': 0.28.0
chai: 4.3.7
dev: true
/@vitest/runner@0.28.0:
resolution: {integrity: sha512-SXQO9aubp7Hg4DV4D5DP70wJ/4o0krH1gAPrSt+rhEZQbQvMaBJAHWOxEibwzLkklgoHreaMEvETFILkGQWXww==}
@@ -4638,13 +4523,11 @@ packages:
'@vitest/utils': 0.28.0
p-limit: 4.0.0
pathe: 1.1.0
dev: true
/@vitest/spy@0.28.0:
resolution: {integrity: sha512-gYBDQIP0QDvxrscl2Id0BTbzLUbuAzFiFur3eHxH9Yt5cM6YCH/kxBrSHhmXTbu92UenLx53Gwq17u5N0zGNDQ==}
dependencies:
tinyspy: 1.0.2
dev: true
/@vitest/ui@0.28.0:
resolution: {integrity: sha512-ihcVEx8t1gZXMboPGcIvoHk+PxiW5USxDMqnZOeUVIUm+XrRCtoJ96YDXdeR6MyPWeYLBPXfBWSxp5gMqoNSkw==}
@@ -4654,7 +4537,6 @@ packages:
pathe: 1.1.0
picocolors: 1.0.0
sirv: 2.0.2
dev: true
/@vitest/utils@0.28.0:
resolution: {integrity: sha512-Dt+jDZbwriZWzJ5Hi9nAUnz9IPgNb+ACE96tWiXPp/u9NmCYWIWcuNoUOYS8HQyGFz31GiNYGvaZ4ZEDjAgi1g==}
@@ -4664,7 +4546,6 @@ packages:
loupe: 2.3.6
picocolors: 1.0.0
pretty-format: 27.5.1
dev: true
/@vscode/emmet-helper@2.8.6:
resolution: {integrity: sha512-IIB8jbiKy37zN8bAIHx59YmnIelY78CGHtThnibD/d3tQOKRY83bYVi9blwmZVUZh6l9nfkYH3tvReaiNxY9EQ==}
@@ -4730,7 +4611,6 @@ packages:
/acorn-walk@8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn@8.8.2:
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
@@ -4855,7 +4735,6 @@ packages:
/ansi-styles@5.2.0:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
dev: true
/ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
@@ -5005,7 +4884,6 @@ packages:
/assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
dev: true
/ast-module-types@3.0.0:
resolution: {integrity: sha512-CMxMCOCS+4D+DkOQfuZf+vLrSEmY/7xtORwdxs4wtcC1wVgvk2MqFFTwQCFhvWsI4KPU9lcWXPI8DgRiz+xetQ==}
@@ -5275,7 +5153,6 @@ packages:
/buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: true
/buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
@@ -5326,7 +5203,6 @@ packages:
/cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
dev: true
/cacache@16.1.3:
resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==}
@@ -5456,7 +5332,6 @@ packages:
loupe: 2.3.6
pathval: 1.1.1
type-detect: 4.0.8
dev: true
/chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
@@ -5504,7 +5379,6 @@ packages:
/check-error@1.0.2:
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
dev: true
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
@@ -5583,7 +5457,6 @@ packages:
dependencies:
slice-ansi: 5.0.0
string-width: 5.1.2
dev: true
/cli-width@3.0.0:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
@@ -6063,7 +5936,6 @@ packages:
engines: {node: '>=6'}
dependencies:
type-detect: 4.0.8
dev: true
/deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
@@ -7262,7 +7134,6 @@ packages:
/get-func-name@2.0.0:
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
dev: true
/get-intrinsic@1.2.0:
resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==}
@@ -8094,7 +7965,6 @@ packages:
/is-fullwidth-code-point@4.0.0:
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
engines: {node: '>=12'}
dev: true
/is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
@@ -8738,7 +8608,6 @@ packages:
/local-pkg@0.4.3:
resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
engines: {node: '>=14'}
dev: true
/locate-path@2.0.0:
resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==}
@@ -8831,7 +8700,6 @@ packages:
resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
dependencies:
get-func-name: 2.0.0
dev: true
/lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -9726,7 +9594,6 @@ packages:
pathe: 1.1.0
pkg-types: 1.0.2
ufo: 1.1.1
dev: true
/modify-values@1.0.1:
resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==}
@@ -9772,7 +9639,6 @@ packages:
/mrmime@1.0.1:
resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
engines: {node: '>=10'}
dev: true
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
@@ -10453,7 +10319,6 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
yocto-queue: 1.0.0
dev: true
/p-locate@2.0.0:
resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==}
@@ -10706,11 +10571,9 @@ packages:
/pathe@1.1.0:
resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==}
dev: true
/pathval@1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
dev: true
/peggy@3.0.2:
resolution: {integrity: sha512-n7chtCbEoGYRwZZ0i/O3t1cPr6o+d9Xx4Zwy2LYfzv0vjchMBU0tO+qYYyvZloBPcgRgzYvALzGWHe609JjEpg==}
@@ -10787,7 +10650,6 @@ packages:
jsonc-parser: 3.2.0
mlly: 1.2.0
pathe: 1.1.0
dev: true
/pkg@5.8.1:
resolution: {integrity: sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA==}
@@ -10989,7 +10851,6 @@ packages:
ansi-regex: 5.0.1
ansi-styles: 5.2.0
react-is: 17.0.2
dev: true
/pretty-format@29.4.3:
resolution: {integrity: sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==}
@@ -11149,7 +11010,6 @@ packages:
/react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
dev: true
/react-is@18.2.0:
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
@@ -11903,7 +11763,6 @@ packages:
/siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
dev: true
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@@ -11951,7 +11810,6 @@ packages:
'@polka/url': 1.0.0-next.21
mrmime: 1.0.1
totalist: 3.0.0
dev: true
/sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
@@ -11970,7 +11828,6 @@ packages:
dependencies:
ansi-styles: 6.2.1
is-fullwidth-code-point: 4.0.0
dev: true
/smart-buffer@4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
@@ -12029,7 +11886,6 @@ packages:
dependencies:
buffer-from: 1.1.2
source-map: 0.6.1
dev: true
/source-map@0.5.7:
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
@@ -12112,7 +11968,6 @@ packages:
/stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
dev: true
/standardized-audio-context@25.3.37:
resolution: {integrity: sha512-lr0+RH/IJXYMts95oYKIJ+orTmstOZN3GXWVGmlkbMj8OLahREkRh7DhNGLYgBGDkBkhhc4ev5pYGSFN3gltHw==}
@@ -12124,7 +11979,6 @@ packages:
/std-env@3.3.2:
resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==}
dev: true
/stdopt@2.2.0:
resolution: {integrity: sha512-D/p41NgXOkcj1SeGhfXOwv9z1K6EV3sjAUY5aeepVbgEHv7DpKWLTjhjScyzMWAQCAgUQys1mjH0eArm4cjRGw==}
@@ -12295,7 +12149,6 @@ packages:
resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==}
dependencies:
acorn: 8.8.2
dev: true
/strong-log-transformer@2.1.0:
resolution: {integrity: sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==}
@@ -12582,17 +12435,14 @@ packages:
/tinybench@2.5.0:
resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==}
dev: true
/tinypool@0.3.1:
resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==}
engines: {node: '>=14.0.0'}
dev: true
/tinyspy@1.0.2:
resolution: {integrity: sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==}
engines: {node: '>=14.0.0'}
dev: true
/tmp@0.0.33:
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
@@ -12628,7 +12478,6 @@ packages:
/totalist@3.0.0:
resolution: {integrity: sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==}
engines: {node: '>=6'}
dev: true
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -12735,7 +12584,6 @@ packages:
/type-detect@4.0.8:
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
engines: {node: '>=4'}
dev: true
/type-fest@0.13.1:
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
@@ -12836,7 +12684,6 @@ packages:
/ufo@1.1.1:
resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==}
dev: true
/uglify-js@3.17.4:
resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
@@ -13212,7 +13059,6 @@ packages:
- sugarss
- supports-color
- terser
dev: true
/vite-plugin-pwa@0.14.7(vite@4.3.3)(workbox-build@6.5.4)(workbox-window@6.5.4):
resolution: {integrity: sha512-dNJaf0fYOWncmjxv9HiSa2xrSjipjff7IkYE5oIUJ2x5HKu3cXgA8LRgzOwTc5MhwyFYRSU0xyN0Phbx3NsQYw==}
@@ -13233,38 +13079,6 @@ packages:
- supports-color
dev: true
/vite@4.3.3:
resolution: {integrity: sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
'@types/node': '>= 14'
less: '*'
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
dependencies:
esbuild: 0.17.18
postcss: 8.4.23
rollup: 3.21.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/vite@4.3.3(@types/node@18.11.18):
resolution: {integrity: sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -13296,7 +13110,6 @@ packages:
rollup: 3.21.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/vite@4.3.3(@types/node@18.16.3):
resolution: {integrity: sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==}
@@ -13394,7 +13207,6 @@ packages:
- sugarss
- supports-color
- terser
dev: true
/vscode-css-languageservice@6.2.3:
resolution: {integrity: sha512-EAyhyIVHpEaf+GjtI+tVe7SekdoANfG0aubnspsQwak3Qkimn/97FpAufNyXk636ngW05pjNKAR9zyTCzo6avQ==}
@@ -13582,7 +13394,6 @@ packages:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
dev: true
/wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
@@ -13916,7 +13727,6 @@ packages:
/yocto-queue@1.0.0:
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
engines: {node: '>=12.20'}
dev: true
/zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
-3
View File
@@ -1,3 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/
-3445
View File
File diff suppressed because it is too large Load Diff
-32
View File
@@ -1,32 +0,0 @@
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
default-run = "app"
edition = "2021"
rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.4.0", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.4.0", features = ["fs-all"] }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
[profile.release]
panic = "abort"
codegen-units = 1
lto = true
opt-level = "s"
-29
View File
@@ -1,29 +0,0 @@
# @strudel.cycles/tauri
Rust source files for building native desktop apps using Tauri
## Usage
Install [Rust](https://rustup.rs/) on your system.
From the project root:
- install Strudel dependencies
```js
pnpm i
```
- to run Strudel for development
```js
pnpm tauri dev
```
- to build the binary and installer/bundle
```js
pnpm tauri build
```
The binary and installer can be found in the 'src-tauri/target/release/bundle' directory
-3
View File
@@ -1,3 +0,0 @@
fn main() {
tauri_build::build()
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

-8
View File
@@ -1,8 +0,0 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
-71
View File
@@ -1,71 +0,0 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:3000",
"distDir": "../website/dist",
"withGlobalTauri": true
},
"package": {
"productName": "Strudel",
"version": "0.1.0"
},
"tauri": {
"allowlist": {
"all": false,
"fs": {
"all": true,
"scope": ["$HOME/**", "$HOME", "$HOME/*"]
}
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "com.strudel.dev",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
"csp": null
},
"updater": {
"active": false
},
"windows": [
{
"fullscreen": false,
"height": 1200,
"resizable": true,
"title": "Strudel REPL",
"width": 1800
}
]
}
}
+76 -93
View File
@@ -1079,22 +1079,22 @@ exports[`runs examples > example "chunkBack" example index 0 1`] = `
exports[`runs examples > example "clip" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c s:piano clip:0.5 ]",
"[ 1/41/2 | note:a s:piano clip:0.5 ]",
"[ 1/2 → 3/4 | note:f s:piano clip:0.5 ]",
"[ 3/41/1 | note:e s:piano clip:0.5 ]",
"[ 1/1 → 5/4 | note:c s:piano clip:1 ]",
"[ 5/4 → 3/2 | note:a s:piano clip:1 ]",
"[ 3/2 → 7/4 | note:f s:piano clip:1 ]",
"[ 7/4 → 2/1 | note:e s:piano clip:1 ]",
"[ 2/1 → 9/4 | note:c s:piano clip:2 ]",
"[ 9/4 → 5/2 | note:a s:piano clip:2 ]",
"[ 5/2 → 11/4 | note:f s:piano clip:2 ]",
"[ 11/4 → 3/1 | note:e s:piano clip:2 ]",
"[ 3/1 → 13/4 | note:c s:piano clip:0.5 ]",
"[ 13/4 → 7/2 | note:a s:piano clip:0.5 ]",
"[ 7/2 → 15/4 | note:f s:piano clip:0.5 ]",
"[ 15/4 → 4/1 | note:e s:piano clip:0.5 ]",
"[ 0/1 → 1/5 | note:c s:piano clip:1 ]",
"[ 1/52/5 | note:a s:piano clip:1 ]",
"[ 2/5 → 3/5 | note:f s:piano clip:1 ]",
"[ 3/54/5 | note:e s:piano clip:1 ]",
"[ 1/1 → 6/5 | note:c s:piano clip:1 ]",
"[ 6/5 → 7/5 | note:a s:piano clip:1 ]",
"[ 7/5 → 8/5 | note:f s:piano clip:1 ]",
"[ 8/5 → 9/5 | note:e s:piano clip:1 ]",
"[ 2/1 → 11/5 | note:c s:piano clip:1 ]",
"[ 11/5 → 12/5 | note:a s:piano clip:1 ]",
"[ 12/5 → 13/5 | note:f s:piano clip:1 ]",
"[ 13/5 → 14/5 | note:e s:piano clip:1 ]",
"[ 3/1 → 16/5 | note:c s:piano clip:1 ]",
"[ 16/517/5 | note:a s:piano clip:1 ]",
"[ 17/5 → 18/5 | note:f s:piano clip:1 ]",
"[ 18/5 → 19/5 | note:e s:piano clip:1 ]",
]
`;
@@ -1588,42 +1588,42 @@ exports[`runs examples > example "echo" example index 0 1`] = `
exports[`runs examples > example "echoWith" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:C3 clip:0.2 ]",
"[ -3/8 ⇜ (0/1 → 1/8) | note:Bb3 clip:0.2 ]",
"[ (1/8 → 1/1) ⇝ 9/8 | note:Eb3 clip:0.2 ]",
"[ -1/4 ⇜ (0/1 → 1/4) | note:D4 clip:0.2 ]",
"[ (1/4 → 1/1) ⇝ 5/4 | note:G3 clip:0.2 ]",
"[ -1/8 ⇜ (0/1 → 3/8) | note:F4 clip:0.2 ]",
"[ (3/8 → 1/1) ⇝ 11/8 | note:Bb3 clip:0.2 ]",
"[ 1/1 → 3/2 | note:Eb3 clip:0.2 ]",
"[ 3/2 → 2/1 | note:G3 clip:0.2 ]",
"[ 1/8 ⇜ (1/1 → 9/8) | note:Eb3 clip:0.2 ]",
"[ 9/8 → 13/8 | note:G3 clip:0.2 ]",
"[ (13/8 → 2/1) ⇝ 17/8 | note:Bb3 clip:0.2 ]",
"[ 1/4 ⇜ (1/1 → 5/4) | note:G3 clip:0.2 ]",
"[ 5/4 → 7/4 | note:Bb3 clip:0.2 ]",
"[ (7/4 → 2/1) ⇝ 9/4 | note:D4 clip:0.2 ]",
"[ 3/8 ⇜ (1/1 → 11/8) | note:Bb3 clip:0.2 ]",
"[ 11/8 → 15/8 | note:D4 clip:0.2 ]",
"[ (15/8 → 2/1) ⇝ 19/8 | note:F4 clip:0.2 ]",
"[ 2/1 → 3/1 | note:C3 clip:0.2 ]",
"[ 13/8 ⇜ (2/1 → 17/8) | note:Bb3 clip:0.2 ]",
"[ (17/8 → 3/1) ⇝ 25/8 | note:Eb3 clip:0.2 ]",
"[ 7/4 ⇜ (2/1 → 9/4) | note:D4 clip:0.2 ]",
"[ (9/4 → 3/1) ⇝ 13/4 | note:G3 clip:0.2 ]",
"[ 15/8 ⇜ (2/1 → 19/8) | note:F4 clip:0.2 ]",
"[ (19/8 → 3/1) ⇝ 27/8 | note:Bb3 clip:0.2 ]",
"[ 3/1 → 7/2 | note:Eb3 clip:0.2 ]",
"[ 7/2 → 4/1 | note:G3 clip:0.2 ]",
"[ 17/8 ⇜ (3/1 → 25/8) | note:Eb3 clip:0.2 ]",
"[ 25/8 → 29/8 | note:G3 clip:0.2 ]",
"[ (29/8 → 4/1) ⇝ 33/8 | note:Bb3 clip:0.2 ]",
"[ 9/4 ⇜ (3/1 → 13/4) | note:G3 clip:0.2 ]",
"[ 13/4 → 15/4 | note:Bb3 clip:0.2 ]",
"[ (15/4 → 4/1) ⇝ 17/4 | note:D4 clip:0.2 ]",
"[ 19/8 ⇜ (3/1 → 27/8) | note:Bb3 clip:0.2 ]",
"[ 27/8 → 31/8 | note:D4 clip:0.2 ]",
"[ (31/8 → 4/1) ⇝ 35/8 | note:F4 clip:0.2 ]",
"[ 0/1 → 1/5 | note:C3 ]",
"[ -3/8 ⇜ (0/1 → 1/40) ⇝ -11/40 | note:Bb3 ]",
"[ (1/8 → 3/10) ⇝ 13/40 | note:Eb3 ]",
"[ -1/4 ⇜ (0/1 → 1/20) ⇝ -3/20 | note:D4 ]",
"[ (1/4 → 2/5) ⇝ 9/20 | note:G3 ]",
"[ -1/8 ⇜ (0/1 → 3/40) ⇝ -1/40 | note:F4 ]",
"[ (3/8 → 1/2) ⇝ 23/40 | note:Bb3 ]",
"[ 1/1 → 11/10 | note:Eb3 ]",
"[ 3/2 → 8/5 | note:G3 ]",
"[ 1/8 ⇜ (1/1 → 41/40) ⇝ 13/40 | note:Eb3 ]",
"[ 9/8 → 49/40 | note:G3 ]",
"[ (13/8 → 17/10) ⇝ 69/40 | note:Bb3 ]",
"[ 1/4 ⇜ (1/1 → 21/20) ⇝ 9/20 | note:G3 ]",
"[ 5/4 → 27/20 | note:Bb3 ]",
"[ (7/4 → 9/5) ⇝ 37/20 | note:D4 ]",
"[ 3/8 ⇜ (1/1 → 43/40) ⇝ 23/40 | note:Bb3 ]",
"[ 11/8 → 59/40 | note:D4 ]",
"[ (15/8 → 19/10) ⇝ 79/40 | note:F4 ]",
"[ 2/1 → 11/5 | note:C3 ]",
"[ 13/8 ⇜ (2/1 → 81/40) ⇝ 69/40 | note:Bb3 ]",
"[ (17/8 → 23/10) ⇝ 93/40 | note:Eb3 ]",
"[ 7/4 ⇜ (2/1 → 41/20) ⇝ 37/20 | note:D4 ]",
"[ (9/4 → 12/5) ⇝ 49/20 | note:G3 ]",
"[ 15/8 ⇜ (2/1 → 83/40) ⇝ 79/40 | note:F4 ]",
"[ (19/8 → 5/2) ⇝ 103/40 | note:Bb3 ]",
"[ 3/1 → 31/10 | note:Eb3 ]",
"[ 7/2 → 18/5 | note:G3 ]",
"[ 17/8 ⇜ (3/1 → 121/40) ⇝ 93/40 | note:Eb3 ]",
"[ 25/8 → 129/40 | note:G3 ]",
"[ (29/8 → 37/10) ⇝ 149/40 | note:Bb3 ]",
"[ 9/4 ⇜ (3/1 → 61/20) ⇝ 49/20 | note:G3 ]",
"[ 13/4 → 67/20 | note:Bb3 ]",
"[ (15/4 → 19/5) ⇝ 77/20 | note:D4 ]",
"[ 19/8 ⇜ (3/1 → 123/40) ⇝ 103/40 | note:Bb3 ]",
"[ 27/8 → 139/40 | note:D4 ]",
"[ (31/8 → 39/10) ⇝ 159/40 | note:F4 ]",
]
`;
@@ -3193,23 +3193,6 @@ exports[`runs examples > example "rarely" example index 0 1`] = `
]
`;
exports[`runs examples > example "ratio" example index 0 1`] = `
[
"[ (0/1 → 1/1) ⇝ 2/1 | freq:110 s:piano ]",
"[ (0/1 → 1/1) ⇝ 2/1 | freq:137.5 s:piano ]",
"[ (0/1 → 1/1) ⇝ 2/1 | freq:165 s:piano ]",
"[ 0/1 ⇜ (1/1 → 2/1) | freq:110 s:piano ]",
"[ 0/1 ⇜ (1/1 → 2/1) | freq:137.5 s:piano ]",
"[ 0/1 ⇜ (1/1 → 2/1) | freq:165 s:piano ]",
"[ (2/1 → 3/1) ⇝ 4/1 | freq:110 s:piano ]",
"[ (2/1 → 3/1) ⇝ 4/1 | freq:137.5 s:piano ]",
"[ (2/1 → 3/1) ⇝ 4/1 | freq:165 s:piano ]",
"[ 2/1 ⇜ (3/1 → 4/1) | freq:110 s:piano ]",
"[ 2/1 ⇜ (3/1 → 4/1) | freq:137.5 s:piano ]",
"[ 2/1 ⇜ (3/1 → 4/1) | freq:165 s:piano ]",
]
`;
exports[`runs examples > example "release" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c3 release:0 ]",
@@ -3503,30 +3486,30 @@ exports[`runs examples > example "samples" example index 1 1`] = `
exports[`runs examples > example "saw" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c3 clip:0.03125 ]",
"[ 1/4 → 1/2 | note:eb3 clip:0.09375 ]",
"[ 1/4 → 1/2 | note:g3 clip:0.09375 ]",
"[ 1/2 → 3/4 | note:g2 clip:0.15625 ]",
"[ 3/4 → 1/1 | note:g3 clip:0.21875 ]",
"[ 3/4 → 1/1 | note:bb3 clip:0.21875 ]",
"[ 1/1 → 5/4 | note:c3 clip:0.28125 ]",
"[ 5/4 → 3/2 | note:eb3 clip:0.34375 ]",
"[ 5/4 → 3/2 | note:g3 clip:0.34375 ]",
"[ 3/2 → 7/4 | note:g2 clip:0.40625 ]",
"[ 7/4 → 2/1 | note:g3 clip:0.46875 ]",
"[ 7/4 → 2/1 | note:bb3 clip:0.46875 ]",
"[ 2/1 → 9/4 | note:c3 clip:0.53125 ]",
"[ 9/4 → 5/2 | note:eb3 clip:0.59375 ]",
"[ 9/4 → 5/2 | note:g3 clip:0.59375 ]",
"[ 5/2 → 11/4 | note:g2 clip:0.65625 ]",
"[ 11/4 → 3/1 | note:g3 clip:0.71875 ]",
"[ 11/4 → 3/1 | note:bb3 clip:0.71875 ]",
"[ 3/1 → 13/4 | note:c3 clip:0.78125 ]",
"[ 13/4 → 7/2 | note:eb3 clip:0.84375 ]",
"[ 13/4 → 7/2 | note:g3 clip:0.84375 ]",
"[ 7/2 → 15/4 | note:g2 clip:0.90625 ]",
"[ 15/4 → 4/1 | note:g3 clip:0.96875 ]",
"[ 15/4 → 4/1 | note:bb3 clip:0.96875 ]",
"[ 0/1 → 1/32 | note:c3 ]",
"[ 1/4 → 9/32 | note:eb3 ]",
"[ 1/4 → 9/32 | note:g3 ]",
"[ 1/2 → 17/32 | note:g2 ]",
"[ 3/4 → 25/32 | note:g3 ]",
"[ 3/4 → 25/32 | note:bb3 ]",
"[ 1/1 → 35/32 | note:c3 ]",
"[ 5/4 → 43/32 | note:eb3 ]",
"[ 5/4 → 43/32 | note:g3 ]",
"[ 3/2 → 51/32 | note:g2 ]",
"[ 7/4 → 59/32 | note:g3 ]",
"[ 7/4 → 59/32 | note:bb3 ]",
"[ 2/1 → 69/32 | note:c3 ]",
"[ 9/4 → 77/32 | note:eb3 ]",
"[ 9/4 → 77/32 | note:g3 ]",
"[ 5/2 → 85/32 | note:g2 ]",
"[ 11/4 → 93/32 | note:g3 ]",
"[ 11/4 → 93/32 | note:bb3 ]",
"[ 3/1 → 103/32 | note:c3 ]",
"[ 13/4 → 111/32 | note:eb3 ]",
"[ 13/4 → 111/32 | note:g3 ]",
"[ 7/2 → 119/32 | note:g2 ]",
"[ 15/4 → 127/32 | note:g3 ]",
"[ 15/4 → 127/32 | note:bb3 ]",
]
`;
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -221,7 +221,7 @@ export const testCycles = {
festivalOfFingers: 16,
festivalOfFingers2: 22,
undergroundPlumber: 20,
bridgeIsOver: 8,
bridgeIsOver: 16,
goodTimes: 16,
echoPiano: 8,
sml1: 48,
+1 -1
View File
@@ -364,7 +364,7 @@ export const customTrigger = `// licensed with CC BY-NC-SA 4.0 https://creativec
// by Felix Roos
stack(
freq("55 [110,165] 110 [220,275]".mul("<1 <3/4 2/3>>").struct("x(3,8)").layer(x=>x.mul("1.006,.995"))),
freq("440(5,8)".clip(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
freq("440(5,8)".legato(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
).s("<sawtooth square>/2")
.onTrigger((t,hap,ct)=>{
const ac = Tone.getContext().rawContext;
+1 -1
View File
@@ -87,7 +87,7 @@ export const SIDEBAR: Sidebar = {
{ text: 'Signals', link: 'learn/signals' },
{ text: 'Conditional Modifiers', link: 'learn/conditional-modifiers' },
{ text: 'Accumulation', link: 'learn/accumulation' },
{ text: 'Tonal Functions', link: 'learn/tonal' },
{ text: 'Tonal Modifiers', link: 'learn/tonal' },
],
Development: [
{ text: 'REPL', link: 'technical-manual/repl' },
-8
View File
@@ -16,11 +16,3 @@
.cm-gutters {
display: none !important;
}
.cm-cursorLayer {
animation-name: inherit !important;
}
.cm-cursor {
border-left: 2px solid currentcolor !important;
}
@@ -0,0 +1,162 @@
---
title: Strudel Live Coding Workshop
layout: ../../../layouts/MainLayout.astro
---
# Strudel: Live Coding Workshop
![workshop](https://toplap-ka.de/img/2023-06-10_strudel_workshop.jpeg)
Mit: Felix Roos
Veranstaltet von TOPLAP Karlsruhe, Digitale Kunstfabrik e.V. & Die Anstoß e.V.
# Was ist Live Coding
"In live coding performance, performers create time-based works by programming them while these same works are being executed."
Roberts & Wakefield, 2016
"In einer Live Coding Performance werden künstlerische Werke mit Code kreiert, während dieselben Werke ausgeführt werden"
- Sound & Musik
- Grafiken / Visuelles
- Tanz
- Webmuster
- ...
Dieser Workshop = Musik Live Coding
![live coding](https://www.thetimes.co.uk/imageserver/image/%2Fmethode%2Ftimes%2Fprod%2Fweb%2Fbin%2F2a5b60ca-718e-11e9-a116-49ac88679a93.jpg?crop=3291%2C1851%2C573%2C722)
## TOPLAP
- gegründet 2004, heute ein weltweites Netzwerk
- Temporary Organisation for the Promotion of Live Algorithm Programming
- https://toplap.org/
- toplap karlsruhe: https://toplap-ka.de/
![alex ka](https://trxprds3.s3.amazonaws.com/uploads/2020/03/photo-by-yecto.jpg)
## Live Coding is not Software Engineering
"Live Coders are much more interested in causing problems than solving them" - Alex McLean
## Viele verschiedene Programme / Tools
- meistens frei und open source
- supercollider, orca, foxdot, sonic pi, gibber, sardine, estuary, hydra, ... tidalcycles, strudel
<div class="grid grid-cols-3">
<img
alt="orca"
src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTlZAfu-tbPdn2yzwIGIqkqMmB2HpbtKR5MHA&usqp=CAU"
/>
<img
alt="hydra"
src="https://cdn.glitch.com/9c3a06ab-0731-42ca-a1a3-3a249825d08f%2FScreen%20Shot%202018-06-24%20at%204.47.49%20PM.png?1531889983803"
/>
<img
alt="gibber"
src="https://i.vimeocdn.com/video/456734745-c00bd0c9f6a11a966a4346335f9d55b128584323d237e6d9630ab559a607dfce-d"
/>
</div>
## TidalCycles & Strudel
- TidalCycles = Computersprache für algorithmische Muster, meist für Musik verwendet
- Strudel = Version von TidalCycles für den Browser
- Keine Installation notwendig
- frei & open source
<a target="_blank" href="https://strudel.tidalcycles.org/?MCc4-cDOwIdG">
strudel.tidalcycles.org
</a>
## Videos
**Algorave Generation**
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/S2EZqikCIfY"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
**hellocatfood x yaxu**
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/Vomnc9R-7mw?start=261"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
**Heavy Lifting x Graham Dunning**
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/ca3J1cztnrc?start=719"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
**Andrew Sorensen - Five over Four**
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/FYWtlV4JV6o?start=226"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
**boggo - Live Coding Metal Djents**
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/n0rhn9-PRwE?start=226"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
**froos - Solstice Night 2022**
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/P1DDsOvcyco?start=414"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
## Strudel Demos
- [yellow magic orchestra - firecracker](https://felixroos.github.io/strudel/#Ly9jb250aW51aW5nIHRoZSB0cmFuc2NyaXB0aW9uIG9mIG9uZSBvZiBteSBmYXZvcml0ZSB0cmFja3MgaW4gZWxlY3Ryb25pYyBtdXNpYyBoaXN0b3J5CgpsZXQgZHJ1bXMgPSBzdGFjaygKICBzKCJ%2BQDMgaGggfiBoaCB%2BIH4iKS5sYXRlKC4wMikuc3BlZWQoLjgpLmdhaW4oLjIpLnJvb20oLjQpLmhjdXRvZmYoNDAwMCksCiAgcygiYmQiKSwKICBzKCJ%2BIHNkIikucm9vbSgiPDAgLjY%2BIiksCiAgcygifiBwZXJjOjEqMiB%2BIHBlcmM6MSIpLnNwZWVkKCIyIDMiKS5yb29tKC4yKS5nYWluKC4yNSkKKS5iYW5rKCdSb2xhbmRDb21wdXJoeXRobTEwMDAnKQoKbGV0IHdoaXN0bGUgPSB4PT54Lm5vdGUoKS5zKCdzaW5lJykKICAuZGVjYXkoLjEpLnN1c3RhaW4oLjIpLmdhaW4oLjIpCiAgLnBhbiguMikucm9vbSguOCkKCmxldCBiYXNzID0geD0%2BeC5hZGQoIjAsMTIsMTEuOTIiKS5ub3RlKCkKICAucygic2F3dG9vdGgsc3F1YXJlIikuY3V0b2ZmKDgwMCkKICAuZGVjYXkoLjEpLnN1c3RhaW4oLjEpLmdhaW4oLjQpCgpsZXQgYmxpcHMgPSB4PT54Lm5vdGUoKS5qdXgocmV2KQogIC5zKCdzYXd0b290aCcpLmN1dG9mZig1MDApLmdhaW4oc2luZS5yYW5nZSguMiwuOCkuc2xvdyg0KSkKICAuZGVjYXkoLjA4KS5zdXN0YWluKDApCgpsZXQgZ3Jvb3ZlQSA9ICIyIH4gMyB%2BIDQgfiB%2BIC0xIH4gLTEgMCB%2BIC0xIH4gMCB%2BIi5sYXllcigKICB4PT54LnNjYWxlKCdlNyBtaW5vcicpLmxheWVyKHdoaXN0bGUpLAogIHg9Pnguc2NhbGUoJ2UxIG1pbm9yJykubGF5ZXIoYmFzcykKKS5zdGFjaygKICAiMioxNiIuZGVncmFkZUJ5KCIxIC4yQDciKS5zY2FsZSgnZTMgbWlub3InKS5sYXllcihibGlwcykKKS5zbG93KDIpCiAgLnN0YWNrKGRydW1zKQoKbGV0IG1lbG9keUEgPSBjYXQoCiAgIjYgWzcgNl0gNCAzIDIgMCAtMSAwIiwKICAiWzkgMTBdIFs5IDddIDYgNCAzIDQgNiA3Ii5zdWIoNyksCiAgIns2IDcgNiA0IDMgfn0lMTYiLnN1Yig3KS5yZXN0YXJ0KCJ4IiksCiAgIjMqMiA0KjIgNioyIDcqMiIuc3ViKDcpCikuc2xvdygyKS5hZGQoIjAiKS5zY2FsZSgnZTQgbWlub3InKS5ub3RlKCkKLmxheWVyKAogICAgeD0%2BeC5zKCJ4eWxvcGhvbmVfaGFyZF9mZiIpLmdhaW4oMS4yKSwKICAgIHg9Pngucygic2F3dG9vdGgiKS5nYWluKC40KS5kZWNheSguMSkuc3VzdGFpbigwLjIpLmFkZChub3RlKCIxMiwxMi4wNSIpKQogICkubXVsKGdhaW4oLjUpKQoKbGV0IG1lbG9keUIgPSAiWzBAMiB%2BIDBANCAxIDJAMiB%2BIDJANV0vNCIuc3ViKCIwLDMiKQogIC5zY2FsZSgnZzQgbWFqb3InKS5ub3RlKCkubGF5ZXIoCiAgICB4PT54LnMoInJlY29yZGVyX2FsdG9fc3VzIikuY2xpcCgxKS5zaGFwZSguOCkuZ2FpbiguNzUpLAogICAgeD0%2BeC5zKCJzcXVhcmUiKS5nYWluKC4xKS5hZGQobm90ZSgxMikpCiAgKQogIC5yb29tKC42KS5yZWxlYXNlKC4yKQoKCmZ1bmN0aW9uIGFycmFuZ2UoLi4uc2VjdGlvbnMpIHsKICBsZXQgdG90YWwgPSBzZWN0aW9ucy5yZWR1Y2UoKHN1bSwgW2N5Y2xlc10pID0%2BIHN1bSArIGN5Y2xlcywgMCkKICBzZWN0aW9ucyA9IHNlY3Rpb25zLm1hcCgoW2N5Y2xlcywgc2VjdGlvbl0pID0%2BIFtjeWNsZXMsIHNlY3Rpb24uZmFzdChjeWNsZXMpXSkKICByZXR1cm4gdGltZUNhdCguLi5zZWN0aW9ucykuc2xvdyh0b3RhbCkKfQoKZ3Jvb3ZlQS5zdGFjaygKICBhcnJhbmdlKFs4LCBzaWxlbmNlXSwgWzgsIG1lbG9keUFdLCBbNCwgbWVsb2R5Ql0sIFs4LCBtZWxvZHlBXSwgWzQsIG1lbG9keUJdKQopCg%3D%3D)
- [reliefpfeier](https://felixroos.github.io/strudel/#Ly8gdGl0bGU6IHJlbGllZnBmZWlsZXIKcygiPHBlcmMgbWlzYyByaW0oMyw4KSBvaD4iKS5iYW5rKCdZYW1haGFURzMzJykuZW5kKCI8MUAzIC4wMj4iKQoubigiPDAgMSAyIDM%2BIi5vdXRzaWRlKDQsIHBhbGluZHJvbWUpKQoub2ZmKCI8MSAyIDMgND4iLmRpdigiPDIgMyA0IDY%2BLzQiKSwgeD0%2BeC5zcGVlZCgiPDIgMyA0PiIpKQogIAouY2h1bmsoNCwgcGx5KCI8MiAzIDQ%2BIikpCi5sYXN0T2YoOCwgc2VnbWVudCgiPDMgNiA4PiIpKQogIAoubGF5ZXIoCiAgeD0%2BeC5jdXRvZmYocGVybGluLnJhbmdlKDEwMDAsMjAwMDApKSwKICB4PT54LmJhbmsoJycpLnNsb3coMikucygncGlhbm8nKS5nYWluKC42KS5kZWdyYWRlKCkubXVsKHNwZWVkKC0xKSkKICAgIC5iYW5kZihzaW5lLnJhbmdlKDIwMDAsMTAwMDApLnNsb3coNykpLnJlc29uYW5jZSgxMikuYXR0YWNrKC44KQogICAgLnBhbihyYW5kKSwKKQogIAoubGFzdE9mKDYsIHg9PnguY3J1c2goIjgiKSkKLmdhaW4oc2F3LnJhbmdlKC40LC45KSkKLm91dHNpZGUoNCwganV4QnkoLjUsIHJldikpCi5ub3RlKCIwIDUgNCBbMiw3XSIuc2NhbGUoJ0cxIG1pbm9yJykpCi5yb29tKC40KS5mYXN0KDIvMyk%3D)
- [insect schneckno](https://felixroos.github.io/strudel/#Ly8gYWxzbyBoYXZpbmcgYSBmdW4gdGltZSB3aXRoICNzaGFiZGEKLy90aXRsZTogaW5zZWN0IHNjaG5lY2tubwoKYXdhaXQgc2FtcGxlcygnaHR0cHM6Ly9zaGFiZGEubmRyZS5nci9iYXNzOjQsaGloYXQ6NCxyaW1zaG90OjIuanNvbj9zdHJ1ZGVsPTEnKQoKc3RhY2soCiAgbigiezEgMiAzfSUxMiIpLnNwZWVkKHNpbmUucmFuZ2UoMSwxLjIpKQogIC5zKCJoaWhhdCIpLmVuZChzYXcucmFuZ2V4KC4wNCwuMTUpLnNsb3coOCkubGF0ZSguMjUpKQogIC5nYWluKCIuNSAuOSIuZmFzdCgyKSkKICAuYnBmKHNpbmUucmFuZ2UoNDAwMCwxMDAwMCkuc2xvdyg0KSkKICAucm9vbSguOCkuZ2FpbiguNikucGFuKHNpbmUucmFuZ2UoLjMsLjYpLnNsb3coNykpCiAgLAogIG4oIjAgWzJAMyAxQDJdIDMqOCAxIikucygiYmFzcyIpLmN1dCgxKQogICwKICBzKCJiZCoyIikuc3BlZWQoLjYpLmJhbmsoJ1JvbGFuZFRSOTA5JykKICAsCiAgcygifiBbY3Ascmltc2hvdCo8MSAyPl0iKS5vZmYoMS84LHg9Pnguc3BlZWQoIjw0IC0yPiIpKS5zbG93KDIpCiAgLAogIG5vdGUoInswIDQgNSozIDIgMSAtMSoyIC0yfSUyIi5zY2FsZSgnZzIgZG9yaWFuJykpLmxhdGUoLjI4KQogIC5kZWxheSguNSkuZGVsYXl0aW1lKC4xNjYpLnJvb20oLjUpCiAgLnMoIlJoeXRobUFjZV9wZXJjOjEiKS5vZmYoMS8yNCwgeD0+eC5hZGQobm90ZSgxMikpKS5zbG93KDIpCiAgLmp1eEJ5KC43NSwgcmV2KS5scGYocGVybGluLnJhbmdlKDgwMCw4MDAwKSkubHBxKDExKQogIC5nYWluKHNpbmUucmFuZ2UoMCwuOCkuc2xvdygxMikpCikuZmFzdCgzLzQp)
- [hip hop with sampling](https://felixroos.github.io/strudel/#Ly8gYW5vdGhlciBzdHVtYmxpbmcgYmVhdCB3aXRoIHF1aW50dXBsZXQgc3dpbmcgYW5kIHNvbWUgc2xpY2VkIHN0cmluZyBpbnN0cnVtZW50cwphd2FpdCBzYW1wbGVzKCdnaXRodWI6ZmVsaXhyb29zL3NhbXBsZXMvbWFpbicpCmF3YWl0IHNhbXBsZXMoeyAKICBqel9ndHI6J2h0dHBzOi8vY2RuLmZyZWVzb3VuZC5vcmcvcHJldmlld3MvNTkvNTk1MDBfNTM4MzgzLWhxLm1wMycsIAogIGp6X2Jhc3M6J2h0dHBzOi8vY2RuLmZyZWVzb3VuZC5vcmcvcHJldmlld3MvMTYvMTYxNzFfMTg4MTEtaHEubXAzJ30KKQpQYXR0ZXJuLnByb3RvdHlwZS5zbGljZSA9IGZ1bmN0aW9uKHNsaWNlcywgY29udHJvbCkgewogIHJldHVybiBjb250cm9sLmZtYXAoaSA9PiB0aGlzLmJlZ2luKGkvc2xpY2VzKS5lbmQoKGkrMSkvc2xpY2VzKSkub3V0ZXJKb2luKCkKfQoKc3RhY2soCiAgLy8gZ3VpdGFyIHNsaWNlcwogIHMoJ2p6X2d0cicpLnNsaWNlKDgsICJbM0AzIDNAMl0qMiAyKlszIDZdIDEqNCAwKjIiKS5zcGVlZCgiMSAtMiIpLmxhdGUoLjAwOCkKICAuc2xvdyg0KS5jdXQoMSkucmVsZWFzZSguMikuYXR0YWNrKCIwLjA1IDAuMjUiKS5wYW4oc2luZS5yYW5nZSguMiwuOCkuc2xvdyg0KSkKICAubXVsKHNwZWVkKDQvMykpLmdhaW4ocmFuZC5yYW5nZSguMywuNikpCiAgLmxwZihzaW5lLnJhbmdlKDUwMCw1MDAwKS5zbG93KDgpKS5scHEoMTIpCiAgLCAvLyBiYXNzIHNsaWNlcwogIHMoJ2p6X2Jhc3MnKS5zbGljZSg4LCAiWzAgWzdAMyA3QDJdXSBbMSA2KjhdIDIgPDUgNj4qMiIpLmRlY2F5KC4yKS5zdXN0YWluKC41KQogIC5hZGQoYmVnaW4oLjAxMikuZW5kKC4wKSkubXVsKGJlZ2luKC44OCkuZW5kKC44OSkpLnJlbGVhc2UoLjEpCiAgLnNsb3coNCkuY3V0KDIpLm5vdGUoImQyIikKICAsIC8vIGtpY2sgCiAgcygiW2JkOjAoPDIgMT4sNSw8MyAwPiksfiA8fiBbfkAzIGJkQDJdPl0gfiIpLmJhbmsoJ1lhbWFoYVJYNScpCiAgLmdhaW4ocmFuZC5yYW5nZSguNSwuOSkpLm1hc2soIjwxQDYgMEAyPiIpCiAgLCAvLyBzbmFyZQogIHMoIn4gPHNkIHNkPiIpLmJhbmsoJ1lhbWFoYVJYNScpLmdhaW4oLjgpLnJvb20oIjwwIC4xPiIpLnNwZWVkKCI8LjkgMT4iKQogIC5lbmQoIjwuMSAuMj4iKS5yZWxlYXNlKC4xKQogICwgLy8gaGloYXQKICBzKCJoaEAzIGhoQDIiLmZhc3QoMikpLm4oMSkuYmFuaygnS29yZ00xJykuc3BlZWQoLjgpLmVuZCgwKS5yZWxlYXNlKC4wNSkuZ2FpbiguNSkKICAub2ZmKDEvNSwgeD0%2BeC5tdWwoc3BlZWQoMS4yKSkuZ2FpbiguMSkuZGVncmFkZSgpKQogICwgLy8geHlsb3Bob25lCiAgbm90ZSgiPFtlNEAzIGEzXSBkNCB%2BQDI%2BLzIiLmFkZCgyNCkpCiAgLnMoInh5bG8iKS5nYWluKC40KS5sYXRlKDQpLmNsaXAoMSkKICAuaHBmKDUwMCkuaHBxKDE2KQogIC5yb29tKDEpLmRlbGF5KC41KS5kZWxheXRpbWUoLjExKS5kZWxheWZlZWRiYWNrKC45KQogIC5tYXNrKCI8MCAxPi84IikKKS5lYXJseSgyKQouZmFzdCgyLzMp)
- [fuzzy planet](https://felixroos.github.io/strudel/#Ly8gc3BlZWRydW4gbm8gMywgdGhpcyB0aW1lIGEgbW9yZSBmdXp6eSB0eXBlIG9mIGdlbnJlCgphd2FpdCBzYW1wbGVzKCdnaXRodWI6ZmVsaXhyb29zL3NhbXBsZXMvbWFpbicpCmF3YWl0IHNhbXBsZXMoJ2dpdGh1Yjp0aWRhbGN5Y2xlcy9EaXJ0LVNhbXBsZXMvbWFzdGVyJykKCnNhbXBsZXMoeyBwbHVjazoge2U0OiAnaHR0cHM6Ly9jZG4uZnJlZXNvdW5kLm9yZy9wcmV2aWV3cy81OTIvNTkyNDQyXzEwODcwODgwLWxxLm1wMyd9IH0pCgphZGRWb2ljaW5ncygnY29va2llJywgewogIDc6IFsnM00gN20gOU0gMTJQIDE1UCcsICc3bSAxME0gMTNNIDE2TSAxOVAnXSwKICAnXjcnOiBbJzNNIDZNIDlNIDEyUCAxNE0nLCAnN00gMTBNIDEzTSAxNk0gMTlQJ10sCiAgbTc6IFsnOFAgMTFQIDE0bSAxN20gMTlQJywgJzVQIDhQIDExUCAxNG0gMTdtJ10sCiAgbTdiNTogWyczbSA1ZCA4UCAxMVAgMTRtJywgJzVkIDhQIDExUCAxNG0gMTdtJ10sCiAgbzc6IFsnM20gNk0gOU0gMTFBIDE1UCddLAogICc3YWx0JzogWyczTSA3bSAxMG0gMTNtIDE1UCddLAogICc3IzExJzogWyc3bSAxMG0gMTNtIDE1UCAxN20nXSwKfSwgWydDMycsICdDNiddKQoKc3RhY2soCiAgIjxDbTchMiBEbTdiNSBDIzcjMTE%2BLzQiLmxheWVyKAogICAgeD0%2BeC52b2ljaW5ncygnY29va2llJykubm90ZSgpLnMoJ3BsdWNrJykuY2xpcCgxKQogICAgICAuYXJwKCJbMyAyIDEgMEAyMV0gWzAgMSAyIDMgNEAyMV0iLmxlZ2F0byg0MCkuZmFzdCgyKSkuZWFybHkoMS80OCkKICAgICAgLmxwZihzaW5lLnJhbmdlKDQwMCw0MDAwKS5zbG93KDE2KSkuZ2FpbigxLjIpLnBhbiguMjUpLAogICAgeD0%2BeC5yb290Tm90ZXMoMikuYWRkLm91dCgiWzBAMyAwXSBbNyAxMiB%2BIDEyXSB%2BIH4iLnNsb3coMikpLm5vdGUoKQogICAgICAucygnamF6emJhc3MnKS5jbGlwKDEpLmdhaW4oLjgpLAogICAgeD0%2BeC52b2ljaW5ncygnY29va2llJykuYWRkKCItLjEsLjEiKS5ub3RlKCkKICAgICAgLnMoJ3NpbmUnKS5hdHRhY2soLjUpLmRlY2F5KC4yKS5zdXN0YWluKC41KS5nYWluKC4xKS5wYW4oLjc1KSwKICAgIHg9Pngudm9pY2luZ3MoJ2Nvb2tpZScpLmFkZCgwKS5hcnAoInsxIFsyIDNdIDAhM30lNCIpLm5vdGUoKQogICAgICAucygnc3F1YXJlJykubHBmKHNpbmUucmFuZ2UoNDAwLDIwMDApLnNsb3coMzYpKS5kZWdyYWRlKCkKICAgICAgLmRlY2F5KC4xKS5zdXN0YWluKDApLm9mZigxLzYsIGFkZChub3RlKDEyKSkpLnJvb20oLjgpLmdhaW4oLjYpCiAgKSwKICBzdGFjaygKICAgIHMoImJkIFt%2BIDx%2BIGJkPl0iKS5uKDIpLnNwZWVkKC41KS5iYW5rKCdSb2xhbmRUUjkwOScpLmdhaW4oIi44IC41IiksCiAgICBzKCJiZCoyIi5sYXRlKC4yNSkpLnNwZWVkKC41KS5iYW5rKCdVbml2b3hNaWNyb1JoeXRobWVyMTInKS5ocGYoMjAwMCkucm9vbSguMjUpLAogICAgcygifiBzZCIpLnNwZWVkKC4yNSkuYmFuaygnVW5pdm94TWljcm9SaHl0aG1lcjEyJykucm9vbSguNSkuZW5kKCI8LjA0QDMgLjg%2BIikKICApCikKLnNsb3coMikuY3BtKDkyKQ%3D%3D)
- [wutang](https://felixroos.github.io/strudel/#YXdhaXQgc2FtcGxlcygnZ2l0aHViOmZlbGl4cm9vcy9zYW1wbGVzL21haW4nKQphd2FpdCBzYW1wbGVzKCdnaXRodWI6eWF4dS9jbGVhbi1icmVha3MvbWFpbi8nKQoKY29uc3QgcnVuID0gKG4pID0%2BIHNhdy5yYW5nZSgwLG4pLmZsb29yKCkuc2VnbWVudChuKQoKc3RhY2soCiAgbihydW4oOCkuc2xvdygyKS5hZGQoMSkpLnMoInJlbmUiKQogIC5jbGlwKDEpLmxhc3RPZig0LHJldikKICAuc29tZXRpbWVzKHBseSgyKSkKICAubGFzdE9mKDgsIHg9Pnguc3BlZWQoMS41KSksCiAgcygiYWN0aW9uIikubG9vcEF0KDIpLmNob3AoOCkuZ2FpbiguNCkKICAubHBmKHNpbmUucmFuZ2UoMTUwMCw4MDAwKS5zbG93KDgpKS5scHEoMTIpCiAgLnJhcmVseSh4PT54LnNwZWVkKCIxLDQiKS5tdWwoZ2FpbiguNSkpKQogIC5hbG1vc3ROZXZlcih4PT54LnBseSgiNCIpKQopCiAgCg%3D%3D)
- [african groove](https://felixroos.github.io/strudel/#YXdhaXQgc2FtcGxlcygnZ2l0aHViOmZlbGl4cm9vcy9zYW1wbGVzL21haW4nKQoKc3RhY2soCiAgcygiYmQgc2QsaGgoPDMgNCAzIDU%2BLDgsPDAgMSAwIFswIDFdPiksPH5AMyBvaD4iKS5iYW5rKCdwcmVzaWRlbnQnKSwKICBzdGFjaygKICAgIG5vdGUoIntjIGViIDxnIGEgYmIgYT4gZWJ9JTMqPDFAMyAyPiIuYWRkKDEyKSkucygiZ21fZWxlY3RyaWNfZ3VpdGFyX211dGVkOjAiKQogICAgLmxwZihjb3NpbmUucmFuZ2UoMjAwLDgwMDApLnNsb3coMTYpKS5wYW4oc2luZS5zbG93KDgpKSwKICAgIG5vdGUoIn4gW1tjNCA8ZzMgYTMgYmIzIGEzPiBlYjNdIH5ANF0iLmFkZCgxMikpLnMoImdtX2VsZWN0cmljX2d1aXRhcl9jbGVhbjowIikucmVsZWFzZSguNSkuZ2FpbiguMzUpCiAgICAuYnBmKHBlcmxpbi5yYW5nZSgxMDAwLDUwMDApKS5zaGFwZSguNSksCiAgICBub3RlKCI8W3tjMioyIGcyIFtlYjIgZWIzXX0lMiB%2BXSBbZWIyIH5dKDMsOCk%2BIikucygiZ21fc2xhcF9iYXNzXzE6MSIpLm9mZiguMjUsIGFkZChub3RlKDEyKSkpLmxwZig4MDApLAogICAgbm90ZSgiYzUqOCIpLmxwZihzaW5lLnJhbmdlKDIwMCw4MDAwKS5zbG93KDE2KSkubHBxKDUpLnMoJ3h5bG8nKS5kZWdyYWRlKCkuanV4KHJldikuZ2FpbiguNCkKICApLnN1Yihub3RlKCI8NUAxNiA3QDg%2BIikpCikuZmFzdCg0LzUpLnJvb20oLjUp)
- [yaxu groove](https://felixroos.github.io/strudel/#c2V0Y3BzKDEuMSkKCnMoIntiZCB%2BIHJpbSB%2BLCB%2BIH4gaGh9IikuYmFuaygnUm9sYW5kVFI5MDknKQogIC5jaHVuayg0LCBmYXN0KDIpKQogIC5qdXgocmV2KQogIC5vZmYoLjI1LCBtdWwoc3BlZWQoLjgpKSkKICAuc3VwZXJpbXBvc2UoCiAgICB4PT54LnZvd2VsKCI8YSA8ZSBpPj4vNCIpLnJvb20oMikuc2xvdygyKS5sYXRlKC4yNSkuY29sb3IoIiMwMGZmMDA1MCIpCiAgKQogIC5sYXN0T2YoOCwgbXVsKHNwZWVkKC0yKSkpCiAgLmNvbG9yKCJbIzAwMDBmZjUwICNmZmZmMDAyMF0qMyIpCiAgLnB1bmNoY2FyZCgpCgo%3D)
- [dubby techno](https://felixroos.github.io/strudel/#Ly8gaHR0cHM6Ly9zdHJ1ZGVsLnRpZGFsY3ljbGVzLm9yZy8%2FTUNjNC1jRE93SWRHCi8vIGZyb29zIC0gY3J1bWJsaW5nIGR1bXBsaW5ncwoKbGV0IGNob3JkcyA9ICI8RG0hNyA8QSEzIDxGIEM%2BPj4iCgpzdGFjaygKICBzKCI8fiExNiBiZCoyITQwPix%2BIDx%2BITMyIHNkOjIhMjQ%2BLCA8fiEyOCBbfiBoaDoxXSoyITI4PiIpCiAgLmJhbmsoJ1JvbGFuZFRSOTA5JyksCiAgCiAgcygifiBoaCwgfiByaW0gfkAyIikKICAuYmFuaygnUm9sYW5kVFI5MDknKQogIC5qdXhCeSguNSwgcmV2KQogIC5zZXQubWl4KHNwZWVkKCIxIFs8MSAyPiA8LjkgPC43NSAuOD4%2BXSIpKQogIC5sYXN0T2YoOCwgZmFzdCgiMSBbPDMgND4gMl0iKSkuZ2FpbiguOCkKICAubHBmKHNpbmUucmFuZ2UoODAwLCA4MDAwKS5zbG93KDE1KS5sYXRlKDUpKQogIC5nYWluKC41KSwKICAKICBjaG9yZHMudm9pY2luZ3MoJ3RyaWFkcycpLm5vdGUoKQogIC5hcnAoIjAgMSAyIDEiKS5hZGQobm90ZSgiPDAgMTI%2BLzE2IikpCiAgLnJhcmVseSh4PT54LmFkZChub3RlKCIxMiIpKSkKICAucygncGlhbm8nKS5jbGlwKDEpCiAgLmRlY2F5KHBlcmxpbi5yYW5nZSguMSwuNSkpLnN1c3RhaW4oMCkKICAucm9vbShzaW5lLnJhbmdlKDAsIDQpLnNsb3coMTYpKQogIC5vZmYoMS84LCBhZGQobm90ZSgxMikpKQogIC5scGYoY29zaW5lLnJhbmdlKDIwMCwyMDAwKS5zbG93KDMyKSkKICAubHBxKDgpLmp1eChyZXYpLmdhaW4oLjQpLAogIAogIGNob3Jkcy52b2ljaW5ncygndHJpYWRzJykKICAubm90ZSgpLmF0dGFjaygiPC4wMSAuMDg%2BLzMyIikuc3VzdGFpbigwKS5zdHJ1Y3QoIlt%2BIHhdKjIiKQogIC5zKCJzcXVhcmUsdHJpYW5nbGUiKQogIC5scGYocGVybGluLnJhbmdlKDQwMCw4MDApLnNsb3coMS4yKSkKICAucmFyZWx5KG9mZigxLzgseD0%2BeC5hZGQobm90ZSgiMTIiKSkubHBmKDgwMCkpKQogIC5yb29tKC41KS5nYWluKC42KSwKICAKICBjaG9yZHMucm9vdE5vdGVzKDIpLmFkZCgiMCwuMSIpLm5vdGUoKS5zKCdzYXd0b290aCcpCiAgLmRlY2F5KC4xKS5zdXN0YWluKDApCiAgLmdhaW4oIlsuMyAxXSoyIikKICAucGx5KDgpLmxwZihzaW5lLnJhbmdlKDgwMCwyMDAwKS5zbG93KDgpKQogIC5scHEoMTApLm11bChnYWluKC44KSkKKQogIC5sYXRlKCJbMCAuMDJdKjQiKQogIC5jcG0oMTAwLzIp)
@@ -139,10 +139,6 @@ This group of functions allows to modify the value of events.
<JsDoc client:idle name="Pattern.range2" h={0} />
## ratio
<JsDoc client:idle name="Pattern.ratio" h={0} />
# Custom Parameters
You can also create your own parameters:
+1 -1
View File
@@ -32,4 +32,4 @@ import { JsDoc } from '../../docs/JsDoc';
<JsDoc client:idle name="stut" h={0} />
There are also [Tonal Functions](/learn/tonal).
There are also [Tonal Modifiers](/learn/tonal).
+1 -1
View File
@@ -1,3 +1,3 @@
<script is:inline>
window.location.pathname = `/workshop/getting-started`;
window.location.pathname = `/learn/getting-started`;
</script>
@@ -38,10 +38,6 @@ Some of these have equivalent operators in the Mini Notation:
<JsDoc client:idle name="Pattern.legato" h={0} />
## clip
<JsDoc client:idle name="clip" h={0} />
## euclid
<JsDoc client:idle name="Pattern.euclid" h={0} />
+18 -18
View File
@@ -1,29 +1,14 @@
---
title: Tonal Functions
title: Tonal API
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Tonal Functions
# Tonal API
These functions use [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations.
### scale(name)
Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like scaleTranspose.
<MiniRepl
client:idle
tune={`"0 2 4 6 4 2"
.scale("C2:major C2:minor").slow(2))
.note().s("piano")`}
/>
Note that the scale root is octaved here. You can also omit the octave, then index zero will default to octave 3.
All the available scale names can be found [here](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts).
The Tonal API, uses [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations.
### transpose(semitones)
@@ -37,6 +22,21 @@ Instead of numbers, scientific interval notation can be used as well:
<MiniRepl client:only="react" tune={`"c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()`} />
### scale(name)
Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like scaleTranspose.
<MiniRepl
client:idle
tune={`"0 2 4 6 4 2"
.scale(seq('C2 major', 'C2 minor').slow(2))
.note()`}
/>
Note that the scale root is octaved here. You can also omit the octave, then index zero will default to octave 3.
All the available scale names can be found [here](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts).
### scaleTranspose(steps)
Transposes notes inside the scale by the number of steps:
+1 -1
View File
@@ -1,3 +1,3 @@
This section introduces you to Strudel in a technical sense. If you just want to _use_ Strudel, have a look at the [Tutorial](/workshop/getting-started).
This section introduces you to Strudel in a technical sense. If you just want to _use_ Strudel, have a look at the [Tutorial](/learn/getting-started).
TODO
@@ -7,7 +7,7 @@ import { MiniRepl } from '../../docs/MiniRepl';
# Strudel Packages
The [strudel repo](https://github.com/tidalcycles/strudel) is organized as a monorepo, containing multiple npm packages.
The [strudel repo](github.com/tidalcycles/strudel) is organized as a monorepo, containing multiple npm packages.
The purpose of the multiple packages is to
- organize the codebase into more modular, encapsulated pieces
+1 -1
View File
@@ -1,3 +1,3 @@
<script is:inline>
window.location.pathname = `/workshop/getting-started`;
window.location.pathname = `/learn/getting-started`;
</script>
@@ -80,6 +80,7 @@ These letter combinations stand for different parts of a drum set:
- `bd` = **b**ass **d**rum
- `sd` = **s**nare **d**rum
- `sd` = **sd**are
- `rim` = **rim**shot
- `hh` = **h**i**h**at
- `oh` = **o**pen **h**ihat
-75
View File
@@ -1,75 +0,0 @@
import { Fragment, useEffect } from 'react';
import React, { useMemo, useState } from 'react';
import { isAudioFile, readDir, dir, playFile } from './files.mjs';
export function FilesTab() {
const [path, setPath] = useState([]);
useEffect(() => {
let init = false;
readDir('', { dir, recursive: true })
.then((children) => setPath([{ name: '~/music', children }]))
.catch((err) => {
console.log('error loadin files', err);
});
return () => {
init = true;
};
}, []);
const current = useMemo(() => path[path.length - 1], [path]);
const subpath = useMemo(
() =>
path
.slice(1)
.map((p) => p.name)
.join('/'),
[path],
);
const folders = useMemo(() => current?.children.filter((e) => !!e.children), [current]);
const files = useMemo(() => current?.children.filter((e) => !e.children && isAudioFile(e.name)), [current]);
const select = (e) => setPath((p) => p.concat([e]));
return (
<div className="px-4 flex flex-col h-full">
<div className="flex justify-between font-mono pb-1">
<div>
<span>{`samples('`}</span>
{path?.map((p, i) => {
if (i < path.length - 1) {
return (
<Fragment key={i}>
<span className="cursor-pointer underline" onClick={() => setPath((p) => p.slice(0, i + 1))}>
{p.name}
</span>
<span>/</span>
</Fragment>
);
} else {
return (
<span className="cursor-pointer underline" key={i}>
{p.name}
</span>
);
}
})}
<span>{`')`}</span>
</div>
</div>
<div className="overflow-auto">
{!folders?.length && !files?.length && <span className="text-gray-500">Nothing here</span>}
{folders?.map((e, i) => (
<div className="cursor-pointer" key={i} onClick={() => select(e)}>
{e.name}
</div>
))}
{files?.map((e, i) => (
<div
className="text-gray-500 cursor-pointer select-none"
key={i}
onClick={async () => playFile(`${subpath}/${e.name}`)}
>
{e.name}
</div>
))}
</div>
</div>
);
}
+3 -27
View File
@@ -9,9 +9,6 @@ import { themes } from './themes.mjs';
import { useSettings, settingsMap, setActiveFooter, defaultSettings } from '../settings.mjs';
import { getAudioContext, soundMap } from '@strudel.cycles/webaudio';
import { useStore } from '@nanostores/react';
import { FilesTab } from './FilesTab';
const TAURI = window.__TAURI__;
export function Footer({ context }) {
const footerContent = useRef();
@@ -80,7 +77,6 @@ export function Footer({ context }) {
<FooterTab name="console" />
<FooterTab name="reference" />
<FooterTab name="settings" />
{TAURI && <FooterTab name="files" />}
</div>
{activeFooter !== '' && (
<button onClick={() => setActiveFooter('')} className="text-foreground" aria-label="Close Panel">
@@ -95,7 +91,6 @@ export function Footer({ context }) {
{activeFooter === 'sounds' && <SoundsTab />}
{activeFooter === 'reference' && <Reference />}
{activeFooter === 'settings' && <SettingsTab scheduler={context.scheduler} />}
{activeFooter === 'files' && <FilesTab />}
</div>
)}
</footer>
@@ -145,7 +140,7 @@ function WelcomeTab() {
</p>
<p>
To learn more about what this all means, check out the{' '}
<a href="./workshop/getting-started" target="_blank">
<a href="./learn/getting-started" target="_blank">
interactive tutorial
</a>
. Also feel free to join the{' '}
@@ -369,16 +364,7 @@ const fontFamilyOptions = {
};
function SettingsTab({ scheduler }) {
const {
theme,
keybindings,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isLineWrappingEnabled,
fontSize,
fontFamily,
} = useSettings();
const { theme, keybindings, isLineNumbersDisplayed, fontSize, fontFamily } = useSettings();
return (
<div className="text-foreground p-4 space-y-4">
{/* <FormItem label="Tempo">
@@ -420,7 +406,7 @@ function SettingsTab({ scheduler }) {
/>
</FormItem>
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormItem label="Keybindings">
<ButtonGroup
value={keybindings}
@@ -433,16 +419,6 @@ function SettingsTab({ scheduler }) {
onChange={(cbEvent) => settingsMap.setKey('isLineNumbersDisplayed', cbEvent.target.checked)}
value={isLineNumbersDisplayed}
/>
<Checkbox
label="Enable auto-completion"
onChange={(cbEvent) => settingsMap.setKey('isAutoCompletionEnabled', cbEvent.target.checked)}
value={isAutoCompletionEnabled}
/>
<Checkbox
label="Enable line wrapping"
onChange={(cbEvent) => settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)}
value={isLineWrappingEnabled}
/>
</div>
<FormItem label="Reset Settings">
<button
-8
View File
@@ -45,11 +45,3 @@
#code .cm-theme-light {
width: 100%;
}
#code .cm-cursorLayer {
animation-name: inherit !important;
}
#code .cm-cursor {
border-left: 2px solid currentcolor !important;
}
+2 -14
View File
@@ -19,7 +19,6 @@ import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import { themes } from './themes.mjs';
import { settingsMap, useSettings, setLatestCode } from '../settings.mjs';
import Loader from './Loader';
import { settingPatterns } from '../settings.mjs';
const { latestCode } = settingsMap.get();
@@ -46,7 +45,6 @@ const modules = [
const modulesLoading = evalScope(
controls, // sadly, this cannot be exported from core direclty
settingPatterns,
...modules,
);
@@ -107,15 +105,7 @@ export function Repl({ embedded = false }) {
const [lastShared, setLastShared] = useState();
const [pending, setPending] = useState(true);
const {
theme,
keybindings,
fontSize,
fontFamily,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isLineWrappingEnabled,
} = useSettings();
const { theme, keybindings, fontSize, fontFamily, isLineNumbersDisplayed } = useSettings();
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
useStrudel({
@@ -167,7 +157,7 @@ export function Repl({ embedded = false }) {
e.preventDefault();
flash(view);
await activateCode();
} else if (e.key === '.' || e.code === 'Period') {
} else if (e.key === '.' || e.keyCode === 'Period') {
stop();
e.preventDefault();
}
@@ -282,8 +272,6 @@ export function Repl({ embedded = false }) {
value={code}
keybindings={keybindings}
isLineNumbersDisplayed={isLineNumbersDisplayed}
isAutoCompletionEnabled={isAutoCompletionEnabled}
isLineWrappingEnabled={isLineWrappingEnabled}
fontSize={fontSize}
fontFamily={fontFamily}
onChange={handleChangeCode}
-107
View File
@@ -1,107 +0,0 @@
import {
processSampleMap,
registerSamplesPrefix,
registerSound,
onTriggerSample,
getAudioContext,
loadBuffer,
} from '@strudel.cycles/webaudio';
let TAURI;
if (typeof window !== 'undefined') {
TAURI = window?.__TAURI__;
}
export const { BaseDirectory, readDir, readBinaryFile, writeTextFile, readTextFile, exists } = TAURI?.fs || {};
export const dir = BaseDirectory?.Audio; // https://tauri.app/v1/api/js/path#audiodir
const prefix = '~/music/';
async function hasStrudelJson(subpath) {
return exists(subpath + '/strudel.json', { dir });
}
async function loadStrudelJson(subpath) {
const contents = await readTextFile(subpath + '/strudel.json', { dir });
const sampleMap = JSON.parse(contents);
processSampleMap(sampleMap, (key, value) => {
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value, fileResolver(subpath)), {
type: 'sample',
samples: value,
fileSystem: true,
tag: 'local',
});
});
}
async function writeStrudelJson(subpath) {
const children = await readDir(subpath, { dir, recursive: true });
const name = subpath.split('/').slice(-1)[0];
const tree = { name, children };
let samples = {};
let count = 0;
walkFileTree(tree, (entry, parent) => {
if (['wav', 'mp3'].includes(entry.name.split('.').slice(-1)[0])) {
samples[parent.name] = samples[parent.name] || [];
count += 1;
samples[parent.name].push(entry.subpath.slice(1).concat([entry.name]).join('/'));
}
});
const json = JSON.stringify(samples, null, 2);
const filepath = subpath + '/strudel.json';
await writeTextFile(filepath, json, { dir });
console.log(`wrote strudel.json with ${count} samples to ${subpath}!`);
}
registerSamplesPrefix(prefix, async (path) => {
const subpath = path.replace(prefix, '');
const hasJson = await hasStrudelJson(subpath);
if (!hasJson) {
await writeStrudelJson(subpath);
}
return loadStrudelJson(subpath);
});
export const walkFileTree = (node, fn) => {
if (!Array.isArray(node?.children)) {
return;
}
for (const entry of node.children) {
entry.subpath = (node.subpath || []).concat([node.name]);
fn(entry, node);
if (entry.children) {
walkFileTree(entry, fn);
}
}
};
export const isAudioFile = (filename) => ['wav', 'mp3'].includes(filename.split('.').slice(-1)[0]);
function uint8ArrayToDataURL(uint8Array) {
const blob = new Blob([uint8Array], { type: 'audio/*' });
const dataURL = URL.createObjectURL(blob);
return dataURL;
}
const loadCache = {}; // caches local urls to data urls
export async function resolveFileURL(url) {
if (loadCache[url]) {
return loadCache[url];
}
loadCache[url] = (async () => {
const contents = await readBinaryFile(url, { dir });
return uint8ArrayToDataURL(contents);
})();
return loadCache[url];
}
const fileResolver = (subpath) => (url) => resolveFileURL(subpath.endsWith('/') ? subpath + url : subpath + '/' + url);
export async function playFile(path) {
const url = await resolveFileURL(path);
const ac = getAudioContext();
const bufferSource = ac.createBufferSource();
bufferSource.buffer = await loadBuffer(url, ac);
bufferSource.connect(ac.destination);
bufferSource.start(ac.currentTime);
}
+1 -2
View File
@@ -1,7 +1,6 @@
import { Pattern, noteToMidi, valueToMidi } from '@strudel.cycles/core';
import { registerSynthSounds, samples } from '@strudel.cycles/webaudio';
import './piano.mjs';
import './files.mjs';
export async function prebake() {
// https://archive.org/details/SalamanderGrandPianoV3
@@ -119,7 +118,7 @@ const maxPan = noteToMidi('C8');
const panwidth = (pan, width) => pan * width + (1 - width) / 2;
Pattern.prototype.piano = function () {
return this.fmap((v) => ({ ...v, clip: v.clip ?? 1 })) // set clip if not already set..
return this.clip(1)
.s('piano')
.release(0.1)
.fmap((value) => {
+102 -70
View File
@@ -205,24 +205,27 @@ samples({
}
}, 'https://loophole-letters.vercel.app/')
const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', cat('Db major','Db mixolydian')])
stack(
s("<bd sn> <hh hh*2 hh*3>").color('#00B8D4'),
"<g4 c5 a4 [ab4 <eb5 f5>]>"
.scale("<C:major C:mixolydian F:lydian [F:minor <Db:major Db:mixolydian>]>")
.scale(scales)
.struct("x*8")
.scaleTranspose("0 [-5,-2] -7 [-9,-2]")
.legato(.3)
.slow(2)
.note()
.clip(.3)
.s('rhodes')
.clip(1)
.room(.5)
.delay(.3)
.delayfeedback(.4)
.delaytime(1/12).gain(.5).color('#7ED321'),
"<c2 c3 f2 [[F2 C2] db2]>/2"
.add("0,.02")
"<c2 c3 f2 [[F2 C2] db2]>"
.legato("<1@3 [.3 1]>")
.slow(2).superimpose(x=>x.add(.02))
.note().gain(.3)
.clip("<1@3 [.3 1]>/2")
.s('sawtooth').cutoff(600).color('#F8E71C'),
).fast(3/2)
//.pianoroll({fold:1})`;
@@ -234,7 +237,7 @@ export const wavyKalimba = `// "Wavy kalimba"
samples({
'kalimba': { c5:'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' }
})
const scales = "<C:major C:mixolydian F:lydian [F:minor Db:major]>"
const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major'])
stack(
"[0 2 4 6 9 2 0 -2]*3"
@@ -251,11 +254,13 @@ stack(
.velocity(0.8)
.slow(2)
)
.legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
.fast(1)
.note()
.clip("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
.clip(1)
.s('kalimba')
.delay(.2)`;
.delay(.2)
`;
export const festivalOfFingers = `// "Festival of fingers"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@@ -288,19 +293,19 @@ stack(
s("<<bd*2 bd> sn> hh").fast(2).gain(.7),
"[c2 a1 bb1 ~] ~"
.echo(2, 1/16, 1)
.legato(.4)
.slow(2)
.layer(h)
.note().s('square')
.clip(.4)
.cutoff(400).decay(.12).sustain(0)
,
"[g2,[c3 eb3]]".iter(4)
.echoWith(4, 1/8, (x,n)=>x.transpose(n*12).velocity(Math.pow(.4,n)))
.legato(.1)
.layer(h).note()
.clip(.1)
)
.fast(2/3)
.pianoroll()`;
.pianoroll({})`;
export const bridgeIsOver = `// "Bridge is over"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@@ -309,16 +314,19 @@ export const bridgeIsOver = `// "Bridge is over"
samples({mad:'https://freesound.org/data/previews/22/22274_109943-lq.mp3'})
stack(
stack(
note("c3*2 [[c3@1.4 bb2] ab2] gb2*2 <[[gb2@1.4 ab2] bb2] gb2>")
.gain(.8).clip("[.5 1]*2"),
n("<0 1 2 3 4 3 2 1>")
.clip(.5)
.echoWith(8, 1/32, (x,i)=>x.add(n(i)).velocity(Math.pow(.7,i)))
.scale('c4 whole tone')
.echo(3, 1/8, .5)
).piano(),
"c3*2 [[c3@1.4 bb2] ab2] gb2*2 <[[gb2@1.4 ab2] bb2] gb2>".legato(".5 1".fast(2)).velocity(.8),
"0 ~".scale('c4 whole tone')
.euclidLegato(3,8).slow(2).mask("x ~")
.stutWith(8, 1/16, (x,n)=>x.scaleTranspose(n).velocity(Math.pow(.7,n)))
.scaleTranspose("<0 1 2 3 4 3 2 1>")
.fast(2)
.velocity(.7)
.legato(.5)
.stut(3, .5, 1/8)
).transpose(-1).note().piano(),
s("mad").slow(2)
).cpm(78).slow(4)
.pianoroll()
`;
@@ -328,39 +336,63 @@ export const goodTimes = `// "Good times"
const scale = cat('C3 dorian','Bb2 major').slow(4);
stack(
n("2*4".add(12)).off(1/8, add(2))
"2*4".add(12).scale(scale)
.off(1/8, scaleTranspose("2")).fast(2)
.scaleTranspose("<0 1 2 1>").hush(),
"<0 1 2 3>(3,8,2)"
.scale(scale)
.fast(2)
.add("<0 1 2 1>").hush(),
"<0 1 2 3>(3,8,2)".off(1/4, add("2,4"))
.n().scale(scale),
n("<0 4>(5,8,-1)").scale(scale).sub(note(12))
.off(1/4, scaleTranspose("2,4")),
"<0 4>(5,8,-1)".scale(scale).transpose(-12)
)
.velocity(".6 .7".fast(4))
.add(note(4))
.piano()
.clip(2)
.legato("2")
.scaleTranspose("<0>".slow(4))
.transpose(5)
.note().piano()
.velocity(.8)
.slow(2)
.pianoroll()`;
.pianoroll({maxMidi:100,minMidi:20})`;
/* // new style notation:
export const goodTimes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const scale = cat('C3 dorian','Bb2 major').slow(4);
stack(
n("2*4").add(12)
.off(1/8, add("2")).fast(2)
.add("<0 1 2 1>").hush(),
n("<0 1 2 3>(3,8,2)")
.off(1/4, add("2,4")),
n("<0 4>(5,8,-1)").sub(7)
)
.scale(scale)
.gain(".6 .7".fast(4))
.legato(2)
.add(note(5))
.piano()
.mul(gain(.8))
.slow(2)
.pianoroll({maxMidi:100,minMidi:20})`;
*/
export const echoPiano = `// "Echo piano"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// @by Felix Roos
n("<0 2 [4 6](3,4,2) 3*2>").color('salmon')
.off(1/4, x=>x.add(2).color('green'))
.off(1/2, x=>x.add(6).color('steelblue'))
"<0 2 [4 6](3,4,2) 3*2>"
.scale('D minor')
.color('salmon')
.off(1/4, x=>x.scaleTranspose(2).color('green'))
.off(1/2, x=>x.scaleTranspose(6).color('steelblue'))
.legato(.5)
.echo(4, 1/8, .5)
.clip(.5)
.piano()
.note().piano()
.pianoroll()`;
export const sml1 = `// Hirokazu Tanaka - World 1-1
stack(
// melody
note(\`<
\`<
[e5 ~] [[d5@2 c5] [~@2 e5]] ~ [~ [c5@2 d5]] [e5 e5] [d5 c5] [e5 f5] [g5 a5]
[~ c5] [c5 d5] [e5 [c5@2 c5]] [~ c5] [f5 e5] [c5 d5] [~ g6] [g6 ~]
[e5 ~] [[d5@2 c5] [~@2 e5]] ~ [~ [c5@2 d5]] [e5 e5] [d5 c5] [a5 g5] [c6 [e5@2 d5]]
@@ -369,28 +401,28 @@ stack(
[f5 ~] [[g5@2 f5] ~] [[e5 ~] [f5 ~]] [[f#5 ~] [g5 ~]]
[~ a5] [b5 c6] [b5@2 ~@2 g5] ~
[eb6 d6] [~ c6] ~!2
>\`)
.clip(.95),
>\`
.legato(.95),
// sub melody
note(\`<
\`<
[~ g4]!2 [~ ab4]!2 [~ a4]!2 [~ bb4]!2
[~ a4]!2 [~ g4]!2 [d4 e4] [f4 gb4] ~!2
[~ g4]!2 [~ ab4]!2 [~ a4]!2 [~ bb4]!2
[~ a4]!2 [~ g4]!2 [d4 e4] [f4 gb4] ~!2
[~ c5]!4 [~ a4]!2 [[c4 ~] [d4 ~]] [[eb4 ~] [e4 ~]]
[~ c5]!4 [~ eb5]!2 [g4*2 [f4 ~]] [[e4 ~] [d4 ~]]
>\`),
>\`,
// bass
note(\`<
\`<
c3!7 a3 f3!2
e3!2 ~!4
c3!7 a3 f3!2
e3!2 ~!4
f3!2 e3!2 d3!2 ~!2
f3!2 e3!2 ab3!2 ~!2
>\`)
.clip(.5)
).fast(2)`;
>\`
.legato(.5)
).fast(2).note()`;
export const randomBells = `// "Random bells"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@@ -406,14 +438,16 @@ stack(
"0".euclidLegato(3,8)
.echo(3, 1/16, .5)
.add(rand.range(0,12))
.scale("D:minor:pentatonic").note()
.velocity(rand.range(.5,1))
.legato(rand.range(.4,3))
.scale(cat('D minor pentatonic')).note()
.s('bell').gain(.6).delay(.2).delaytime(1/3).delayfeedback(.8),
// bass
note("<D2 A2 G2 F2>").euclidLegatoRot(6,8,4).s('bass').clip(1).gain(.8)
"<D2 A2 G2 F2>".euclidLegatoRot(6,8,4).note().s('bass').clip(1).gain(.8)
)
.slow(6)
.pianoroll({vertical:1})`;
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})
`;
export const waa2 = `// "Waa2"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@@ -426,7 +460,7 @@ n(
.off(1/8,x=>x.add(12))
)
.slow(2)
.clip(sine.range(0.3, 2).slow(28))
.legato(sine.range(0.3, 2).slow(28))
.s("sawtooth square".fast(2))
.cutoff(cosine.range(500,4000).slow(16))
.gain(.5)
@@ -490,22 +524,20 @@ export const festivalOfFingers3 = `// "Festival of fingers 3"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// @by Felix Roos
n("[-7*3],0,2,6,[8 7]")
.echoWith(
4, // echo 4 times
1/4, // 1/4s between echos
(x,i)=>x
.add(n(i*7)) // add octaves
.gain(1/(i+1)) // reduce gain
.clip(1/(i+1))
)
.velocity(perlin.range(.5,.9).slow(8))
.stack(n("[22 25]*3")
.clip(sine.range(.5,2).slow(8))
.gain(sine.range(.4,.8).slow(5))
.echo(4,1/12,.5))
.scale("<D:dorian G:mixolydian C:dorian F:mixolydian>")
.slow(2).piano()
"[-7*3],0,2,6,[8 7]"
.echoWith(4,1/4, (x,n)=>x
.add(n*7)
.velocity(1/(n+1))
.legato(1/(n+1)))
.velocity(perlin.range(.5,.9).slow(8))
.stack("[22 25]*3"
.legato(sine.range(.5,2).slow(8))
.velocity(sine.range(.4,.8).slow(5))
.echo(4,1/12,.5))
.scale(cat('D dorian','G mixolydian','C dorian','F mixolydian'))
.legato(1)
.slow(2)
.note().piano()
//.pianoroll({maxMidi:160})`;
export const meltingsubmarine = `// "Melting submarine"
@@ -865,9 +897,9 @@ stack(
,
note("<C2 A1 D2 F2>/2").ply(8).csound('Bass').gain("1 4 1 4")
,
n("0 7 [4 3] 2".fast(2/3).off(".25 .125", add("<2 4 -3 -1>"))
note("0 7 [4 3] 2".fast(2/3).off(".25 .125",add("<2 4 -3 -1>"))
.slow(2).scale('A4 minor'))
.clip(.25).csound('SynHarp')
.legato(.25).csound('SynHarp')
,
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
)`;
@@ -878,15 +910,15 @@ export const arpoon = `// "Arpoon"
await samples('github:tidalcycles/Dirt-Samples/master')
note("<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings('lefthand'))
.arp("[0,3] 2 [1,3] 2".fast(3).lastOf(4, fast(2))).clip(2)
.add(perlin.range(0,0.2).add("<0 12>/8").note())
.cutoff(perlin.range(500,4000)).resonance(12)
"<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings('lefthand')
.arp("[0,3] 2 [1,3] 2".fast(3).lastOf(4, fast(2))).legato(2)
.add(perlin.range(0,0.2)).sub("<0 -12>/8")
.note().cutoff(perlin.range(500,4000)).resonance(12)
.gain("<.5 .8>*16")
.decay(.16).sustain(0.5)
.delay(.2)
.room(.5).pan(sine.range(.3,.6))
.s('piano')
.s('piano').clip(1)
.stack("<<A1 C2>!2 F2 [F2 E2]>".add.out("0 -5".fast(2)).add("0,.12").note().s('sawtooth').clip(1).cutoff(300))
.slow(4)
.stack(s("bd*4, [~ [hh hh? hh?]]*2,~ [sd ~ [sd:2? bd?]]").bank('RolandTR909').gain(.5).slow(2))
-20
View File
@@ -1,13 +1,10 @@
import { persistentMap } from '@nanostores/persistent';
import { useStore } from '@nanostores/react';
import { register } from '@strudel.cycles/core';
export const defaultSettings = {
activeFooter: 'intro',
keybindings: 'codemirror',
isLineNumbersDisplayed: true,
isAutoCompletionEnabled: false,
isLineWrappingEnabled: false,
theme: 'strudelTheme',
fontFamily: 'monospace',
fontSize: 18,
@@ -24,8 +21,6 @@ export function useSettings() {
...state,
isZen: [true, 'true'].includes(state.isZen) ? true : false,
isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false,
isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false,
isLineWrappingEnabled: [true, 'true'].includes(state.isLineWrappingEnabled) ? true : false,
fontSize: Number(state.fontSize),
};
}
@@ -34,18 +29,3 @@ export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
export const setLatestCode = (code) => settingsMap.setKey('latestCode', code);
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
const patternSetting = (key) =>
register(key, (value, pat) => {
value = Array.isArray(value) ? value.join(' ') : value;
if (value !== settingsMap.get()[key]) {
settingsMap.setKey(key, value);
}
return pat;
});
export const theme = patternSetting('theme');
export const fontFamily = patternSetting('fontFamily');
export const fontSize = patternSetting('fontSize');
export const settingPatterns = { theme, fontFamily, fontSize };