Compare commits

...

75 Commits

Author SHA1 Message Date
Felix Roos 90a58858ca Publish
- @strudel/codemirror@0.8.4
 - @strudel.cycles/core@0.8.2
 - @strudel.cycles/csound@0.8.0
 - @strudel.cycles/eval@0.8.0
 - @strudel.cycles/midi@0.8.0
 - @strudel.cycles/mini@0.8.2
 - @strudel.cycles/osc@0.8.0
 - @strudel.cycles/react@0.8.0
 - @strudel.cycles/serial@0.8.0
 - @strudel.cycles/soundfonts@0.8.2
 - @strudel.cycles/tonal@0.8.2
 - @strudel.cycles/tone@0.8.0
 - @strudel.cycles/transpiler@0.8.2
 - @strudel/web@0.8.3
 - @strudel.cycles/webaudio@0.8.2
 - @strudel.cycles/webdirt@0.8.0
 - @strudel.cycles/xen@0.8.0
2023-06-30 08:17:19 +02:00
Felix Roos 8149b85a55 Merge pull request #627 from tidalcycles/fix-clock-drift
fix: midi clock drift
2023-06-30 08:14:40 +02:00
Felix Roos e79b6c514a Merge pull request #621 from tidalcycles/tauri-fs
desktop: play samples from disk
2023-06-29 22:02:27 +02:00
Felix Roos aaa894d74b Merge pull request #620 from tidalcycles/loopAt
cps dependent functions
2023-06-29 21:59:42 +02:00
Felix Roos b55721f16f fix: midi clock drift 2023-06-29 21:58:15 +02:00
Felix Roos b39d948d71 fix: build 2023-06-29 12:32:59 +02:00
Felix Roos 0fee279e3d rename fay to fit 2023-06-29 12:05:31 +02:00
Felix Roos c7d8a9cb82 fix: playFile import 2023-06-29 12:05:09 +02:00
Felix Roos b50075f102 feat: can now load disk samples via samples
+ encapsulate file logic from FilesTab
2023-06-29 10:47:20 +02:00
Felix Roos fb3ca9f960 feat: register fn for sample prefix handling 2023-06-29 10:46:32 +02:00
Felix Roos 058b971870 can now play samples from file system
- ui still wip
2023-06-29 02:02:00 +02:00
Felix Roos 610ea414c1 expose tauri fs api 2023-06-29 02:01:22 +02:00
Felix Roos cc6997baec - encapsulate sample map handling
- add resolveUrl param
2023-06-29 02:00:59 +02:00
Felix Roos d126c28b85 fix: loopAt with cps 1 for fallback 2023-06-28 22:25:49 +02:00
Felix Roos c1649b9dc0 - fix: loopAt for non default cps value
- add fay to repitch samples to fit hap duration
- add setcpm function
2023-06-28 22:13:05 +02:00
Felix Roos b0a8d12112 Merge pull request #520 from paikwiki/patch-1
Fix typo on packages.mdx
2023-06-26 22:45:04 +02:00
Felix Roos b5160d38cb another dummy commit 2023-06-26 22:43:14 +02:00
Felix Roos 360e9b5936 dummy commit 2023-06-26 22:41:47 +02:00
Felix Roos 85a6cd828e Merge pull request #606 from tidalcycles/setting-patterning
patterning ui settings
2023-06-26 22:35:02 +02:00
Felix Roos e698f19a64 Merge pull request #614 from tidalcycles/spirals
add spiral viz
2023-06-26 22:32:46 +02:00
Felix Roos 83c1b580dd Merge pull request #613 from vasilymilovidov/tauri
tauri test
2023-06-26 22:17:46 +02:00
vvm 9273e1f8ed fix tauri icons 2023-06-26 13:13:22 +04:00
vvm 5287825279 tauri workflow fixes 2023-06-26 11:27:21 +04:00
vvm 5567959afd add tauri github workflow 2023-06-26 01:58:16 +04:00
Felix Roos eaab36b051 add spiral viz 2023-06-25 14:23:18 +02:00
vvm 9a4f3af580 add README 2023-06-24 02:34:02 +04:00
vvm b83664dfce add build optimizations 2023-06-24 02:32:57 +04:00
vvm 349f7ae965 init 2023-06-23 21:14:55 +04:00
vvm 6f41716ab5 init 2023-06-23 21:06:10 +04:00
Felix Roos abeb62dea8 Merge pull request #612 from tidalcycles/fix-links
fix: doc links
2023-06-23 09:59:43 +02:00
Felix Roos 7cfed80443 Merge pull request #598 from tidalcycles/clip-support-floats
clip now works like legato in tidal
2023-06-23 09:55:18 +02:00
Felix Roos 36aa7d9b25 Merge pull request #607 from tidalcycles/doc-fixes
tonal fixes
2023-06-18 10:36:17 +02:00
Felix Roos 2a449ad3c0 rename tonal api -> tonal functions 2023-06-18 10:32:51 +02:00
Felix Roos a71c5957cc fix: better fix 2023-06-16 14:23:39 +02:00
Felix Roos eef6c7d55e quick fix: typo 2023-06-16 14:20:49 +02:00
Felix Roos 3872322da1 allow patterning theme + font settings 2023-06-16 13:45:13 +02:00
Felix Roos f1e850a62a fix: doc links 2023-06-16 09:20:29 +02:00
Felix Roos 8d9278419d fix: flatten scale lists 2023-06-15 20:19:31 +02:00
Felix Roos 0862827a51 Merge pull request #581 from roipoussiere/line_wrap
editor: enable line wrapping
2023-06-15 12:51:40 +02:00
Roipoussiere f37a220a05 add checkbox in settings to enable line wrapping 2023-06-15 11:52:29 +02:00
Felix Roos aa51c2b6dd Merge remote-tracking branch 'upstream/main' into line_wrap 2023-06-15 10:59:52 +02:00
Felix Roos 92da70a7ae Merge remote-tracking branch 'origin/main' into clip-support-floats 2023-06-15 10:46:10 +02:00
Felix Roos a55862fc25 Merge pull request #602 from tidalcycles/ratio-function
add ratio function
2023-06-15 10:22:46 +02:00
Felix Roos 09b14a5702 add ratio function 2023-06-14 23:19:04 +02:00
Felix Roos 260b831815 refactor: legato -> clip 2023-06-14 00:22:55 +02:00
Felix Roos 665cf6ac65 Merge pull request #588 from roipoussiere/enable_autocomplete
enable auto-completion
2023-06-12 23:24:28 +02:00
Felix Roos 5e90021698 update snapshot 2023-06-12 23:12:40 +02:00
Felix Roos 16aeb7e12c docs: update clip entry 2023-06-12 23:11:07 +02:00
Felix Roos b4cbec173b only accept undefined for clip 2023-06-12 22:58:25 +02:00
Felix Roos 8c9e06c329 - clip now works like legato in tidal
- supports floats
- hap.duration now respects clip value
- hap.endClipped is now end*clip
- visualizations show clipped length
- clip(0) will now be silence
2023-06-12 22:39:32 +02:00
Felix Roos e4a35fdd72 Merge pull request #597 from tidalcycles/cursor-fix
improve cursor
2023-06-11 22:04:17 +02:00
Felix Roos 370d82e66c + disable cursor blink
+ make cursor thicker
2023-06-11 20:21:11 +02:00
Felix Roos 876e851517 Merge pull request #570 from dariacotocu/branch-daria
Solmization added
2023-06-11 20:00:45 +02:00
Felix Roos 190fe37a9f move solmization logic from midi2note to sol2note 2023-06-11 19:55:24 +02:00
Felix Roos 623661f8d2 Merge remote-tracking branch 'upstream/main' into branch-daria 2023-06-11 19:54:55 +02:00
Felix Roos f31bc3f8c5 Merge pull request #591 from tidalcycles/fix-fast0
fix: division by zero
2023-06-11 19:45:00 +02:00
Felix Roos 12fc48544c Merge pull request #596 from tidalcycles/fix-option-dot
Fix option dot
2023-06-11 19:44:40 +02:00
Felix Roos 3b4e44a9fd fix: main repl option-dot on mac 2023-06-11 13:55:11 +02:00
Felix Roos 7850fb727e fix: allow f for flat notes like tidal 2023-06-11 13:23:45 +02:00
Felix Roos 9b3122837e fix: division by zero 2023-06-11 13:15:52 +02:00
Felix Roos d4c59cc73b fix:typo + remove notes 2023-06-11 11:21:19 +02:00
Felix Roos fe36bf54b2 Merge commit '4d8308a763a632cc4dd93ed2a2ab129eb957af72' 2023-06-09 21:39:14 +02:00
Felix Roos 11f26c1e2c Merge pull request #587 from tidalcycles/workshop-new
New Workshop
2023-06-09 21:02:11 +02:00
Roipoussiere 5e09c3c318 add autocompletion extension from useMemo 2023-06-09 18:28:16 +02:00
Roipoussiere 8e6909478c autocompletion: lint 2023-06-09 16:42:31 +02:00
Roipoussiere 2d2d0cabb3 Add option in settings to enable autocompletion 2023-06-09 16:38:48 +02:00
Roipoussiere 6f04a1b8fa autocomplete: add vanilla js completion 2023-06-09 14:52:07 +02:00
Roipoussiere a9ad3a56ad autocomplete: avoid tabs overlapping 2023-06-09 14:30:20 +02:00
Roipoussiere 3069b14e9c enable autocompletion 2023-06-09 13:41:40 +02:00
Roipoussiere b6d1f96930 editor: enable line wrapping 2023-06-06 13:29:55 +02:00
Daria Cotocu b63f4eb503 Update solmization.test.js 2023-05-24 18:58:11 +01:00
Daria Cotocu cd7bc09f9c Update solmization.test.js 2023-05-24 18:43:24 +01:00
Daria Cotocu ccf775e976 Format code 2023-05-24 17:57:05 +01:00
Daria Cotocu 811346e83d Solmization added 2023-05-24 17:24:34 +01:00
paikwiki 74ad521fc9 Fix typo on packages.mdx
I found a broken link.

Because it omits protocol, page move to wrong page(https://strudel.tidalcycles.org/github.com/tidalcycles/strudel)

I add protocol(https://) on that link.
2023-03-14 13:01:40 +09:00
87 changed files with 7930 additions and 3275 deletions
+62
View File
@@ -0,0 +1,62 @@
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,3 +43,4 @@ 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/learn/getting-started),
If you find some weak spots in the [docs](https://strudel.tidalcycles.org/workshop/getting-started/),
you can edit each file directly on github via the "Edit this page" link located in the right sidebar.
## Propose a Feature
+4 -3
View File
@@ -52,10 +52,10 @@
"@strudel.cycles/webaudio": "workspace:*",
"@strudel.cycles/xen": "workspace:*",
"acorn": "^8.8.1",
"dependency-tree": "^9.0.0",
"vitest": "^0.28.0"
"dependency-tree": "^9.0.0"
},
"devDependencies": {
"@tauri-apps/cli": "^1.4.0",
"@vitest/ui": "^0.28.0",
"canvas": "^2.11.2",
"eslint": "^8.39.0",
@@ -66,6 +66,7 @@
"jsdoc-to-markdown": "^8.0.0",
"lerna": "^6.6.1",
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.8.1"
"rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.28.0"
}
}
+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.whole.end);
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
this.highlight(currentFrame);
onDraw?.(haps, time, currentFrame);
}, drawTime);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel/codemirror",
"version": "0.8.3",
"version": "0.8.4",
"description": "Codemirror Extensions for Strudel",
"main": "codemirror.mjs",
"publishConfig": {
+6 -4
View File
@@ -222,6 +222,7 @@ 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>")
*
@@ -749,13 +750,14 @@ const generic_params = [
['val'],
['cps'],
/**
* 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)
* 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.
*
* @name clip
* @param {number | Pattern} active 1 or 0
* @param {number | Pattern} factor >= 0
* @example
* note("c a f e ~").s("piano").clip(1)
* note("c a f e").s("piano").clip("<.5 1 2>")
*
*/
['clip'],
+5 -1
View File
@@ -32,7 +32,11 @@ export class Hap {
}
get duration() {
return this.whole.end.sub(this.whole.begin);
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);
}
wholeOrPart() {
+1
View File
@@ -24,6 +24,7 @@ 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.1",
"version": "0.8.2",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
+33 -5
View File
@@ -1582,6 +1582,24 @@ 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
@@ -1677,6 +1695,9 @@ 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));
@@ -1703,6 +1724,9 @@ 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));
});
@@ -2029,7 +2053,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().legato(.2)
* .scale('C minor').note().clip(.2)
*/
export const { echoWith, echowith, stutWith, stutwith } = register(
['echoWith', 'echowith', 'stutWith', 'stutwith'],
@@ -2179,6 +2203,7 @@ 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
@@ -2251,7 +2276,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)
*/
const slice = register(
export const slice = register(
'slice',
function (npat, ipat, opat) {
return npat.innerBind((n) =>
@@ -2279,7 +2304,7 @@ const slice = register(
* s("breaks165").splice(8, "0 1 [2 3 0]@2 3 0@2 7").hurry(0.65)
*/
const splice = register(
export const splice = register(
'splice',
function (npat, ipat, opat) {
const sliced = slice(npat, ipat, opat);
@@ -2296,7 +2321,10 @@ const splice = register(
false, // turns off auto-patternification
);
const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
// 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) {
return _loopAt(factor, pat, 1);
});
@@ -2313,6 +2341,6 @@ const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat)
* s("rhodes").loopAtCps(4,1.5).cps(1.5)
*/
// TODO - global cps clock
const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
export 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.whole.end >= t + from;
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= t + to && event.endClipped >= t + from;
events.filter(inFrame).forEach((event) => {
const isActive = event.whole.begin <= t && event.whole.end > t;
const isActive = event.whole.begin <= t && event.endClipped > 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,6 +3,7 @@ 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,
@@ -50,10 +51,32 @@ 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]".legato(saw.slow(4)).note()
* "c3 [eb3,g3] g2 [g3,bb3]".note().clip(saw.slow(4))
* @example
* saw.range(0,8).segment(8).scale('C major').slow(4).note()
*
+117
View File
@@ -0,0 +1,117 @@
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
@@ -0,0 +1,40 @@
/*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);
});
+60 -4
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][#bs]*[0-9]?$/.test(name);
export const isNote = (name) => /^[a-gA-G][#bsf]*[0-9]?$/.test(name);
export const tokenizeNote = (note) => {
if (typeof note !== 'string') {
return [];
}
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bs]*)([0-9])?$/)?.slice(1) || [];
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)([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 }[char], 0) || 0;
const offset = acc?.split('').reduce((o, char) => o + { '#': 1, b: -1, s: 1, f: -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 does not appear to be referenced or invoked anywhere in the codebase
* @deprecated only used in workshop (first-notes)
* @noAutocomplete
*/
export const midi2note = (n) => {
@@ -215,3 +215,59 @@ 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.7.1",
"version": "0.8.0",
"description": "csound bindings for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/eval",
"version": "0.7.1",
"version": "0.8.0",
"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().currentTime * 1000;
const timingOffset = WebMidi.time - getAudioContext().getOutputTimestamp().contextTime * 1000;
time = time * 1000 + timingOffset;
// destructure value
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/midi",
"version": "0.7.1",
"version": "0.8.0",
"description": "Midi API for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/mini",
"version": "0.8.1",
"version": "0.8.2",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/osc",
"version": "0.7.1",
"version": "0.8.0",
"description": "OSC messaging for strudel",
"main": "osc.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/react",
"version": "0.7.1",
"version": "0.8.0",
"description": "React components for strudel",
"main": "src/index.js",
"publishConfig": {
+25 -13
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 } from '@codemirror/lang-javascript';
import { javascript, javascriptLanguage } 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,14 +88,7 @@ const highlightField = StateField.define({
provide: (f) => EditorView.decorations.from(f),
});
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
];
const staticExtensions = [javascript(), highlightField, flashField];
export default function CodeMirror({
value,
@@ -105,6 +98,8 @@ export default function CodeMirror({
theme,
keybindings,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isLineWrappingEnabled,
fontSize = 18,
fontFamily = 'monospace',
options,
@@ -116,12 +111,14 @@ export default function CodeMirror({
},
[onChange],
);
const handleOnCreateEditor = useCallback(
(view) => {
onViewChanged?.(view);
},
[onViewChanged],
);
const handleOnUpdate = useCallback(
(viewUpdate) => {
if (viewUpdate.selectionSet && onSelectionChange) {
@@ -130,16 +127,31 @@ export default function CodeMirror({
},
[onSelectionChange],
);
const extensions = useMemo(() => {
let _extensions = [...staticExtensions];
let bindings = {
vim,
emacs,
};
if (bindings[keybindings]) {
return [...staticExtensions, bindings[keybindings]()];
_extensions.push(bindings[keybindings]());
}
return staticExtensions;
}, [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 (
<div style={{ fontSize, fontFamily }} className="w-full">
<_CodeMirror
+4
View File
@@ -24,3 +24,7 @@
.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.whole.end > audioTime); // keep only highlights that are still active
highlights.current = highlights.current.filter((hap) => hap.endClipped > 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.7.1",
"version": "0.8.0",
"description": "Webserial API for strudel",
"main": "serial.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/soundfonts",
"version": "0.8.1",
"version": "0.8.2",
"description": "Soundsfont support for strudel",
"main": "index.mjs",
"publishConfig": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tonal",
"version": "0.8.1",
"version": "0.8.2",
"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.join(' ');
scale = scale.flat().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.7.1",
"version": "0.8.0",
"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.1",
"version": "0.8.2",
"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.2",
"version": "0.8.3",
"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/transpiler": "workspace:*",
"@strudel.cycles/webaudio": "workspace:*"
},
"devDependencies": {
"vite": "^4.3.3"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.8.1",
"version": "0.8.2",
"description": "Web Audio helpers for Strudel",
"main": "index.mjs",
"type": "module",
+65 -33
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) => {
export const getSampleBufferSource = async (s, n, note, speed, freq, bank, resolveUrl) => {
let transpose = 0;
if (freq !== undefined && note !== undefined) {
logger('[sampler] hap has note and freq. ignoring note', 'warning');
@@ -45,6 +45,9 @@ export const getSampleBufferSource = async (s, n, note, speed, freq, bank) => {
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?
@@ -91,6 +94,46 @@ 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
@@ -107,6 +150,11 @@ export const getLoadedBuffer = (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;
@@ -130,39 +178,23 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
});
}
const { prebake, tag } = options;
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,
});
});
processSampleMap(
sampleMap,
(key, value) =>
registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), {
type: 'sample',
samples: value,
baseUrl,
prebake,
tag,
}),
baseUrl,
);
};
const cutGroups = [];
export async function onTriggerSample(t, value, onended, bank) {
export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
const {
s,
freq,
@@ -170,7 +202,7 @@ export async function onTriggerSample(t, value, onended, bank) {
nudge = 0, // TODO: is this in seconds?
cut,
loop,
clip = 0, // if 1, samples will be cut off when the hap ends
clip = undefined, // if 1, samples will be cut off when the hap ends
n = 0,
note,
speed = 1, // sample playback speed
@@ -188,7 +220,7 @@ export async function onTriggerSample(t, value, onended, bank) {
//const soundfont = getSoundfontKey(s);
const time = t + nudge;
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank);
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl);
// asny stuff above took too long?
if (ac.currentTime > t) {
@@ -232,7 +264,7 @@ export async function onTriggerSample(t, value, onended, bank) {
out.disconnect();
onended();
};
const stop = (endTime, playWholeBuffer = !clip) => {
const stop = (endTime, playWholeBuffer = clip === undefined) => {
let releaseTime = endTime;
if (playWholeBuffer) {
releaseTime = t + (end - begin) * bufferDuration;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webdirt",
"version": "0.7.1",
"version": "0.8.0",
"description": "WebDirt integration for Strudel",
"main": "index.mjs",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/xen",
"version": "0.7.1",
"version": "0.8.0",
"description": "Xenharmonic API for strudel",
"main": "index.mjs",
"publishConfig": {
+215 -25
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,6 +65,9 @@ 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:
@@ -92,7 +95,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/core:
dependencies:
@@ -102,7 +105,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -127,7 +130,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/core/examples/vite-vanilla-repl-cm6:
dependencies:
@@ -155,7 +158,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.2
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/csound:
dependencies:
@@ -171,7 +174,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/embed: {}
@@ -204,7 +207,7 @@ importers:
version: link:../mini
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -223,7 +226,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/mini:
dependencies:
@@ -236,7 +239,7 @@ importers:
version: 3.0.2
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -255,7 +258,7 @@ importers:
version: 5.8.1
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/react:
dependencies:
@@ -325,7 +328,7 @@ importers:
version: 3.3.2
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/react/examples/nano-repl:
dependencies:
@@ -380,7 +383,7 @@ importers:
version: 3.3.2
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/serial:
dependencies:
@@ -390,7 +393,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/soundfonts:
dependencies:
@@ -412,7 +415,7 @@ importers:
version: 3.3.1
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/tonal:
dependencies:
@@ -431,7 +434,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -447,7 +450,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -469,7 +472,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -494,7 +497,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/web/examples/repl-example:
dependencies:
@@ -504,7 +507,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.2
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/webaudio:
dependencies:
@@ -517,7 +520,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
packages/webdirt:
dependencies:
@@ -533,7 +536,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -546,7 +549,7 @@ importers:
devDependencies:
vite:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.11.18)
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
@@ -3537,6 +3540,7 @@ 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==}
@@ -3770,6 +3774,113 @@ 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:
@@ -3984,9 +4095,11 @@ 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==}
@@ -4063,6 +4176,7 @@ 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==}
@@ -4505,7 +4619,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(@types/node@18.11.18)
vite: 4.3.3
transitivePeerDependencies:
- supports-color
dev: true
@@ -4516,6 +4630,7 @@ 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==}
@@ -4523,11 +4638,13 @@ 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==}
@@ -4537,6 +4654,7 @@ 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==}
@@ -4546,6 +4664,7 @@ 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==}
@@ -4611,6 +4730,7 @@ 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==}
@@ -4735,6 +4855,7 @@ 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==}
@@ -4884,6 +5005,7 @@ 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==}
@@ -5153,6 +5275,7 @@ 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==}
@@ -5203,6 +5326,7 @@ 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==}
@@ -5332,6 +5456,7 @@ 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==}
@@ -5379,6 +5504,7 @@ packages:
/check-error@1.0.2:
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
dev: true
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
@@ -5457,6 +5583,7 @@ 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==}
@@ -5936,6 +6063,7 @@ packages:
engines: {node: '>=6'}
dependencies:
type-detect: 4.0.8
dev: true
/deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
@@ -7134,6 +7262,7 @@ 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==}
@@ -7965,6 +8094,7 @@ 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==}
@@ -8608,6 +8738,7 @@ 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==}
@@ -8700,6 +8831,7 @@ 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==}
@@ -9594,6 +9726,7 @@ 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==}
@@ -9639,6 +9772,7 @@ 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==}
@@ -10319,6 +10453,7 @@ 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==}
@@ -10571,9 +10706,11 @@ 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==}
@@ -10650,6 +10787,7 @@ packages:
jsonc-parser: 3.2.0
mlly: 1.2.0
pathe: 1.1.0
dev: true
/pkg@5.8.1:
resolution: {integrity: sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA==}
@@ -10851,6 +10989,7 @@ 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==}
@@ -11010,6 +11149,7 @@ 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==}
@@ -11763,6 +11903,7 @@ 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==}
@@ -11810,6 +11951,7 @@ 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==}
@@ -11828,6 +11970,7 @@ 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==}
@@ -11886,6 +12029,7 @@ 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==}
@@ -11968,6 +12112,7 @@ 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==}
@@ -11979,6 +12124,7 @@ packages:
/std-env@3.3.2:
resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==}
dev: true
/stdopt@2.2.0:
resolution: {integrity: sha512-D/p41NgXOkcj1SeGhfXOwv9z1K6EV3sjAUY5aeepVbgEHv7DpKWLTjhjScyzMWAQCAgUQys1mjH0eArm4cjRGw==}
@@ -12149,6 +12295,7 @@ 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==}
@@ -12435,14 +12582,17 @@ 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==}
@@ -12478,6 +12628,7 @@ 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==}
@@ -12584,6 +12735,7 @@ 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==}
@@ -12684,6 +12836,7 @@ 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==}
@@ -13059,6 +13212,7 @@ 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==}
@@ -13079,6 +13233,38 @@ 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}
@@ -13110,6 +13296,7 @@ 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==}
@@ -13207,6 +13394,7 @@ packages:
- sugarss
- supports-color
- terser
dev: true
/vscode-css-languageservice@6.2.3:
resolution: {integrity: sha512-EAyhyIVHpEaf+GjtI+tVe7SekdoANfG0aubnspsQwak3Qkimn/97FpAufNyXk636ngW05pjNKAR9zyTCzo6avQ==}
@@ -13394,6 +13582,7 @@ 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==}
@@ -13727,6 +13916,7 @@ 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
@@ -0,0 +1,3 @@
# 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
@@ -0,0 +1,32 @@
[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
@@ -0,0 +1,29 @@
# @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
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

+8
View File
@@ -0,0 +1,8 @@
// 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
@@ -0,0 +1,71 @@
{
"$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
}
]
}
}
+93 -76
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/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 ]",
"[ 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 ]",
]
`;
@@ -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/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 ]",
"[ 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 ]",
]
`;
@@ -3193,6 +3193,23 @@ 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 ]",
@@ -3486,30 +3503,30 @@ exports[`runs examples > example "samples" example index 1 1`] = `
exports[`runs examples > example "saw" example index 0 1`] = `
[
"[ 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 ]",
"[ 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 ]",
]
`;
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: 16,
bridgeIsOver: 8,
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)".legato(.18).mul("<1 3/4 2 2/3>")).gain(perlin.range(.2,.8))
freq("440(5,8)".clip(.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 Modifiers', link: 'learn/tonal' },
{ text: 'Tonal Functions', link: 'learn/tonal' },
],
Development: [
{ text: 'REPL', link: 'technical-manual/repl' },
+8
View File
@@ -16,3 +16,11 @@
.cm-gutters {
display: none !important;
}
.cm-cursorLayer {
animation-name: inherit !important;
}
.cm-cursor {
border-left: 2px solid currentcolor !important;
}
@@ -361,11 +361,3 @@ insect [crow metal] ~ ~,
Jetzt haben wir eine grundlegende Ahnung davon wie man mit Strudel Beats baut!
Im nächsten Kapitel werden wir ein paar [Töne spielen](/de/workshop/first-notes).
---
- vllt mal das wörtchen `sound` behandeln
- was ist überhaupt casio??
- Strg = ctrl on mac "+" ist verwirrend
- cursor ist sehr undeutlich wenn nicht in emacs mode
- wieviele zahlen gibt es
@@ -10,7 +10,7 @@ import { MiniRepl } from '../../../docs/MiniRepl';
<img src="/icons/strudel_icon.png" className="w-32 animate-pulse md:float-right ml-8" />
Willkommen zum Strudel Workshop!
Du hast den richtigen Ort gefunden wenn du lernen möchstest wie man mit Code Musik macht.
Du hast den richtigen Ort gefunden wenn du lernen möchtest wie man mit Code Musik macht.
## Was ist Strudel
@@ -139,6 +139,10 @@ 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 Modifiers](/learn/tonal).
There are also [Tonal Functions](/learn/tonal).
+1 -1
View File
@@ -1,3 +1,3 @@
<script is:inline>
window.location.pathname = `/learn/getting-started`;
window.location.pathname = `/workshop/getting-started`;
</script>
@@ -38,6 +38,10 @@ 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,14 +1,29 @@
---
title: Tonal API
title: Tonal Functions
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Tonal API
# Tonal Functions
The Tonal API, uses [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations.
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).
### transpose(semitones)
@@ -22,21 +37,6 @@ 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](/learn/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](/workshop/getting-started).
TODO
@@ -7,7 +7,7 @@ import { MiniRepl } from '../../docs/MiniRepl';
# Strudel Packages
The [strudel repo](github.com/tidalcycles/strudel) is organized as a monorepo, containing multiple npm packages.
The [strudel repo](https://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 = `/learn/getting-started`;
window.location.pathname = `/workshop/getting-started`;
</script>
@@ -80,7 +80,6 @@ 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
@@ -0,0 +1,75 @@
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>
);
}
+27 -3
View File
@@ -9,6 +9,9 @@ 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();
@@ -77,6 +80,7 @@ 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">
@@ -91,6 +95,7 @@ export function Footer({ context }) {
{activeFooter === 'sounds' && <SoundsTab />}
{activeFooter === 'reference' && <Reference />}
{activeFooter === 'settings' && <SettingsTab scheduler={context.scheduler} />}
{activeFooter === 'files' && <FilesTab />}
</div>
)}
</footer>
@@ -140,7 +145,7 @@ function WelcomeTab() {
</p>
<p>
To learn more about what this all means, check out the{' '}
<a href="./learn/getting-started" target="_blank">
<a href="./workshop/getting-started" target="_blank">
interactive tutorial
</a>
. Also feel free to join the{' '}
@@ -364,7 +369,16 @@ const fontFamilyOptions = {
};
function SettingsTab({ scheduler }) {
const { theme, keybindings, isLineNumbersDisplayed, fontSize, fontFamily } = useSettings();
const {
theme,
keybindings,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isLineWrappingEnabled,
fontSize,
fontFamily,
} = useSettings();
return (
<div className="text-foreground p-4 space-y-4">
{/* <FormItem label="Tempo">
@@ -406,7 +420,7 @@ function SettingsTab({ scheduler }) {
/>
</FormItem>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<FormItem label="Keybindings">
<ButtonGroup
value={keybindings}
@@ -419,6 +433,16 @@ 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,3 +45,11 @@
#code .cm-theme-light {
width: 100%;
}
#code .cm-cursorLayer {
animation-name: inherit !important;
}
#code .cm-cursor {
border-left: 2px solid currentcolor !important;
}
+14 -2
View File
@@ -19,6 +19,7 @@ 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();
@@ -45,6 +46,7 @@ const modules = [
const modulesLoading = evalScope(
controls, // sadly, this cannot be exported from core direclty
settingPatterns,
...modules,
);
@@ -105,7 +107,15 @@ export function Repl({ embedded = false }) {
const [lastShared, setLastShared] = useState();
const [pending, setPending] = useState(true);
const { theme, keybindings, fontSize, fontFamily, isLineNumbersDisplayed } = useSettings();
const {
theme,
keybindings,
fontSize,
fontFamily,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
isLineWrappingEnabled,
} = useSettings();
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
useStrudel({
@@ -157,7 +167,7 @@ export function Repl({ embedded = false }) {
e.preventDefault();
flash(view);
await activateCode();
} else if (e.key === '.' || e.keyCode === 'Period') {
} else if (e.key === '.' || e.code === 'Period') {
stop();
e.preventDefault();
}
@@ -272,6 +282,8 @@ export function Repl({ embedded = false }) {
value={code}
keybindings={keybindings}
isLineNumbersDisplayed={isLineNumbersDisplayed}
isAutoCompletionEnabled={isAutoCompletionEnabled}
isLineWrappingEnabled={isLineWrappingEnabled}
fontSize={fontSize}
fontFamily={fontFamily}
onChange={handleChangeCode}
+107
View File
@@ -0,0 +1,107 @@
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);
}
+2 -1
View File
@@ -1,6 +1,7 @@
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
@@ -118,7 +119,7 @@ const maxPan = noteToMidi('C8');
const panwidth = (pan, width) => pan * width + (1 - width) / 2;
Pattern.prototype.piano = function () {
return this.clip(1)
return this.fmap((v) => ({ ...v, clip: v.clip ?? 1 })) // set clip if not already set..
.s('piano')
.release(0.1)
.fmap((value) => {
+70 -102
View File
@@ -205,27 +205,24 @@ 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(scales)
.scale("<C:major C:mixolydian F:lydian [F:minor <Db:major Db:mixolydian>]>")
.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]>"
.legato("<1@3 [.3 1]>")
.slow(2).superimpose(x=>x.add(.02))
"<c2 c3 f2 [[F2 C2] db2]>/2"
.add("0,.02")
.note().gain(.3)
.clip("<1@3 [.3 1]>/2")
.s('sawtooth').cutoff(600).color('#F8E71C'),
).fast(3/2)
//.pianoroll({fold:1})`;
@@ -237,7 +234,7 @@ export const wavyKalimba = `// "Wavy kalimba"
samples({
'kalimba': { c5:'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' }
})
const scales = cat('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major'])
const scales = "<C:major C:mixolydian F:lydian [F:minor Db:major]>"
stack(
"[0 2 4 6 9 2 0 -2]*3"
@@ -254,13 +251,11 @@ stack(
.velocity(0.8)
.slow(2)
)
.legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
.fast(1)
.note()
.clip(1)
.clip("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
.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/
@@ -293,19 +288,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/
@@ -314,19 +309,16 @@ export const bridgeIsOver = `// "Bridge is over"
samples({mad:'https://freesound.org/data/previews/22/22274_109943-lq.mp3'})
stack(
stack(
"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(),
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(),
s("mad").slow(2)
).cpm(78).slow(4)
.pianoroll()
`;
@@ -336,63 +328,39 @@ export const goodTimes = `// "Good times"
const scale = cat('C3 dorian','Bb2 major').slow(4);
stack(
"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)"
n("2*4".add(12)).off(1/8, add(2))
.scale(scale)
.off(1/4, scaleTranspose("2,4")),
"<0 4>(5,8,-1)".scale(scale).transpose(-12)
.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))
)
.velocity(".6 .7".fast(4))
.legato("2")
.scaleTranspose("<0>".slow(4))
.transpose(5)
.note().piano()
.add(note(4))
.piano()
.clip(2)
.velocity(.8)
.slow(2)
.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})`;
*/
.pianoroll()`;
export const echoPiano = `// "Echo piano"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// @by Felix Roos
"<0 2 [4 6](3,4,2) 3*2>"
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'))
.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)
.note().piano()
.clip(.5)
.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]]
@@ -401,28 +369,28 @@ stack(
[f5 ~] [[g5@2 f5] ~] [[e5 ~] [f5 ~]] [[f#5 ~] [g5 ~]]
[~ a5] [b5 c6] [b5@2 ~@2 g5] ~
[eb6 d6] [~ c6] ~!2
>\`
.legato(.95),
>\`)
.clip(.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
>\`
.legato(.5)
).fast(2).note()`;
>\`)
.clip(.5)
).fast(2)`;
export const randomBells = `// "Random bells"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@@ -438,16 +406,14 @@ 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
"<D2 A2 G2 F2>".euclidLegatoRot(6,8,4).note().s('bass').clip(1).gain(.8)
note("<D2 A2 G2 F2>").euclidLegatoRot(6,8,4).s('bass').clip(1).gain(.8)
)
.slow(6)
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})
`;
.pianoroll({vertical:1})`;
export const waa2 = `// "Waa2"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@@ -460,7 +426,7 @@ n(
.off(1/8,x=>x.add(12))
)
.slow(2)
.legato(sine.range(0.3, 2).slow(28))
.clip(sine.range(0.3, 2).slow(28))
.s("sawtooth square".fast(2))
.cutoff(cosine.range(500,4000).slow(16))
.gain(.5)
@@ -524,20 +490,22 @@ 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
"[-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()
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()
//.pianoroll({maxMidi:160})`;
export const meltingsubmarine = `// "Melting submarine"
@@ -897,9 +865,9 @@ stack(
,
note("<C2 A1 D2 F2>/2").ply(8).csound('Bass').gain("1 4 1 4")
,
note("0 7 [4 3] 2".fast(2/3).off(".25 .125",add("<2 4 -3 -1>"))
n("0 7 [4 3] 2".fast(2/3).off(".25 .125", add("<2 4 -3 -1>"))
.slow(2).scale('A4 minor'))
.legato(.25).csound('SynHarp')
.clip(.25).csound('SynHarp')
,
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
)`;
@@ -910,15 +878,15 @@ export const arpoon = `// "Arpoon"
await samples('github:tidalcycles/Dirt-Samples/master')
"<<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)
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)
.gain("<.5 .8>*16")
.decay(.16).sustain(0.5)
.delay(.2)
.room(.5).pan(sine.range(.3,.6))
.s('piano').clip(1)
.s('piano')
.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,10 +1,13 @@
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,
@@ -21,6 +24,8 @@ 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),
};
}
@@ -29,3 +34,18 @@ 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 };