Compare commits

..

60 Commits

Author SHA1 Message Date
alex 97a38a6f59 more fiddling with making module level curried functions 2022-12-05 20:45:25 +00:00
alex 986de6a73b add more toplevel functions 2022-12-05 14:51:41 +00:00
alex 1750db2f1c more strudel import fixes 2022-12-05 13:34:37 +00:00
Alex McLean eadcf25775 Merge branch 'main' into separate-alignment-methods 2022-12-05 13:22:43 +00:00
alex 1d2c4fd771 untabify 2022-12-05 13:14:19 +00:00
alex 92bf43e3a6 fix up imports from core, and remove circular deps that were breaking everything 2022-12-05 13:07:56 +00:00
alex 9dd4385bbf failing attempt at bundling everything into a single core object 2022-12-05 12:23:51 +00:00
alex 274378df42 export pattern.mjs as single object 2022-12-05 11:33:40 +00:00
alex 5ec217ea21 default alignment op to set 2022-12-05 10:10:51 +00:00
Felix Roos 485a96962a Merge pull request #281 from tidalcycles/arp
implement collect + arp function
2022-12-04 12:51:01 +01:00
Felix Roos 98a104f731 update snapshot 2022-12-04 11:58:19 +01:00
Felix Roos ff987f14d7 add arp example tune 2022-12-04 11:57:40 +01:00
Felix Roos 4dd08268dc implement collect + arp function 2022-12-04 11:38:11 +01:00
Felix Roos c18a13d4fc Merge pull request #278 from tidalcycles/do-not-recompile-orc
do not recompile orc
2022-12-02 13:38:27 +01:00
Felix Roos 7a79995727 do not recompile orc 2022-12-02 13:34:05 +01:00
Felix Roos 01eca562c5 Merge pull request #275 from tidalcycles/csound
add basic csound output
2022-12-02 12:49:26 +01:00
Felix Roos f491d0c0c4 fix loadOrc in tests 2022-12-02 12:22:32 +01:00
Felix Roos edf2287e13 Merge remote-tracking branch 'origin/main' into csound 2022-12-02 12:20:12 +01:00
Felix Roos fcaab633c1 Merge pull request #277 from tidalcycles/tune-licenses
add licenses / credits to all tunes + remove some
2022-12-02 12:18:41 +01:00
Felix Roos 33b6579b5d update snapshots 2022-12-02 12:17:15 +01:00
Felix Roos 72a8e4bc22 add licenses / credits to all tunes + remove some 2022-12-02 12:11:28 +01:00
Felix Roos c67b02b7b0 update tunes 2022-12-02 12:09:58 +01:00
Felix Roos 486cd00384 added loadOrc + removed livecode.orc by default 2022-12-02 12:09:21 +01:00
Alex McLean caea7bb39d Support sending CRC16 bytes with serial messages (#276)
Optionally add a crc16 checksum.
Optionally support sending names in name/value pairs as single characters
2022-12-02 10:45:01 +00:00
Felix Roos e8fb7ee683 now passing controls as string keymap 2022-12-02 11:38:47 +01:00
Felix Roos e4ee4211c8 delete strudel unrealted example 2022-12-01 15:10:22 +01:00
Felix Roos 3fa3ebee14 Merge remote-tracking branch 'origin/main' into csound 2022-12-01 15:05:23 +01:00
Felix Roos 8e531f400e allow defining csound on the window 2022-12-01 15:00:16 +01:00
Felix Roos 5727c0dc8a clean up tunes + also load presets.orc 2022-12-01 14:56:50 +01:00
Felix Roos f599088cdf cleanup 2022-12-01 14:44:53 +01:00
Felix Roos d78da55f54 lazy init + add livecode.org 2022-12-01 14:36:00 +01:00
Felix Roos 9052ff71e6 get rid of csound warning on reevaluation:
- only compile on init, use evalCode after
- pull presets into csd file
- use custom logging
2022-12-01 12:34:26 +01:00
Felix Roos 713da887ca Merge branch 'tonal-update' into csound 2022-12-01 12:32:05 +01:00
alex d2375f89ee experiment simplifying how/what matrix into separate methods 2022-11-30 08:45:34 +00:00
Felix Roos ad5950697e Merge pull request #273 from tidalcycles/fix-tutorial-bugs
release version bumps
2022-11-29 23:41:39 +01:00
Felix Roos 85d0d06cab Merge branch 'main' into fix-tutorial-bugs 2022-11-29 23:37:35 +01:00
Felix Roos be6ec558e3 Merge pull request #271 from tidalcycles/eslint
add eslint
2022-11-29 23:34:50 +01:00
Felix Roos dd2f60810a Merge pull request #272 from tidalcycles/tonal-update
tonal update with fixed memory leak
2022-11-29 23:34:26 +01:00
Felix Roos 6c0b86d8f4 update tonal, fixes #269 2022-11-29 23:31:23 +01:00
Felix Roos 517ada6dbf tiny time improvement 2022-11-29 23:02:09 +01:00
Felix Roos 0a098e3008 mock csound 2022-11-29 22:04:46 +01:00
Felix Roos c40078ffc9 improve csound bindings:
- better timing
- can now pass instrument to .csound(...)
- can now write instruments with csound(...)
2022-11-29 21:59:22 +01:00
Felix Roos 729e0afdbe fix typo 2022-11-29 00:10:21 +01:00
Felix Roos eab86840d3 baasic csound output 2022-11-29 00:05:51 +01:00
Felix Roos 43aa2247f1 baasic csound output 2022-11-29 00:05:36 +01:00
Felix Roos afe17a507e update tonal: not working... 2022-11-28 23:00:15 +01:00
Felix Roos e621020474 continue tutorial 2022-11-28 21:33:17 +01:00
Felix Roos ac2aa7f288 add csound minimal example 2022-11-28 20:48:52 +01:00
Felix Roos 805bd7e930 simplify without-audio example 2022-11-24 20:56:09 +01:00
Felix Roos f9bf2122b7 add without-audio example 2022-11-24 20:13:54 +01:00
Felix Roos 4e3e533e13 repl: optional eval callbacks 2022-11-24 20:13:04 +01:00
Felix Roos 611c429912 lint before test 2022-11-24 15:31:47 +01:00
Felix Roos e2898ee5bf add eslint + fix lint errors 2022-11-24 15:30:11 +01:00
Alex McLean e1a532500e Tidying up core (#256)
* remove _ prefixes except for functions to be patternified
* categorise pattern methods
* experimental support for `.add.squeeze` and friends as alternative to `.addSqueeze`
* `every` is now an alias for `firstOf` with additional `lastOf` (which every will become an alias for next)
2022-11-22 08:51:25 +00:00
Felix Roos 4cf412b93d Merge pull request #266 from tidalcycles/faster-fast
fix performance bottleneck
2022-11-21 22:15:48 +01:00
Felix Roos 54c9c434e0 fix examples snapshot 2022-11-21 22:09:26 +01:00
Felix Roos de19f3e5fe fix tune snapshots 2022-11-21 22:08:17 +01:00
Felix Roos 8304993481 fix: #194 2022-11-21 21:59:08 +01:00
Felix Roos 4c838aeaca hotfix: weird audion bug 2022-11-17 11:13:49 +01:00
Felix Roos 547d925065 Merge pull request #263 from tidalcycles/fix-tutorial-bugs
fix tutorial bugs
2022-11-17 11:08:37 +01:00
63 changed files with 8663 additions and 8859 deletions
+14
View File
@@ -0,0 +1,14 @@
krill-parser.js
krill.pegjs
.eslintrc.json
server.js
tidal-sniffer.js
*.jsx
tunejs.js
out/**
postcss.config.js
postcss.config.cjs
tailwind.config.js
vite.config.js
/**/dist/**/*
!**/*.mjs
+16
View File
@@ -0,0 +1,16 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": ["eslint:recommended"],
"overrides": [],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [],
"rules": {
"no-unused-vars": ["warn", { "destructuredArrayIgnorePattern": ".", "ignoreRestSiblings": false }]
}
}
+2574 -5391
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -5,7 +5,7 @@
"description": "Port of tidalcycles to javascript",
"scripts": {
"pretest": "cd tutorial && npm run jsdoc-json",
"test": "vitest run --version",
"test": "npm run lint && vitest run --version",
"test-ui": "vitest --ui",
"test-coverage": "vitest --coverage",
"bootstrap": "lerna bootstrap",
@@ -17,7 +17,8 @@
"preview": "npx serve ./out",
"deploy": "NODE_DEBUG=gh-pages gh-pages -d out",
"jsdoc": "jsdoc packages/ -c jsdoc.config.json",
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json"
"jsdoc-json": "jsdoc packages/ --template ./node_modules/jsdoc-json --destination doc.json -c jsdoc.config.json",
"lint": "npx eslint . --ext mjs,js --quiet"
},
"workspaces": [
"packages/*"
@@ -42,6 +43,7 @@
"devDependencies": {
"@vitest/ui": "^0.21.1",
"c8": "^7.12.0",
"eslint": "^8.28.0",
"events": "^3.3.0",
"gh-pages": "^4.0.0",
"jsdoc": "^3.6.10",
+4 -3
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, sequence } from './pattern.mjs';
import pattern from './pattern.mjs';
const { Pattern, sequence } = pattern;
const controls = {};
const generic_params = [
@@ -400,7 +401,7 @@ const generic_params = [
* @example
* freq("220 110 440 110").s("superzow").osc()
* @example
* freq("110".mulOut(".5 1.5 .6 [2 3]")).s("superzow").osc()
* freq("110".mul.out(".5 1.5 .6 [2 3]")).s("superzow").osc()
*
*/
['f', 'freq', ''],
@@ -787,6 +788,6 @@ controls.createParam = (name) => {
};
controls.createParams = (...names) =>
names.reduce((acc, name) => Object.assign(acc, { [name]: createParam(name) }), {});
names.reduce((acc, name) => Object.assign(acc, { [name]: controls.createParam(name) }), {});
export default controls;
+5 -1
View File
@@ -4,7 +4,11 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, getTime } from './index.mjs';
import pattern from './pattern.mjs';
const Pattern = pattern.Pattern;
import { getTime } from './time.mjs';
import { State } from './state.mjs';
import { TimeSpan } from './timespan.mjs';
export const getDrawContext = (id = 'test-canvas') => {
let canvas = document.querySelector('#' + id);
+2 -1
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, timeCat } from './pattern.mjs';
import pattern from './pattern.mjs';
const { Pattern, timeCat } = pattern;
import bjork from 'bjork';
import { rotate } from './util.mjs';
import Fraction from './fraction.mjs';
+2 -1
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { isPattern, Pattern } from './index.mjs';
import pattern from './pattern.mjs';
const { isPattern, Pattern } = pattern;
let scoped = false;
export const evalScope = async (...args) => {
+65
View File
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Buildless Vanilla Strudel REPL</title>
</head>
<body style="margin: 0; background: #222">
<div style="display: grid; height: 100vh">
<textarea
id="text"
style="font-size: 2em; border: 0; color: white; background: transparent; outline: none; padding: 20px"
spellcheck="false"
>
// LOADING</textarea
>
</div>
<button
id="start"
style="
position: absolute;
border-radius: 10px;
top: 20px;
right: 20px;
padding: 20px;
border: 2px solid white;
background: transparent;
color: white;
cursor: pointer;
"
>
evaluate
</button>
<div id="output"></div>
<script type="module">
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel.cycles/core@0.4.1';
import { transpiler } from 'https://cdn.skypack.dev/@strudel.cycles/transpiler@0.4.1';
const modules = [
import('https://cdn.skypack.dev/@strudel.cycles/core@0.4.1'),
import('https://cdn.skypack.dev/@strudel.cycles/mini@0.4.1'),
];
const input = document.getElementById('text');
Promise.all(modules).then(() => {
input.innerHTML = `note("<c3 [d3 e3]>").cutoff(1000)`;
document.getElementById('start').addEventListener('click', () => {
evaluate(input.value);
});
});
evalScope(controls, ...modules);
const { evaluate } = repl({
defaultOutput: (hap, deadline, duration) => {
console.log(deadline, duration, hap.value);
},
getTime: () => Date.now() / 1000,
transpiler,
beforeEval: (code) => console.log('evaluate', code),
afterEval: (code) => {},
});
</script>
</body>
</html>
+1 -1
View File
@@ -74,7 +74,7 @@ const fraction = (n) => {
-> those farey sequences turn out to make pattern querying ~20 times slower! always use strings!
-> still, some optimizations could be done: .mul .div .add .sub calls still use numbers
*/
n = String(n);
// n = String(n); // this is actually faster but imprecise...
}
return Fraction(n);
};
+28 -19
View File
@@ -4,27 +4,36 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import controls from './controls.mjs';
export * from './euclid.mjs';
import Fraction from './fraction.mjs';
import drawLine from './drawLine.mjs';
import gist from './gist.js';
import { logger } from './logger.mjs';
export { Fraction, controls };
export * from './hap.mjs';
export * from './pattern.mjs';
export * from './signal.mjs';
export * from './state.mjs';
export * from './timespan.mjs';
export * from './util.mjs';
export * from './speak.mjs';
export * from './evaluate.mjs';
export * from './repl.mjs';
export * from './logger.mjs';
export * from './time.mjs';
export * from './draw.mjs';
export * from './pianoroll.mjs';
export * from './ui.mjs';
export { default as drawLine } from './drawLine.mjs';
export { default as gist } from './gist.js';
// Fraction: Fraction, drawLine: drawline, gist: gist, logger: logger,
import controls from './controls.mjs';
import pattern from './pattern.mjs';
import * as draw from './draw.mjs';
import * as euclid from './euclid.mjs';
import * as evaluate from './evaluate.mjs';
import * as hap from './hap.mjs';
import * as repl from './repl.mjs';
import * as pianoroll from './pianoroll.mjs';
import * as signal from './signal.mjs';
import * as speak from './speak.mjs';
import * as state from './state.mjs';
import * as time from './time.mjs';
import * as timespan from './timespan.mjs';
import * as ui from './ui.mjs';
import * as util from './util.mjs';
const core = { drawLine, gist, logger, Fraction, ...controls, ...pattern, ...draw, ...euclid,
...evaluate, ...hap, ...repl, ...pianoroll, ...signal, ...speak,
...state, ...time, ...timespan, ...ui, ...util
};
export default core;
// below won't work with runtime.mjs (json import fails)
/* import * as p from './package.json';
export const version = p.version; */
+802 -662
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -4,7 +4,11 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern } from './index.mjs';
import pattern from './pattern.mjs';
const { Pattern } = pattern;
import { toMidi } from './util.mjs';
import { getDrawContext } from './draw.mjs';
const scale = (normalized, min, max) => normalized * (max - min) + min;
const getValue = (e) => {
+2 -2
View File
@@ -40,11 +40,11 @@ export function repl({
throw new Error('no code to evaluate');
}
try {
beforeEval({ code });
beforeEval?.({ code });
const { pattern } = await _evaluate(code, transpiler);
logger(`[eval] code updated`);
scheduler.setPattern(pattern, autostart);
afterEval({ code, pattern });
afterEval?.({ code, pattern });
return pattern;
} catch (err) {
// console.warn(`[repl] eval error: ${err.message}`);
+9 -8
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { Hap } from './hap.mjs';
import { Pattern, fastcat, reify, silence, stack, isPattern } from './pattern.mjs';
import pattern from './pattern.mjs';
const { Pattern, fastcat, reify, silence, stack, isPattern } = pattern;
import Fraction from './fraction.mjs';
import { id } from './util.mjs';
@@ -20,7 +21,7 @@ export const signal = (func) => {
};
export const isaw = signal((t) => 1 - (t % 1));
export const isaw2 = isaw._toBipolar();
export const isaw2 = isaw.toBipolar();
/**
* A sawtooth signal between 0 and 1.
@@ -33,7 +34,7 @@ export const isaw2 = isaw._toBipolar();
*
*/
export const saw = signal((t) => t % 1);
export const saw2 = saw._toBipolar();
export const saw2 = saw.toBipolar();
export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
@@ -45,7 +46,7 @@ export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
* sine.segment(16).range(0,15).slow(2).scale('C minor').note()
*
*/
export const sine = sine2._fromBipolar();
export const sine = sine2.fromBipolar();
/**
* A cosine signal between 0 and 1.
@@ -67,7 +68,7 @@ export const cosine2 = sine2._early(Fraction(1).div(4));
*
*/
export const square = signal((t) => Math.floor((t * 2) % 2));
export const square2 = square._toBipolar();
export const square2 = square.toBipolar();
/**
* A triangle signal between 0 and 1.
@@ -127,7 +128,7 @@ export const rand = signal(timeToRand);
/**
* A continuous pattern of random numbers, between -1 and 1
*/
export const rand2 = rand._toBipolar();
export const rand2 = rand.toBipolar();
export const _brandBy = (p) => rand.fmap((x) => x < p);
export const brandBy = (pPat) => reify(pPat).fmap(_brandBy).innerJoin();
@@ -201,7 +202,7 @@ Pattern.prototype.choose = function (...xs) {
* @returns {Pattern}
*/
Pattern.prototype.choose2 = function (...xs) {
return chooseWith(this._fromBipolar(), xs);
return chooseWith(this.fromBipolar(), xs);
};
/**
@@ -259,7 +260,7 @@ export const perlinWith = (pat) => {
export const perlin = perlinWith(time.fmap((v) => Number(v)));
Pattern.prototype._degradeByWith = function (withPat, x) {
return this.fmap((a) => (_) => a).appLeft(withPat._filterValues((v) => v > x));
return this.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x));
};
/**
+2 -1
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, patternify2 } from './index.mjs';
import pattern from './pattern.mjs';
const {Pattern, patternify2, reify} = pattern;
let synth;
try {
+2 -1
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { fastcat, stack, slowcat, silence, pure } from '../pattern.mjs';
import pattern from '../pattern.mjs';
const { fastcat, stack, slowcat, silence, pure } = pattern;
import { describe, it, expect } from 'vitest';
import drawLine from '../drawLine.mjs';
+69 -61
View File
@@ -8,10 +8,9 @@ import Fraction from 'fraction.js';
import { describe, it, expect } from 'vitest';
import {
TimeSpan,
Hap,
State,
import core from '../index.mjs';
const {
Pattern,
pure,
stack,
@@ -29,6 +28,12 @@ import {
sub,
mul,
div,
id,
ply,
rev,
TimeSpan,
Hap,
State,
saw,
saw2,
isaw,
@@ -39,17 +44,13 @@ import {
square2,
tri,
tri2,
id,
ply,
rev,
time,
} from '../index.mjs';
time} = core;
import { steady } from '../signal.mjs';
import controls from '../controls.mjs';
const { n } = controls;
const { n, s } = controls;
const st = (begin, end) => new State(ts(begin, end));
const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end));
const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, context);
@@ -58,7 +59,7 @@ const third = Fraction(1, 3);
const twothirds = Fraction(2, 3);
const sameFirst = (a, b) => {
return expect(a._sortHapsByPart().firstCycle()).toStrictEqual(b._sortHapsByPart().firstCycle());
return expect(a.sortHapsByPart().firstCycle()).toStrictEqual(b.sortHapsByPart().firstCycle());
};
describe('TimeSpan', () => {
@@ -156,32 +157,32 @@ describe('Pattern', () => {
describe('add()', () => {
it('can structure In()', () => {
expect(pure(3).add(pure(4)).query(st(0, 1))[0].value).toBe(7);
expect(pure(3).addIn(pure(4)).query(st(0, 1))[0].value).toBe(7);
expect(pure(3).add.in(pure(4)).query(st(0, 1))[0].value).toBe(7);
});
it('can structure Out()', () => {
sameFirst(sequence(1, 2).addOut(4), sequence(5, 6).struct(true));
sameFirst(sequence(1, 2).add.out(4), sequence(5, 6).struct(true));
});
it('can Mix() structure', () => {
expect(sequence(1, 2).addMix(silence, 5, silence).firstCycle()).toStrictEqual([
expect(sequence(1, 2).add.mix(silence, 5, silence).firstCycle()).toStrictEqual([
new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 6),
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 7),
]);
});
it('can Trig() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).addTrig(20, 30).early(2),
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).add.trig(20, 30).early(2),
sequence(26, 27, 36, 37),
);
});
it('can Trigzero() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).addTrigzero(20, 30).early(2),
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).add.trigzero(20, 30).early(2),
sequence(21, 22, 31, 32),
);
});
it('can Squeeze() structure', () => {
sameFirst(
sequence(1, [2, 3]).addSqueeze(sequence(10, 20, 30)),
sequence(1, [2, 3]).add.squeeze(sequence(10, 20, 30)),
sequence(
[11, 21, 31],
[
@@ -193,7 +194,7 @@ describe('Pattern', () => {
});
it('can SqueezeOut() structure', () => {
sameFirst(
sequence(1, [2, 3]).addSqueezeOut(10, 20, 30),
sequence(1, [2, 3]).add.squeezeout(10, 20, 30),
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
);
});
@@ -204,32 +205,32 @@ describe('Pattern', () => {
describe('keep()', () => {
it('can structure In()', () => {
expect(pure(3).keep(pure(4)).query(st(0, 1))[0].value).toBe(3);
expect(pure(3).keepIn(pure(4)).query(st(0, 1))[0].value).toBe(3);
expect(pure(3).keep.in(pure(4)).query(st(0, 1))[0].value).toBe(3);
});
it('can structure Out()', () => {
sameFirst(sequence(1, 2).keepOut(4), sequence(1, 2).struct(true));
sameFirst(sequence(1, 2).keep.out(4), sequence(1, 2).struct(true));
});
it('can Mix() structure', () => {
expect(sequence(1, 2).keepMix(silence, 5, silence).firstCycle()).toStrictEqual([
expect(sequence(1, 2).keep.mix(silence, 5, silence).firstCycle()).toStrictEqual([
new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 1),
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2),
]);
});
it('can Trig() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepTrig(20, 30).early(2),
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keep.trig(20, 30).early(2),
sequence(6, 7, 6, 7),
);
});
it('can Trigzero() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepTrigzero(20, 30).early(2),
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keep.trigzero(20, 30).early(2),
sequence(1, 2, 1, 2),
);
});
it('can Squeeze() structure', () => {
sameFirst(
sequence(1, [2, 3]).keepSqueeze(sequence(10, 20, 30)),
sequence(1, [2, 3]).keep.squeeze(sequence(10, 20, 30)),
sequence(
[1, 1, 1],
[
@@ -239,39 +240,39 @@ describe('Pattern', () => {
),
);
});
it('can SqueezeOut() structure', () => {
sameFirst(sequence(1, [2, 3]).keepSqueezeOut(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]]));
it('can squeezeOut() structure', () => {
sameFirst(sequence(1, [2, 3]).keep.squeezeout(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]]));
});
});
describe('keepif()', () => {
it('can structure In()', () => {
sameFirst(sequence(3, 4).keepif(true, false), sequence(3, silence));
sameFirst(sequence(3, 4).keepifIn(true, false), sequence(3, silence));
sameFirst(sequence(3, 4).keepif.in(true, false), sequence(3, silence));
});
it('can structure Out()', () => {
sameFirst(pure(1).keepifOut(true, false), sequence(1, silence));
sameFirst(pure(1).keepif.out(true, false), sequence(1, silence));
});
it('can Mix() structure', () => {
expect(sequence(1, 2).keepifMix(false, true, false).firstCycle()).toStrictEqual([
expect(sequence(1, 2).keepif.mix(false, true, false).firstCycle()).toStrictEqual([
new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 1),
new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2),
]);
});
it('can Trig() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepifTrig(false, true).early(2),
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepif.trig(false, true).early(2),
sequence(silence, silence, 6, 7),
);
});
it('can Trigzero() structure', () => {
sameFirst(
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepifTrigzero(false, true).early(2),
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepif.trigzero(false, true).early(2),
sequence(silence, silence, 1, 2),
);
});
it('can Squeeze() structure', () => {
sameFirst(
sequence(1, [2, 3]).keepifSqueeze(sequence(true, true, false)),
sequence(1, [2, 3]).keepif.squeeze(sequence(true, true, false)),
sequence(
[1, 1, silence],
[
@@ -282,7 +283,7 @@ describe('Pattern', () => {
);
});
it('can SqueezeOut() structure', () => {
sameFirst(sequence(1, [2, 3]).keepifSqueezeOut(true, true, false), sequence([1, [2, 3]], [1, [2, 3]], silence));
sameFirst(sequence(1, [2, 3]).keepif.squeezeout(true, true, false), sequence([1, [2, 3]], [1, [2, 3]], silence));
});
});
describe('sub()', () => {
@@ -320,15 +321,15 @@ describe('Pattern', () => {
it('Can set things with plain values', () => {
sameFirst(sequence(1, 2, 3).set(4), sequence(4).fast(3));
});
describe('setOut()', () => {
describe('set.out()', () => {
it('Can set things with structure from second pattern', () => {
sameFirst(sequence(1, 2).setOut(4), pure(4).mask(true, true));
sameFirst(sequence(1, 2).set.out(4), pure(4).mask(true, true));
});
});
describe('setSqueeze()', () => {
describe('set.squeeze()', () => {
it('Can squeeze one pattern inside the haps of another', () => {
sameFirst(
sequence(1, [2, 3]).setSqueeze(sequence('a', 'b', 'c')),
sequence(1, [2, 3]).set.squeeze(sequence('a', 'b', 'c')),
sequence(
['a', 'b', 'c'],
[
@@ -338,7 +339,7 @@ describe('Pattern', () => {
),
);
sameFirst(
sequence(1, [2, 3]).setSqueeze('a', 'b', 'c'),
sequence(1, [2, 3]).set.squeeze('a', 'b', 'c'),
sequence(
['a', 'b', 'c'],
[
@@ -382,7 +383,7 @@ describe('Pattern', () => {
);
});
it('copes with breaking up events across cycles', () => {
expect(pure('a').slow(2)._fastGap(2)._setContext({}).query(st(0, 2))).toStrictEqual([
expect(pure('a').slow(2)._fastGap(2).setContext({}).query(st(0, 2))).toStrictEqual([
hap(ts(0, 1), ts(0, 0.5), 'a'),
hap(ts(0.5, 1.5), ts(1, 1.5), 'a'),
]);
@@ -446,8 +447,8 @@ describe('Pattern', () => {
});
describe('slow()', () => {
it('Supports zero-length queries', () => {
expect(steady('a').slow(1)._setContext({}).queryArc(0, 0)).toStrictEqual(
steady('a')._setContext({}).queryArc(0, 0),
expect(steady('a').slow(1).setContext({}).queryArc(0, 0)).toStrictEqual(
steady('a').setContext({}).queryArc(0, 0),
);
});
});
@@ -465,7 +466,7 @@ describe('Pattern', () => {
it('Filters true', () => {
expect(
pure(true)
._filterValues((x) => x)
.filterValues((x) => x)
.firstCycle().length,
).toBe(1);
});
@@ -490,7 +491,7 @@ describe('Pattern', () => {
pure(10)
.when(slowcat(true, false), (x) => x.add(3))
.fast(4)
._sortHapsByPart()
.sortHapsByPart()
.firstCycle(),
).toStrictEqual(fastcat(13, 10, 13, 10).firstCycle());
});
@@ -577,26 +578,26 @@ describe('Pattern', () => {
});
});
describe('every()', () => {
describe('firstOf()', () => {
it('Can apply a function every 3rd time', () => {
expect(
pure('a')
.every(3, (x) => x._fast(2))
.firstOf(3, (x) => x._fast(2))
._fast(3)
.firstCycle(),
).toStrictEqual(sequence(sequence('a', 'a'), 'a', 'a').firstCycle());
});
it('works with currying', () => {
expect(pure('a').every(3, fast(2))._fast(3).firstCycle()).toStrictEqual(
expect(pure('a').firstOf(3, fast(2))._fast(3).firstCycle()).toStrictEqual(
sequence(sequence('a', 'a'), 'a', 'a').firstCycle(),
);
expect(sequence(3, 4, 5).every(3, add(3)).fast(5).firstCycle()).toStrictEqual(
expect(sequence(3, 4, 5).firstOf(3, add(3)).fast(5).firstCycle()).toStrictEqual(
sequence(6, 7, 8, 3, 4, 5, 3, 4, 5, 6, 7, 8, 3, 4, 5).firstCycle(),
);
expect(sequence(3, 4, 5).every(2, sub(1)).fast(5).firstCycle()).toStrictEqual(
expect(sequence(3, 4, 5).firstOf(2, sub(1)).fast(5).firstCycle()).toStrictEqual(
sequence(2, 3, 4, 3, 4, 5, 2, 3, 4, 3, 4, 5, 2, 3, 4).firstCycle(),
);
expect(sequence(3, 4, 5).every(3, add(3)).every(2, sub(1)).fast(2).firstCycle()).toStrictEqual(
expect(sequence(3, 4, 5).firstOf(3, add(3)).firstOf(2, sub(1)).fast(2).firstCycle()).toStrictEqual(
sequence(5, 6, 7, 3, 4, 5).firstCycle(),
);
});
@@ -692,7 +693,7 @@ describe('Pattern', () => {
it('Can set the hap context', () => {
expect(
pure('a')
._setContext([
.setContext([
[
[0, 1],
[1, 2],
@@ -713,13 +714,13 @@ describe('Pattern', () => {
it('Can update the hap context', () => {
expect(
pure('a')
._setContext([
.setContext([
[
[0, 1],
[1, 2],
],
])
._withContext((c) => [
.withContext((c) => [
...c,
[
[3, 4],
@@ -743,7 +744,7 @@ describe('Pattern', () => {
});
describe('apply', () => {
it('Can apply a function', () => {
expect(sequence('a', 'b')._apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle());
expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle());
}),
it('Can apply a pattern of functions', () => {
expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle());
@@ -784,18 +785,18 @@ describe('Pattern', () => {
});
describe('jux', () => {
it('Can juxtapose', () => {
expect(pure({ a: 1 }).jux(fast(2))._sortHapsByPart().firstCycle()).toStrictEqual(
expect(pure({ a: 1 }).jux(fast(2)).sortHapsByPart().firstCycle()).toStrictEqual(
stack(pure({ a: 1, pan: 0 }), pure({ a: 1, pan: 1 }).fast(2))
._sortHapsByPart()
.sortHapsByPart()
.firstCycle(),
);
});
});
describe('juxBy', () => {
it('Can juxtapose by half', () => {
expect(pure({ a: 1 }).juxBy(0.5, fast(2))._sortHapsByPart().firstCycle()).toStrictEqual(
expect(pure({ a: 1 }).juxBy(0.5, fast(2)).sortHapsByPart().firstCycle()).toStrictEqual(
stack(pure({ a: 1, pan: 0.25 }), pure({ a: 1, pan: 0.75 }).fast(2))
._sortHapsByPart()
.sortHapsByPart()
.firstCycle(),
);
});
@@ -805,7 +806,7 @@ describe('Pattern', () => {
expect(
sequence('a', ['a', 'a'])
.fmap((a) => fastcat('b', 'c'))
._squeezeJoin()
.squeezeJoin()
.firstCycle(),
).toStrictEqual(
sequence(
@@ -820,7 +821,7 @@ describe('Pattern', () => {
it('Squeezes to the correct cycle', () => {
expect(
pure(time.struct(true))
._squeezeJoin()
.squeezeJoin()
.queryArc(3, 4)
.map((x) => x.value),
).toStrictEqual([Fraction(3.5)]);
@@ -857,7 +858,7 @@ describe('Pattern', () => {
);
});
it('Can chop(2,3)', () => {
expect(pure({ sound: 'a' }).fast(2).chop(2, 3)._sortHapsByPart().firstCycle()).toStrictEqual(
expect(pure({ sound: 'a' }).fast(2).chop(2, 3).sortHapsByPart().firstCycle()).toStrictEqual(
sequence(
[
{ sound: 'a', begin: 0, end: 0.5 },
@@ -869,7 +870,7 @@ describe('Pattern', () => {
{ sound: 'a', begin: 2 / 3, end: 1 },
],
)
._sortHapsByPart()
.sortHapsByPart()
.firstCycle(),
);
});
@@ -893,4 +894,11 @@ describe('Pattern', () => {
);
});
});
describe('alignments', () => {
it('Can squeeze arguments', () => {
expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(
sequence(5, 6, 6, 7).firstCycle()
);
});
});
});
+2 -1
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { pure } from '../pattern.mjs';
import pattern from '../pattern.mjs';
const { pure } = pattern;
import {
isNote,
tokenizeNote,
+122
View File
@@ -0,0 +1,122 @@
import strudel from '@strudel.cycles/core';
const { getFrequency, logger, Pattern } = strudel;
import { getAudioContext } from '@strudel.cycles/webaudio';
import csd from './project.csd?raw';
// import livecodeOrc from './livecode.orc?raw';
import presetsOrc from './presets.orc?raw';
let csoundLoader, _csound;
// triggers given instrument name using csound.
Pattern.prototype._csound = function (instrument) {
instrument = instrument || 'triangle';
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
// TODO: find a alternative way to wait for csound to load (to wait with first time playback)
return this.onTrigger((time, hap) => {
if (!_csound) {
logger('[csound] not loaded yet', 'warning');
return;
}
if (typeof hap.value !== 'object') {
throw new Error('csound only support objects as hap values');
}
let { gain = 0.8 } = hap.value;
gain *= 0.2;
const freq = Math.round(getFrequency(hap));
const controls = Object.entries({ ...hap.value, freq })
.flat()
.join('/');
// TODO: find out how to send a precise ctx based time
// http://www.csounds.com/manual/html/i.html
const params = [
`"${instrument}"`, // p1: instrument name
time - getAudioContext().currentTime, //.toFixed(precision), // p2: starting time in arbitrary unit called beats
hap.duration + 0, // p3: duration in beats
// instrument specific params:
freq, //.toFixed(precision), // p4: frequency
gain, // p5: gain
`"${controls}"`, // p6 controls as string (like superdirt osc message)
];
const msg = `i ${params.join(' ')}`;
_csound.inputMessage(msg);
});
};
// initializes csound + can be used to reevaluate given instrument code
export async function csound(code = '') {
await init();
if (code) {
code = `${code}`;
// ^ ^
// wrapping in backticks makes sure it works when calling as templated function
await _csound?.evalCode(code);
}
}
Pattern.prototype.define('csound', (a, pat) => pat.csound(a), { composable: false, patternified: true });
function eventLogger(type, args) {
const [msg] = args;
if (
type === 'message' &&
(['[commit: HEAD]'].includes(msg) ||
msg.startsWith('--Csound version') ||
msg.startsWith('libsndfile') ||
msg.startsWith('sr =') ||
msg.startsWith('0dBFS') ||
msg.startsWith('audio buffered') ||
msg.startsWith('writing') ||
msg.startsWith('SECTION 1:'))
) {
// ignore
return;
}
let logType = 'info';
if (msg.startsWith('error:')) {
logType = 'error';
}
logger(`[csound] ${msg || ''}`, logType);
}
async function load() {
if (window.__csound__) {
// allows using some other csound instance
_csound = window.__csound__;
} else {
const { Csound } = await import('@csound/browser');
_csound = await Csound({ audioContext: getAudioContext() });
}
_csound.removeAllListeners('message');
['message'].forEach((k) => _csound.on(k, (...args) => eventLogger(k, args)));
await _csound.setOption('-m0d'); // see -m flag https://csound.com/docs/manual/CommandFlags.html
await _csound.setOption('--sample-accurate');
await _csound.compileCsdText(csd);
// await _csound.compileOrc(livecodeOrc);
await _csound.compileOrc(presetsOrc);
await _csound.start();
return _csound;
}
async function init() {
csoundLoader = csoundLoader || load();
return csoundLoader;
}
let orcCache = {};
export async function loadOrc(url) {
await init();
if (typeof url !== 'string') {
throw new Error('loadOrc: expected url string');
}
if (url.startsWith('github:')) {
const [_, path] = url.split('github:');
url = `https://raw.githubusercontent.com/${path}`;
}
if (!orcCache[url]) {
orcCache[url] = fetch(url)
.then((res) => res.text())
.then((code) => _csound.compileOrc(code));
}
await orcCache[url];
}
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
{
"name": "@strudel.cycles/csound",
"version": "0.3.0",
"description": "csound bindings for strudel",
"main": "csound.mjs",
"scripts": {
"test": "echo \"No tests present.\" && exit 0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tidalcycles/strudel.git"
},
"keywords": [
"tidalcycles",
"strudel",
"pattern",
"livecoding",
"algorave"
],
"author": "Felix Roos <flix91@gmail.com>",
"contributors": [
"Alex McLean <alex@slab.org>"
],
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/tidalcycles/strudel/issues"
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@csound/browser": "^6.18.3"
}
}
+96
View File
@@ -0,0 +1,96 @@
; returns value of given key in given "string map"
; keymap("freq", "note/c3/freq/220/gain/0.5")
; yields "220"
opcode keymap, S, SS
Skey, Smap xin
idelimiter = strindex(Smap, strcat(Skey, "/"))
ifrom = idelimiter + strlen(Skey) + 1
Svalue = strsub(Smap, ifrom, strlen(Smap))
Svalue = strsub(Svalue, 0, strindex(Svalue, "/"))
xout Svalue
endop
; TODO add incredibly dope synths
instr organ
iduration = p3
ifreq = p4
igain = p5
ioct = octcps(ifreq)
asig = vco2(igain, ifreq, 12, .5) ; my edit
kpwm = oscili(.1, 5)
asig = vco2(igain, ifreq, 4, .5 + kpwm)
asig += vco2(igain/4, ifreq * 2)
; filter
; idepth = 2
; acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) ; filter envelope
; asig = zdf_2pole(asig, cpsoct(ioct + acut), 0.5)
; amp envelope
iattack = .001
irelease = .05
asig *= linsegr:a(0, iattack, 1, iduration, 1, irelease, 0)
out(asig, asig)
endin
instr triangle
iduration = p3
ifreq = p4
igain = p5
ioct = octcps(ifreq)
asig = vco2(igain, ifreq, 12, .5)
; amp envelope
iattack = .001
irelease = .05
asig *= linsegr:a(0, iattack, 1, iduration, 1, irelease, 0)
out(asig, asig)
endin
instr pad
iduration = p3
ifreq = p4
igain = p5
ioct = octcps(ifreq)
asig = vco2(igain, ifreq, 0)
; amp envelope
iattack = .5
irelease = .1
asig *= linsegr:a(0, iattack, 1, iduration, 1, irelease, 0)
idepth = 2
acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0)
asig = zdf_2pole(asig, 1000, 2)
out(asig, asig)
endin
gisine ftgen 0, 0, 4096, 10, 1
instr bow
kpres = 2
krat = 0.16
kvibf = 6.12723
kvib linseg 0, 0.5, 0, 1, 1, p3-0.5, 1
kvamp = kvib * 0.01
asig wgbow .7, p4, kpres, krat, kvibf, kvamp, gisine
asig = asig*p5
outs asig, asig
endin
instr Meta
Smap = strget(p6)
Sinstrument = keymap("s", Smap)
schedule(Sinstrument, 0, p3, p4, p5)
; TODO find a way to pipe Sinstrument through effects
endin
+10
View File
@@ -0,0 +1,10 @@
<CsoundSynthesizer>
<CsInstruments>
sr=48000
ksmps=64
nchnls=2
0dbfs=1
</CsInstruments>
</CsoundSynthesizer>
+2 -2
View File
@@ -4,9 +4,9 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { evaluate as _evaluate } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
import shapeshifter from './shapeshifter.mjs';
export const evaluate = async (code) => {
return _evaluate(code, shapeshifter);
return core.evaluate(code, shapeshifter);
};
+1 -1
View File
@@ -21,7 +21,7 @@ import {
import shiftCodegen from 'shift-codegen';
const codegen = shiftCodegen.default || shiftCodegen; // parcel module resolution fuckup
import * as strudel from '@strudel.cycles/core';
import strudel from '@strudel.cycles/core';
const { Pattern } = strudel;
+2 -2
View File
@@ -8,12 +8,12 @@ import { expect, describe, it } from 'vitest';
import { evaluate } from '../evaluate.mjs';
import { mini } from '@strudel.cycles/mini';
import * as strudel from '@strudel.cycles/core';
import strudel from '@strudel.cycles/core';
const { fastcat, evalScope } = strudel;
describe('evaluate', async () => {
await evalScope({ mini }, strudel);
const ev = async (code) => (await evaluate(code)).pattern._firstCycleValues;
const ev = async (code) => (await evaluate(code)).pattern.firstCycleValues;
it('Should evaluate strudel functions', async () => {
expect(await ev('pure("c3")')).toEqual(['c3']);
expect(await ev('cat("c3")')).toEqual(['c3']);
+2 -1
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import * as _WebMidi from 'webmidi';
import { Pattern, isPattern, isNote, getPlayableNoteValue, logger } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, isPattern, isNote, getPlayableNoteValue, logger } = core;
import { getAudioContext } from '@strudel.cycles/webaudio';
// if you use WebMidi from outside of this package, make sure to import that instance:
+10 -7
View File
@@ -5,10 +5,10 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import * as krill from './krill-parser.js';
import * as strudel from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
// import { addMiniLocations } from '@strudel.cycles/eval/shapeshifter.mjs';
const { pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence, reify } = strudel;
const { pure, Pattern, stack, slowcat, sequence, timeCat, silence, reify, rand, chooseInWith, Fraction } = core;
var _seedState = 0;
const randOffset = 0.0002;
@@ -23,14 +23,15 @@ const applyOptions = (parent) => (pat, i) => {
const operator = options?.operator;
if (operator) {
switch (operator.type_) {
case 'stretch':
case 'stretch': {
const speed = Fraction(operator.arguments_.amount).inverse();
return reify(pat).fast(speed);
}
case 'bjorklund':
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
case 'degradeBy':
return reify(pat)._degradeByWith(
strudel.rand.early(randOffset * _nextSeed()).segment(1),
rand.early(randOffset * _nextSeed()).segment(1),
operator.arguments_.amount,
);
// TODO: case 'fixed-step': "%"
@@ -87,7 +88,7 @@ function resolveReplications(ast) {
export function patternifyAST(ast) {
switch (ast.type_) {
case 'pattern':
case 'pattern': {
resolveReplications(ast);
const children = ast.source_.map(patternifyAST).map(applyOptions(ast));
const alignment = ast.arguments_.alignment;
@@ -95,7 +96,7 @@ export function patternifyAST(ast) {
return stack(...children);
}
if (alignment === 'r') {
return strudel.chooseInWith(strudel.rand.early(randOffset * _nextSeed()).segment(1), children);
return chooseInWith(rand.early(randOffset * _nextSeed()).segment(1), children);
}
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === 't') {
@@ -110,7 +111,8 @@ export function patternifyAST(ast) {
return pat;
}
return sequence(...children);
case 'element':
}
case 'element': {
if (ast.source_ === '~') {
return silence;
}
@@ -129,6 +131,7 @@ export function patternifyAST(ast) {
return pure(value).withLocation([start.line, start.column, start.offset], [end.line, end.column, end.offset]);
}
return patternifyAST(ast.source_);
}
case 'stretch':
return patternifyAST(ast.source_).slow(ast.arguments_.amount);
/* case 'scale':
+2 -2
View File
@@ -9,8 +9,8 @@ import '@strudel.cycles/core/euclid.mjs';
import { describe, expect, it } from 'vitest';
describe('mini', () => {
const minV = (v) => mini(v)._firstCycleValues;
const minS = (v) => mini(v)._showFirstCycle;
const minV = (v) => mini(v).firstCycleValues;
const minS = (v) => mini(v).showFirstCycle;
it('supports single elements', () => {
expect(minV('a')).toEqual(['a']);
});
+2 -1
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import OSC from 'osc-js';
import { logger, parseNumeral, Pattern } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, logger, parseNumeral } = core;
let connection; // Promise<OSC>
function connect() {
+3 -2
View File
@@ -13,12 +13,13 @@ npm i @strudel.cycles/react
Here is a minimal example of how to set up a MiniRepl:
```jsx
import { evalScope, controls } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const {evalScope} = core;
import { MiniRepl } from '@strudel.cycles/react';
import { prebake } from '../repl/src/prebake.mjs';
evalScope(
controls,
core,
import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
+1 -1
View File
File diff suppressed because one or more lines are too long
+35 -34
View File
@@ -7,7 +7,7 @@ import { tags as r } from "@lezer/highlight";
import { createTheme as Z } from "@uiw/codemirror-themes";
import { useInView as ee } from "react-hook-inview";
import { webaudioOutput as te, getAudioContext as re } from "@strudel.cycles/webaudio";
import { repl as oe } from "@strudel.cycles/core";
import oe from "@strudel.cycles/core";
import { transpiler as ne } from "@strudel.cycles/transpiler";
const ae = Z({
theme: "dark",
@@ -74,11 +74,11 @@ const B = $.define(), se = G.define({
for (let o of t.effects)
if (o.is(z)) {
const a = o.value.map(
(s) => (s.context.locations || []).map(({ start: f, end: d }) => {
const u = s.context.color || "#FFCA28";
let c = t.newDoc.line(f.line).from + f.column, i = t.newDoc.line(d.line).from + d.column;
(s) => (s.context.locations || []).map(({ start: u, end: d }) => {
const f = s.context.color || "#FFCA28";
let c = t.newDoc.line(u.line).from + u.column, i = t.newDoc.line(d.line).from + d.column;
const m = t.newDoc.length;
return c > m || i > m ? void 0 : E.mark({ attributes: { style: `outline: 1.5px solid ${u};` } }).range(c, i);
return c > m || i > m ? void 0 : E.mark({ attributes: { style: `outline: 1.5px solid ${f};` } }).range(c, i);
})
).flat().filter(Boolean) || [];
e = E.set(a, !0);
@@ -90,13 +90,13 @@ const B = $.define(), se = G.define({
},
provide: (e) => U.decorations.from(e)
}), le = [Y(), ae, ie, se];
function de({ value: e, onChange: t, onViewChanged: o, onSelectionChange: a, options: s, editorDidMount: f }) {
function de({ value: e, onChange: t, onViewChanged: o, onSelectionChange: a, options: s, editorDidMount: u }) {
const d = _(
(i) => {
t?.(i);
},
[t]
), u = _(
), f = _(
(i) => {
o?.(i);
},
@@ -110,7 +110,7 @@ function de({ value: e, onChange: t, onViewChanged: o, onSelectionChange: a, opt
return /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement(X, {
value: e,
onChange: d,
onCreateEditor: u,
onCreateEditor: f,
onUpdate: c,
extensions: le
}));
@@ -119,21 +119,21 @@ function K(...e) {
return e.filter(Boolean).join(" ");
}
function ue({ view: e, pattern: t, active: o, getTime: a }) {
const s = H([]), f = H();
const s = H([]), u = H();
L(() => {
if (e)
if (t && o) {
let u = function() {
let d = requestAnimationFrame(function f() {
try {
const c = a(), m = [Math.max(f.current || c, c - 1 / 10, 0), c + 1 / 60];
f.current = m[1], s.current = s.current.filter((g) => g.whole.end > c);
const c = a(), m = [Math.max(u.current || c, c - 1 / 10, 0), c + 1 / 60];
u.current = m[1], s.current = s.current.filter((g) => g.whole.end > c);
const h = t.queryArc(...m).filter((g) => g.hasOnset());
s.current = s.current.concat(h), e.dispatch({ effects: z.of(s.current) });
} catch {
e.dispatch({ effects: z.of([]) });
}
d = requestAnimationFrame(u);
}, d = requestAnimationFrame(u);
d = requestAnimationFrame(f);
});
return () => {
cancelAnimationFrame(d);
};
@@ -177,20 +177,21 @@ function O({ type: e }) {
function Ee(e) {
return L(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), _((t) => window.postMessage(t, "*"), []);
}
function we({
const we = oe.repl;
function ye({
defaultOutput: e,
interval: t,
getTime: o,
evalOnMount: a = !1,
initialCode: s = "",
autolink: f = !1,
autolink: u = !1,
beforeEval: d,
afterEval: u,
afterEval: f,
onEvalError: c,
onToggle: i
}) {
const m = V(() => ye(), []), [h, g] = w(), [C, N] = w(), [p, y] = w(s), [M, S] = w(), [k, D] = w(), [F, x] = w(!1), b = p !== M, { scheduler: A, evaluate: T, start: J, stop: q, pause: Q } = V(
() => oe({
const m = V(() => ke(), []), [h, g] = w(), [C, N] = w(), [p, y] = w(s), [M, S] = w(), [k, D] = w(), [F, x] = w(!1), b = p !== M, { scheduler: A, evaluate: T, start: J, stop: q, pause: Q } = V(
() => we({
interval: t,
defaultOutput: e,
onSchedulerError: g,
@@ -203,7 +204,7 @@ function we({
y(l), d?.();
},
afterEval: ({ pattern: l, code: P }) => {
S(P), D(l), N(), g(), f && (window.location.hash = "#" + encodeURIComponent(btoa(P))), u?.();
S(P), D(l), N(), g(), u && (window.location.hash = "#" + encodeURIComponent(btoa(P))), f?.();
},
onToggle: (l) => {
x(l), i?.(l);
@@ -243,16 +244,16 @@ function we({
}
};
}
function ye() {
function ke() {
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
}
const ke = () => re().currentTime;
function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
const _e = () => re().currentTime;
function Te({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
const {
code: s,
setCode: f,
setCode: u,
evaluate: d,
activateCode: u,
activateCode: f,
error: c,
isDirty: i,
activeCode: m,
@@ -261,10 +262,10 @@ function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
scheduler: C,
togglePlay: N,
stop: p
} = we({
} = ye({
initialCode: e,
defaultOutput: te,
getTime: ke
getTime: _e
}), [y, M] = w(), [S, k] = ee({
threshold: 0.01
}), D = H(), F = V(() => ((k || !t) && (D.current = !0), k || D.current), [k, t]);
@@ -276,7 +277,7 @@ function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
}), j(() => {
if (a) {
const x = async (b) => {
(b.ctrlKey || b.altKey) && (b.code === "Enter" ? (b.preventDefault(), ce(y), await u()) : b.code === "Period" && (p(), b.preventDefault()));
(b.ctrlKey || b.altKey) && (b.code === "Enter" ? (b.preventDefault(), ce(y), await f()) : b.code === "Period" && (p(), b.preventDefault()));
};
return window.addEventListener("keydown", x, !0), () => window.removeEventListener("keydown", x, !0);
}
@@ -294,7 +295,7 @@ function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
type: g ? "pause" : "play"
})), /* @__PURE__ */ n.createElement("button", {
className: K(i ? v.button : v.buttonDisabled),
onClick: () => u()
onClick: () => f()
}, /* @__PURE__ */ n.createElement(O, {
type: "refresh"
}))), c && /* @__PURE__ */ n.createElement("div", {
@@ -303,18 +304,18 @@ function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
className: v.body
}, F && /* @__PURE__ */ n.createElement(de, {
value: s,
onChange: f,
onChange: u,
onViewChanged: M
})));
}
const Te = (e) => j(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
const Ve = (e) => j(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
export {
de as CodeMirror,
Se as MiniRepl,
Te as MiniRepl,
K as cx,
ce as flash,
ue as useHighlighting,
Te as useKeydown,
Ve as useKeydown,
Ee as usePostMessage,
we as useStrudel
ye as useStrudel
};
@@ -1,4 +1,5 @@
import { evalScope, controls } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { evalScope } = core;
import { getAudioContext, panic, webaudioOutput } from '@strudel.cycles/webaudio';
import { useCallback, useState } from 'react';
import CodeMirror, { flash } from '../../../src/components/CodeMirror6';
@@ -10,8 +11,8 @@ import './style.css';
// TODO: only import stuff when play is pressed?
evalScope(
controls,
import('@strudel.cycles/core'),
core,
// import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
// import('@strudel.cycles/midi'), // TODO: find out why midi loads tone.js
import('@strudel.cycles/tonal'),
+2 -1
View File
@@ -1,7 +1,8 @@
import React from 'react';
import { MiniRepl } from './components/MiniRepl';
import 'tailwindcss/tailwind.css';
import { controls, evalScope } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const {controls, evalScope} = core;
evalScope(
controls,
+2 -5
View File
@@ -7,9 +7,7 @@ function useHighlighting({ view, pattern, active, getTime }) {
useEffect(() => {
if (view) {
if (pattern && active) {
let frame = requestAnimationFrame(updateHighlights);
function updateHighlights() {
let frame = requestAnimationFrame(function updateHighlights() {
try {
const audioTime = getTime();
// force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away
@@ -25,8 +23,7 @@ function useHighlighting({ view, pattern, active, getTime }) {
view.dispatch({ effects: setHighlights.of([]) });
}
frame = requestAnimationFrame(updateHighlights);
}
});
return () => {
cancelAnimationFrame(frame);
};
+2 -1
View File
@@ -1,5 +1,6 @@
import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
import { repl } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const repl = core.repl
import { transpiler } from '@strudel.cycles/transpiler';
import usePostMessage from './usePostMessage.mjs';
+62 -18
View File
@@ -4,9 +4,10 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, isPattern } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, isPattern } = core;
var serialWriter;
var writeMessage;
var choosing = false;
export async function getWriter(br = 38400) {
@@ -14,17 +15,30 @@ export async function getWriter(br = 38400) {
return;
}
choosing = true;
if (serialWriter) {
return serialWriter;
if (writeMessage) {
return writeMessage;
}
if ('serial' in navigator) {
const port = await navigator.serial.requestPort();
await port.open({ baudRate: br });
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
const writer = textEncoder.writable.getWriter();
serialWriter = function (message) {
writer.write(message);
const encoder = new TextEncoder();
const writer = port.writable.getWriter();
writeMessage = function (message, chk) {
const encoded = encoder.encode(message)
if (!chk) {
writer.write(encoded);
}
else {
const bytes = new Uint8Array(4);
bytes[0] = 124; // | symbol
bytes[1] = (chk >> 8) & 0xFF;
bytes[2] = chk & 0xFF;
bytes[3] = 59; // semicolon
const withchk = new Uint8Array(encoded.length+4)
withchk.set(encoded);
withchk.set(bytes, encoded.length);
writer.write(withchk);
}
};
} else {
throw 'Webserial is not available in this browser.';
@@ -33,18 +47,41 @@ export async function getWriter(br = 38400) {
const latency = 0.1;
Pattern.prototype.serial = function (...args) {
return this._withHap((hap) => {
if (!serialWriter) {
getWriter(...args);
// crc16 (CCITT-FALSE) https://gist.github.com/tijnkooijmans/10981093
function crc16(data) {
const length = data.length
if (length == 0) {
return 0;
}
var crc = 0xFFFF;
for (var i = 0; i < length; ++i) {
crc ^= data.charCodeAt(i) << 8;
for (var j = 0; j < 8; ++j) {
crc = (crc & 0x8000) > 0 ? (crc << 1) ^ 0x1021 : crc << 1;
}
}
return crc & 0xffff;
}
Pattern.prototype.serial = function (br=38400,sendcrc=false,singlecharids=false) {
return this.withHap((hap) => {
if (!writeMessage) {
getWriter(br);
}
const onTrigger = (time, hap, currentTime) => {
var message = '';
var chk = 0;
if (typeof hap.value === 'object') {
if ('action' in hap.value) {
message += hap.value['action'] + '(';
var action = hap.value['action'];
if (singlecharids) {
action = action.charAt(0);
}
message += action + '(';
var first = true;
for (const [key, val] of Object.entries(hap.value)) {
for (var [key, val] of Object.entries(hap.value)) {
if (key === 'action') {
continue;
}
@@ -53,19 +90,26 @@ Pattern.prototype.serial = function (...args) {
} else {
message += ',';
}
message += `${key}:${val}`;
if (singlecharids) {
key = key.charAt(0);
}
message += key + ':' + val;
}
message += ')';
if (sendcrc) {
chk = crc16(message)
}
} else {
for (const [key, val] of Object.entries(hap.value)) {
message += `${key}:${val};`;
message += `${key}:${val}`;
}
}
} else {
message = hap.value;
}
const offset = (time - currentTime + latency) * 1000;
window.setTimeout(serialWriter, offset, message);
window.setTimeout(function () {writeMessage(message, chk)}, offset);
};
return hap.setContext({ ...hap.context, onTrigger, dominantTrigger: true });
});
+3
View File
@@ -1,3 +1,6 @@
import core from '@strudel.cycles/core';
const toMidi = core.toMidi;
let loadCache = {};
async function loadFont(name) {
if (loadCache[name]) {
+3 -1
View File
@@ -1,4 +1,6 @@
import { Pattern } from '@strudel.cycles/core';
import strudel from '@strudel.cycles/core';
const { Pattern, getPlayableNoteValue, toMidi } = strudel;
import { getAudioContext } from '@strudel.cycles/webaudio';
import { loadSoundfont as _loadSoundfont, startPresetNote } from 'sfumato';
Pattern.prototype.soundfont = function (sf, n = 0) {
+1 -1
View File
@@ -26,7 +26,7 @@
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.1",
"@tonaljs/tonal": "^4.6.5",
"@tonaljs/tonal": "^4.7.2",
"chord-voicings": "^0.0.1",
"webmidi": "^3.0.21"
}
+3 -2
View File
@@ -7,11 +7,12 @@ This program is free software: you can redistribute it and/or modify it under th
// import { strict as assert } from 'assert';
import '../tonal.mjs'; // need to import this to add prototypes
import { pure } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { pure } = core;
import { describe, it, expect } from 'vitest';
describe('tonal', () => {
it('Should run tonal functions ', () => {
expect(pure('c3').scale('C major').scaleTranspose(1)._firstCycleValues).toEqual(['D3']);
expect(pure('c3').scale('C major').scaleTranspose(1).firstCycleValues).toEqual(['D3']);
});
});
+5 -4
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { Note, Interval, Scale } from '@tonaljs/tonal';
import { Pattern, mod } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, pattern, mod } = core;
// transpose note inside scale by offset steps
// function scaleOffset(scale: string, offset: number, note: string) {
@@ -75,7 +76,7 @@ function scaleOffset(scale, offset, note) {
*/
Pattern.prototype._transpose = function (intervalOrSemitones) {
return this._withHap((hap) => {
return this.withHap((hap) => {
const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones /* as number */)
: String(intervalOrSemitones);
@@ -111,7 +112,7 @@ Pattern.prototype._transpose = function (intervalOrSemitones) {
*/
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
return this._withHap((hap) => {
return this.withHap((hap) => {
if (!hap.context.scale) {
throw new Error('can only use scaleTranspose after .scale');
}
@@ -142,7 +143,7 @@ Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
*/
Pattern.prototype._scale = function (scale /* : string */) {
return this._withHap((hap) => {
return this.withHap((hap) => {
let note = hap.value;
const asNumber = Number(note);
if (!isNaN(asNumber)) {
+6 -4
View File
@@ -4,7 +4,9 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern as _Pattern, stack, Hap, reify } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, stack, reify, Hap } = core
import _voicings from 'chord-voicings';
const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings.default || _voicings; // parcel module resolution fuckup
@@ -17,7 +19,7 @@ const getVoicing = (chord, lastVoicing, range = ['F3', 'A4']) =>
lastVoicing,
});
const Pattern = _Pattern;
// const Pattern = _Pattern;
Pattern.prototype.fmapNested = function (func) {
return new Pattern((span) =>
@@ -51,7 +53,7 @@ Pattern.prototype.voicings = function (range) {
}
return this.fmapNested((event) => {
lastVoicing = getVoicing(event.value, lastVoicing, range);
return stack(...lastVoicing)._withContext(() => ({
return stack(...lastVoicing).withContext(() => ({
locations: event.context.locations || [],
}));
});
@@ -59,7 +61,7 @@ Pattern.prototype.voicings = function (range) {
Pattern.prototype._rootNotes = function (octave = 2) {
return this.fmap((value) => {
const [_, root] = value.match(/^([a-gA-G][b#]?).*$/);
const root = value.match(/^([a-gA-G][b#]?).*$/)[1];
return root + octave;
});
};
+3 -2
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import '../tone.mjs';
import { pure } from '@strudel.cycles/core';
import strudel from '@strudel.cycles/core';
const { pure } = strudel;
import { describe, it, expect } from 'vitest';
describe('tone', () => {
@@ -13,6 +14,6 @@ describe('tone', () => {
// const s = synth().chain(out()); // TODO: mock audio context?
// assert.deepStrictEqual(s, new Tone.Synth().chain(out()));
const s = {};
expect(pure('c3').tone(s)._firstCycleValues).toEqual(['c3']);
expect(pure('c3').tone(s).firstCycleValues).toEqual(['c3']);
});
});
+3 -1
View File
@@ -4,7 +4,9 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern } = core;
import * as _Tone from 'tone';
// import Tone from here, to make sure to get the same AudioContext
+2 -2
View File
@@ -1,5 +1,5 @@
import { evaluate as _evaluate } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
import { transpiler } from './transpiler.mjs';
export * from './transpiler.mjs';
export const evaluate = (code) => _evaluate(code, transpiler);
export const evaluate = (code) => core.evaluate(code, transpiler);
+2 -1
View File
@@ -1,7 +1,8 @@
import escodegen from 'escodegen';
import { parse } from 'acorn';
import { walk } from 'estree-walker';
import { isNote } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { isNote } = core;
export function transpiler(input, options = {}) {
const { wrapAsync = false, addReturn = true, simpleLocs = false } = options;
+3 -1
View File
@@ -1,4 +1,6 @@
import { logger } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { logger, toMidi } = core;
import { getAudioContext } from './index.mjs';
const bufferCache = {}; // string: Promise<ArrayBuffer>
const loadCache = {}; // string: Promise<ArrayBuffer>
+3 -4
View File
@@ -4,9 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// import { Pattern, getFrequency, patternify2 } from '@strudel.cycles/core';
import * as strudel from '@strudel.cycles/core';
import { fromMidi, logger, toMidi } from '@strudel.cycles/core';
import strudel from '@strudel.cycles/core';
const { fromMidi, logger, toMidi } = strudel;
import './feedbackdelay.mjs';
import './reverb.mjs';
import { getSampleBufferSource } from './sampler.mjs';
@@ -157,7 +156,7 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
if (!delays[orbit]) {
const ac = getAudioContext();
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
dly.start(t);
dly.start?.(t); // for some reason, this throws when audion extension is installed..
dly.connect(getDestination());
delays[orbit] = dly;
}
+2 -1
View File
@@ -5,7 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import Tune from './tunejs.js';
import { Pattern } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern } = core;
Pattern.prototype._tune = function (scale, tonic = 220) {
const tune = new Tune();
+2 -1
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Pattern, mod } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, mod } = core;
export function edo(name) {
if (!/^[1-9]+[0-9]*edo$/.test(name)) {
+4 -2
View File
@@ -4,7 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { cleanupDraw, cleanupUi, controls, evalScope, logger } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { cleanupDraw, cleanupUi, evalScope, logger } = core;
import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react';
import {
getAudioContext,
@@ -42,11 +43,12 @@ const modules = [
import('@strudel.cycles/osc'),
import('@strudel.cycles/serial'),
import('@strudel.cycles/soundfonts'),
import('@strudel.cycles/csound'),
];
evalScope(
// Tone,
controls, // sadly, this cannot be exported from core direclty
core,
{ WebDirt },
...modules,
);
+2 -1
View File
@@ -1,5 +1,6 @@
import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon';
import { logger } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { logger } = core;
import { cx } from '@strudel.cycles/react';
import { nanoid } from 'nanoid';
import React, { useContext, useCallback, useLayoutEffect, useRef, useState } from 'react';
+2 -1
View File
@@ -1,4 +1,5 @@
import { Pattern, toMidi } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
const { Pattern, toMidi } = core;
import { samples } from '@strudel.cycles/webaudio';
export async function prebake({ isMock = false, baseDir = '.' } = {}) {
+20 -15
View File
@@ -5,8 +5,8 @@
// import * as tunes from './tunes.mjs';
// import { evaluate } from '@strudel.cycles/eval';
import { evaluate } from '@strudel.cycles/transpiler';
import { evalScope } from '@strudel.cycles/core';
import * as strudel from '@strudel.cycles/core';
import strudel from '@strudel.cycles/core';
const { Pattern, evalScope } = strudel;
import * as webaudio from '@strudel.cycles/webaudio';
import controls from '@strudel.cycles/core/controls.mjs';
// import gist from '@strudel.cycles/core/gist.js';
@@ -85,48 +85,51 @@ const toneHelpersMocked = {
highpass: mockNode,
};
strudel.Pattern.prototype.osc = function () {
Pattern.prototype.osc = function () {
return this;
};
strudel.Pattern.prototype.tone = function () {
Pattern.prototype.csound = function () {
return this;
};
strudel.Pattern.prototype.webdirt = function () {
Pattern.prototype.tone = function () {
return this;
};
Pattern.prototype.webdirt = function () {
return this;
};
// draw mock
strudel.Pattern.prototype.pianoroll = function () {
Pattern.prototype.pianoroll = function () {
return this;
};
// speak mock
strudel.Pattern.prototype.speak = function () {
Pattern.prototype.speak = function () {
return this;
};
// webaudio mock
strudel.Pattern.prototype.wave = function () {
Pattern.prototype.wave = function () {
return this;
};
strudel.Pattern.prototype.filter = function () {
Pattern.prototype.filter = function () {
return this;
};
strudel.Pattern.prototype.adsr = function () {
Pattern.prototype.adsr = function () {
return this;
};
strudel.Pattern.prototype.out = function () {
Pattern.prototype.out = function () {
return this;
};
strudel.Pattern.prototype.soundfont = function () {
Pattern.prototype.soundfont = function () {
return this;
};
// tune mock
strudel.Pattern.prototype.tune = function () {
Pattern.prototype.tune = function () {
return this;
};
strudel.Pattern.prototype.midi = function () {
Pattern.prototype.midi = function () {
return this;
};
@@ -156,7 +159,7 @@ const loadSoundfont = () => {};
evalScope(
// Tone,
strudel,
strudel.Pattern.prototype.bootstrap(),
Pattern.prototype.bootstrap(),
toneHelpersMocked,
uiHelpersMocked,
controls,
@@ -170,6 +173,8 @@ evalScope(
{
// gist,
// euclid,
csound: id,
loadOrc: id,
mini,
getDrawContext,
getAudioContext,
File diff suppressed because it is too large Load Diff
+287
View File
@@ -162,3 +162,290 @@ stack(
drums.fast(2),
synths
).slow(2)`; */
export const tetrisMini = `note(\`[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
[c5 a4 a4 ~]
[[~ d5] [~ f5] a5 [g5 f5]]
[e5 [~ c5] e5 [d5 c5]]
[b4 [b4 c5] d5 e5]
[c5 a4 a4 ~]],
[[e2 e3]*4]
[[a2 a3]*4]
[[g#2 g#3]*2 [e2 e3]*2]
[a2 a3 a2 a3 a2 a3 b1 c2]
[[d2 d3]*4]
[[c2 c3]*4]
[[b1 b2]*2 [e2 e3]*2]
[[a1 a2]*4]\`).slow(16)
`;
export const giantStepsReggae = `stack(
// melody
seq(
"[F#5 D5] [B4 G4] Bb4 [B4 A4]",
"[D5 Bb4] [G4 Eb4] F#4 [G4 F4]",
"Bb4 [B4 A4] D5 [D#5 C#5]",
"F#5 [G5 F5] Bb5 [F#5 [F#5 ~@3]]",
),
// chords
seq(
"[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]",
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
)
.struct("~ [x ~]".fast(4*8))
.voicings(['E3', 'G4']),
// bass
seq(
"[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]",
"[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]",
"[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]",
"[B2 F#2] [F2 Bb2] [Eb2 Bb2] [C#2 F#2]"
)
.struct("x ~".fast(4*8))
).slow(25).note()`;
// TODO:
/*
export const xylophoneCalling = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const t = x => x.scaleTranspose("<0 2 4 3>/4").transpose(-2)
const s = x => x.scale(cat('C3 minor pentatonic','G3 minor pentatonic').slow(4))
const delay = new FeedbackDelay(1/8, .6).chain(vol(0.1), out());
const chorus = new Chorus(1,2.5,0.5).start();
stack(
// melody
"<<10 7> <8 3>>/4".struct("x*3").apply(s)
.scaleTranspose("<0 3 2> <1 4 3>")
.superimpose(scaleTranspose(2).early(1/8))
.apply(t).tone(polysynth().set({
...osc('triangle4'),
...adsr(0,.08,0)
}).chain(vol(0.2).connect(delay),chorus,out())).mask("<~@3 x>/16".early(1/8)),
// pad
"[1,3]/4".scale('G3 minor pentatonic').apply(t).tone(polysynth().set({
...osc('square2'),
...adsr(0.1,.4,0.8)
}).chain(vol(0.2),chorus,out())).mask("<~ x>/32"),
// xylophone
"c3,g3,c4".struct("<x*2 x>").fast("<1 <2!3 [4 8]>>").apply(s).scaleTranspose("<0 <1 [2 [3 <4 5>]]>>").apply(t).tone(polysynth().set({
...osc('sawtooth4'),
...adsr(0,.1,0)
}).chain(vol(0.4).connect(delay),out())).mask("<x@3 ~>/16".early(1/8)),
// bass
"c2 [c2 ~]*2".scale('C hirajoshi').apply(t).tone(synth({
...osc('sawtooth6'),
...adsr(0,.03,.4,.1)
}).chain(vol(0.4),out())),
// kick
"<c1!3 [c1 ~]*2>*2".tone(membrane().chain(vol(0.8),out())),
// snare
"~ <c3!7 [c3 c3*2]>".tone(noise().chain(vol(0.8),out())),
// hihat
"c3*4".transpose("[-24 0]*2").tone(metal(adsr(0,.02)).chain(vol(0.5).connect(delay),out()))
).slow(1)
// strudel disable-highlighting`;
*/
// TODO:
/*
export const sowhatelse = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
// mixer
const mix = (key) => vol({
chords: .2,
lead: 0.8,
bass: .4,
snare: .95,
kick: .9,
hihat: .35,
}[key]||0);
const delay = new FeedbackDelay(1/6, .3).chain(vol(.7), out());
const delay2 = new FeedbackDelay(1/6, .2).chain(vol(.15), out());
const chorus = new Chorus(1,2.5,0.5).start();
// instruments
const instr = (instrument) => ({
organ: polysynth().set({...osc('sawtooth4'), ...adsr(.01,.2,0)}).chain(mix('chords').connect(delay),out()),
lead: polysynth().set({...osc('triangle4'),...adsr(0.01,.05,0)}).chain(mix('lead').connect(delay2), out()),
bass: polysynth().set({...osc('sawtooth8'),...adsr(.02,.05,.3,.2)}).chain(mix('bass'),lowpass(3000), out()),
pad: polysynth().set({...osc('square2'),...adsr(0.1,.4,0.8)}).chain(vol(0.15),chorus,out()),
hihat: metal(adsr(0, .02, 0)).chain(mix('hihat'), out()),
snare: noise(adsr(0, .15, 0.01)).chain(mix('snare'), lowpass(5000), out()),
kick: membrane().chain(mix('kick'), out())
}[instrument]);
// harmony
const t = transpose("<0 0 1 0>/8");
const sowhat = scaleTranspose("0,3,6,9,11");
// track
stack(
"[<0 4 [3 [2 1]]>]/4".struct("[x]*3").mask("[~ x ~]").scale('D5 dorian').off(1/6, scaleTranspose(-7)).off(1/3, scaleTranspose(-5)).apply(t).tone(instr('lead')).mask("<~ ~ x x>/8"),
"<<e3 [~@2 a3]> <[d3 ~] [c3 f3] g3>>".scale('D dorian').apply(sowhat).apply(t).tone(instr('organ')).mask("<x x x ~>/8"),
"<[d2 [d2 ~]*3]!3 <a1*2 c2*3 [a1 e2]>>".apply(t).tone(instr('bass')),
"c1*6".tone(instr('hihat')),
"~ c3".tone(instr('snare')),
"<[c1@5 c1] <c1 [[c1@2 c1] ~] [c1 ~ c1] [c1!2 ~ c1!3]>>".tone(instr('kick')),
"[2,4]/4".scale('D dorian').apply(t).tone(instr('pad')).mask("<x x x ~>/8")
).fast(6/8)
// strudel disable-highlighting`;
*/
// TODO: rework tune to use freq
/*
export const jemblung = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const delay = new FeedbackDelay(1/8, .6).chain(vol(0.15), out());
const snare = noise({type:'white',...adsr(0,0.2,0)}).chain(lowpass(5000),vol(1.8),out());
const s = polysynth().set({...osc('sawtooth4'),...adsr(0.01,.2,.6,0.2)}).chain(vol(.23).connect(delay),out());
stack(
stack(
"0 1 4 [3!2 5]".layer(
// chords
x=>x.add("0,3").duration("0.05!3 0.02"),
// bass
x=>x.add("-8").struct("x*8").duration(0.1)
),
// melody
"12 11*3 12 ~".duration(0.005)
)
.add("<0 1>")
.tune("jemblung2")
//.mul(22/5).round().xen("22edo")
//.mul(12/5).round().xen("12edo")
.tone(s),
// kick
"[c2 ~]*2".duration(0.05).tone(membrane().chain(out())),
// snare
"[~ c1]*2".early(0.001).tone(snare),
// hihat
"c2*8".tone(noise().chain(highpass(6000),vol(0.5).connect(delay),out())),
).slow(3)`;
*/
/*
// TODO: does not work on linux (at least for me..)
export const speakerman = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
backgroundImage('https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fi.ytimg.com%2Fvi%2FXR0rKqW3VwY%2Fmaxresdefault.jpg&f=1&nofb=1',
{ className:'darken', style:'background-size:cover'})
stack(
"[g3,bb3,d4] [f3,a3,c4] [c3,e3,g3]@2".slow(2).late(.1),
cat(
'Baker man',
'is baking bread',
'Baker man',
'is baking bread',
'Sagabona',
'kunjani wena',
'Sagabona',
'kunjani wena',
'The night train, is coming',
'got to keep on running',
'The night train, is coming',
'got to keep on running',
).speak("en zu en".slow(12), "<0 2 3 4 5 6>".slow(2)),
).slow(4)`;
*/
export const bossa = `const scales = sequence('C minor', ['D locrian', 'G phrygian'], 'Bb2 minor', ['C locrian','F phrygian']).slow(4)
stack(
"<Cm7 [Dm7b5 G7b9] Bbm7 [Cm7b5 F7b9]>".fast(2).struct("x ~ x@3 x ~ x ~ ~ ~ x ~ x@3".late(1/8)).early(1/8).slow(2).voicings(),
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
).note().piano().slow(2)`;
/*
export const customTrigger = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// 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))
).s("<sawtooth square>/2")
.onTrigger((t,hap,ct)=>{
const ac = Tone.getContext().rawContext;
t = ac.currentTime + t - ct;
const { freq, s, gain = 1 } = hap.value;
const master = ac.createGain();
master.gain.value = 0.1 * gain;
master.connect(ac.destination);
const o = ac.createOscillator();
o.type = s || 'triangle';
o.frequency.value = Number(freq);
o.connect(master);
o.start(t);
o.stop(t + hap.duration);
}).stack(s("bd(3,8),hh*4,~ sd").webdirt())`; */
export const swimmingWithSoundfonts = `// Koji Kondo - Swimming (Super Mario World)
stack(
n(
"~",
"~",
"~",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [F5@2 C6] A5 G5",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [F5@2 C6] A5 G5",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2",
"A5 [F5@2 C5] A5 F5",
"Ab5 [F5@2 Ab5] G5@2",
"A5 [F5@2 C5] A5 F5",
"Ab5 [F5@2 C5] C6@2",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2"
).s('Sitar: Ethnic'),
n(
"[F4,Bb4,D5] [[D4,G4,Bb4]@2 [Bb3,D4,F4]] [[G3,C4,E4]@2 [[Ab3,F4] [A3,Gb4]]] [Bb3,E4,G4]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, Bb3, Db3] [F3, Bb3, Db3]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]",
"[~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]",
"[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]",
"[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]",
"[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]",
"[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]"
).s('Kalimba: Ethnic'),
n(
"[G3 G3 C3 E3]",
"[F2 D2 G2 C2]",
"[F2 D2 G2 C2]",
"[F2 A2 Bb2 B2]",
"[A2 Ab2 G2 C2]",
"[F2 A2 Bb2 B2]",
"[G2 C2 F2 F2]",
"[F2 A2 Bb2 B2]",
"[A2 Ab2 G2 C2]",
"[F2 A2 Bb2 B2]",
"[G2 C2 F2 F2]",
"[Bb2 Bb2 A2 A2]",
"[Ab2 Ab2 G2 [C2 D2 E2]]",
"[Bb2 Bb2 A2 A2]",
"[Ab2 Ab2 G2 [C2 D2 E2]]",
"[F2 A2 Bb2 B2]",
"[G2 C2 F2 F2]"
).s('Acoustic Bass: Bass')
).slow(51)
`;
export const bossaRandom = `const chords = "<Am7 Am7 Dm7 E7>"
const roots = chords.rootNotes(2)
stack(
chords.voicings(['F4', 'A5']).struct(
\` x@2 ~ x ~ ~ ~ x |
x? ~ ~ x@3 ~ x |
x? ~ ~ x ~ x@3\`),
roots.struct("x [~ x?0.2] x [~ x?] | x!4 | x@2 ~ ~ ~ x x x").transpose("0 7")
).slow(2).pianoroll().note().piano()`;
+154 -309
View File
@@ -4,25 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const tetrisMini = `note(\`[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
[c5 a4 a4 ~]
[[~ d5] [~ f5] a5 [g5 f5]]
[e5 [~ c5] e5 [d5 c5]]
[b4 [b4 c5] d5 e5]
[c5 a4 a4 ~]],
[[e2 e3]*4]
[[a2 a3]*4]
[[g#2 g#3]*2 [e2 e3]*2]
[a2 a3 a2 a3 a2 a3 b1 c2]
[[d2 d3]*4]
[[c2 c3]*4]
[[b1 b2]*2 [e2 e3]*2]
[[a1 a2]*4]\`).slow(16)
`;
export const swimming = `stack(
export const swimming = `// Koji Kondo - Swimming (Super Mario World)
stack(
seq(
"~",
"~",
@@ -83,7 +66,8 @@ export const swimming = `stack(
).note().slow(51);
`;
export const giantSteps = `stack(
export const giantSteps = `// John Coltrane - Giant Steps
stack(
// melody
seq(
"[F#5 D5] [B4 G4] Bb4 [B4 A4]",
@@ -107,34 +91,8 @@ export const giantSteps = `stack(
)
).slow(20).note()`;
export const giantStepsReggae = `stack(
// melody
seq(
"[F#5 D5] [B4 G4] Bb4 [B4 A4]",
"[D5 Bb4] [G4 Eb4] F#4 [G4 F4]",
"Bb4 [B4 A4] D5 [D#5 C#5]",
"F#5 [G5 F5] Bb5 [F#5 [F#5 ~@3]]",
),
// chords
seq(
"[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]",
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
)
.struct("~ [x ~]".fast(4*8))
.voicings(['E3', 'G4']),
// bass
seq(
"[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]",
"[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]",
"[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]",
"[B2 F#2] [F2 Bb2] [Eb2 Bb2] [C#2 F#2]"
)
.struct("x ~".fast(4*8))
).slow(25).note()`;
export const zeldasRescue = `stack(
export const zeldasRescue = `// Koji Kondo - Princess Zelda's Rescue
stack(
// melody
\`[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
[B3@2 D4] [A4@2 G4] [D4@2 [C4 B3]] [A3]
@@ -157,7 +115,9 @@ export const zeldasRescue = `stack(
.room(1)
`;
export const caverave = `const keys = x => x.s('sawtooth').cutoff(1200).gain(.5).attack(0).decay(.16).sustain(.3).release(.1);
export const caverave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const keys = x => x.s('sawtooth').cutoff(1200).gain(.5).attack(0).decay(.16).sustain(.3).release(.1);
const drums = stack(
s("bd*2").mask("<x@7 ~>/8").gain(.8),
@@ -201,90 +161,7 @@ stack(
).s()
`;
// TODO:
/*
export const xylophoneCalling = `const t = x => x.scaleTranspose("<0 2 4 3>/4").transpose(-2)
const s = x => x.scale(cat('C3 minor pentatonic','G3 minor pentatonic').slow(4))
const delay = new FeedbackDelay(1/8, .6).chain(vol(0.1), out());
const chorus = new Chorus(1,2.5,0.5).start();
stack(
// melody
"<<10 7> <8 3>>/4".struct("x*3").apply(s)
.scaleTranspose("<0 3 2> <1 4 3>")
.superimpose(scaleTranspose(2).early(1/8))
.apply(t).tone(polysynth().set({
...osc('triangle4'),
...adsr(0,.08,0)
}).chain(vol(0.2).connect(delay),chorus,out())).mask("<~@3 x>/16".early(1/8)),
// pad
"[1,3]/4".scale('G3 minor pentatonic').apply(t).tone(polysynth().set({
...osc('square2'),
...adsr(0.1,.4,0.8)
}).chain(vol(0.2),chorus,out())).mask("<~ x>/32"),
// xylophone
"c3,g3,c4".struct("<x*2 x>").fast("<1 <2!3 [4 8]>>").apply(s).scaleTranspose("<0 <1 [2 [3 <4 5>]]>>").apply(t).tone(polysynth().set({
...osc('sawtooth4'),
...adsr(0,.1,0)
}).chain(vol(0.4).connect(delay),out())).mask("<x@3 ~>/16".early(1/8)),
// bass
"c2 [c2 ~]*2".scale('C hirajoshi').apply(t).tone(synth({
...osc('sawtooth6'),
...adsr(0,.03,.4,.1)
}).chain(vol(0.4),out())),
// kick
"<c1!3 [c1 ~]*2>*2".tone(membrane().chain(vol(0.8),out())),
// snare
"~ <c3!7 [c3 c3*2]>".tone(noise().chain(vol(0.8),out())),
// hihat
"c3*4".transpose("[-24 0]*2").tone(metal(adsr(0,.02)).chain(vol(0.5).connect(delay),out()))
).slow(1)
// strudel disable-highlighting`;
*/
// TODO:
/*
export const sowhatelse = `// mixer
const mix = (key) => vol({
chords: .2,
lead: 0.8,
bass: .4,
snare: .95,
kick: .9,
hihat: .35,
}[key]||0);
const delay = new FeedbackDelay(1/6, .3).chain(vol(.7), out());
const delay2 = new FeedbackDelay(1/6, .2).chain(vol(.15), out());
const chorus = new Chorus(1,2.5,0.5).start();
// instruments
const instr = (instrument) => ({
organ: polysynth().set({...osc('sawtooth4'), ...adsr(.01,.2,0)}).chain(mix('chords').connect(delay),out()),
lead: polysynth().set({...osc('triangle4'),...adsr(0.01,.05,0)}).chain(mix('lead').connect(delay2), out()),
bass: polysynth().set({...osc('sawtooth8'),...adsr(.02,.05,.3,.2)}).chain(mix('bass'),lowpass(3000), out()),
pad: polysynth().set({...osc('square2'),...adsr(0.1,.4,0.8)}).chain(vol(0.15),chorus,out()),
hihat: metal(adsr(0, .02, 0)).chain(mix('hihat'), out()),
snare: noise(adsr(0, .15, 0.01)).chain(mix('snare'), lowpass(5000), out()),
kick: membrane().chain(mix('kick'), out())
}[instrument]);
// harmony
const t = transpose("<0 0 1 0>/8");
const sowhat = scaleTranspose("0,3,6,9,11");
// track
stack(
"[<0 4 [3 [2 1]]>]/4".struct("[x]*3").mask("[~ x ~]").scale('D5 dorian').off(1/6, scaleTranspose(-7)).off(1/3, scaleTranspose(-5)).apply(t).tone(instr('lead')).mask("<~ ~ x x>/8"),
"<<e3 [~@2 a3]> <[d3 ~] [c3 f3] g3>>".scale('D dorian').apply(sowhat).apply(t).tone(instr('organ')).mask("<x x x ~>/8"),
"<[d2 [d2 ~]*3]!3 <a1*2 c2*3 [a1 e2]>>".apply(t).tone(instr('bass')),
"c1*6".tone(instr('hihat')),
"~ c3".tone(instr('snare')),
"<[c1@5 c1] <c1 [[c1@2 c1] ~] [c1 ~ c1] [c1!2 ~ c1!3]>>".tone(instr('kick')),
"[2,4]/4".scale('D dorian').apply(t).tone(instr('pad')).mask("<x x x ~>/8")
).fast(6/8)
// strudel disable-highlighting`;
*/
export const barryHarris = `backgroundImage(
'https://media.npr.org/assets/img/2017/02/03/barryharris_600dpi_wide-7eb49998aa1af377d62bb098041624c0a0d1a454.jpg',
{style:'background-size:cover'})
export const barryHarris = `// adapted from a Barry Harris excercise
"0,2,[7 6]"
.add("<0 1 2 3 4 5 7 8>")
.scale('C bebop major')
@@ -293,7 +170,9 @@ export const barryHarris = `backgroundImage(
.note().piano()
`;
export const blippyRhodes = `samples({
export const blippyRhodes = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({
bd: 'samples/tidal/bd/BT0A0D0.wav',
sn: 'samples/tidal/sn/ST0T0S3.wav',
hh: 'samples/tidal/hh/000_hh3closedhh.wav',
@@ -332,7 +211,9 @@ stack(
.s('sawtooth').cutoff(600),
).fast(3/2)`;
export const wavyKalimba = `samples({
export const wavyKalimba = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
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'])
@@ -360,37 +241,9 @@ stack(
.delay(.2)
`;
// TODO: rework tune to use freq
/*
export const jemblung = `const delay = new FeedbackDelay(1/8, .6).chain(vol(0.15), out());
const snare = noise({type:'white',...adsr(0,0.2,0)}).chain(lowpass(5000),vol(1.8),out());
const s = polysynth().set({...osc('sawtooth4'),...adsr(0.01,.2,.6,0.2)}).chain(vol(.23).connect(delay),out());
stack(
stack(
"0 1 4 [3!2 5]".layer(
// chords
x=>x.add("0,3").duration("0.05!3 0.02"),
// bass
x=>x.add("-8").struct("x*8").duration(0.1)
),
// melody
"12 11*3 12 ~".duration(0.005)
)
.add("<0 1>")
.tune("jemblung2")
//.mul(22/5).round().xen("22edo")
//.mul(12/5).round().xen("12edo")
.tone(s),
// kick
"[c2 ~]*2".duration(0.05).tone(membrane().chain(out())),
// snare
"[~ c1]*2".early(0.001).tone(snare),
// hihat
"c2*8".tone(noise().chain(highpass(6000),vol(0.5).connect(delay),out())),
).slow(3)`;
*/
export const festivalOfFingers = `const chords = "<Cm7 Fm7 G7 F#7>";
export const festivalOfFingers = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const chords = "<Cm7 Fm7 G7 F#7>";
stack(
chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
chords.rootNotes(2).struct("x(4,8,-2)"),
@@ -403,7 +256,8 @@ stack(
.note().piano()`;
// iter, echo, echoWith
export const undergroundPlumber = `backgroundImage('https://images.nintendolife.com/news/2016/08/video_exploring_the_funky_inspiration_for_the_super_mario_bros_underground_theme/large.jpg',{ className:'darken' })
export const undergroundPlumber = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos, inspired by Friendship - Let's not talk about it (1979) :)
samples({ bd: 'bd/BT0A0D0.wav', sn: 'sn/ST0T0S3.wav', hh: 'hh/000_hh3closedhh.wav', cp: 'cp/HANDCLP0.wav',
}, 'https://loophole-letters.vercel.app/samples/tidal/')
@@ -428,7 +282,9 @@ stack(
.fast(2/3)
.pianoroll({})`;
export const bridgeIsOver = `samples({mad:'https://freesound.org/data/previews/22/22274_109943-lq.mp3'})
export const bridgeIsOver = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos, bassline by BDP - The 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),
@@ -447,7 +303,9 @@ stack(
.pianoroll()
`;
export const goodTimes = `const scale = cat('C3 dorian','Bb2 major').slow(4);
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(
"2*4".add(12).scale(scale)
.off(1/8,x=>x.scaleTranspose("2")).fast(2)
@@ -467,7 +325,9 @@ stack(
.slow(2)
.pianoroll({maxMidi:100,minMidi:20})`;
export const echoPiano = `"<0 2 [4 6](3,4,1) 3*2>"
export const echoPiano = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
"<0 2 [4 6](3,4,1) 3*2>"
.scale('D minor')
.color('salmon')
.off(1/4, x=>x.scaleTranspose(2).color('green'))
@@ -477,7 +337,7 @@ export const echoPiano = `"<0 2 [4 6](3,4,1) 3*2>"
.note().piano()
.pianoroll()`;
export const sml1 = `
export const sml1 = `// Hirokazu Tanaka - World 1-1
stack(
// melody
\`<
@@ -512,30 +372,9 @@ stack(
.legato(.5)
).fast(2).note()`;
/*
// TODO: does not work on linux (at least for me..)
export const speakerman = `backgroundImage('https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fi.ytimg.com%2Fvi%2FXR0rKqW3VwY%2Fmaxresdefault.jpg&f=1&nofb=1',
{ className:'darken', style:'background-size:cover'})
stack(
"[g3,bb3,d4] [f3,a3,c4] [c3,e3,g3]@2".slow(2).late(.1),
cat(
'Baker man',
'is baking bread',
'Baker man',
'is baking bread',
'Sagabona',
'kunjani wena',
'Sagabona',
'kunjani wena',
'The night train, is coming',
'got to keep on running',
'The night train, is coming',
'got to keep on running',
).speak("en zu en".slow(12), "<0 2 3 4 5 6>".slow(2)),
).slow(4)`;
*/
export const randomBells = `samples({
export const randomBells = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({
bell: { c6: 'https://freesound.org/data/previews/411/411089_5121236-lq.mp3' },
bass: { d2: 'https://freesound.org/data/previews/608/608286_13074022-lq.mp3' }
})
@@ -556,7 +395,9 @@ stack(
.pianoroll({minMidi:20,maxMidi:120,background:'transparent'})
`;
export const waa2 = `n(
export const waa2 = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
n(
"a4 [a3 c3] a3 c3"
.sub("<7 12 5 12>".slow(2))
.off(1/4,x=>x.add(7))
@@ -570,7 +411,9 @@ export const waa2 = `n(
.room(.5)
`;
export const hyperpop = `const lfo = cosine.slow(15);
export const hyperpop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
const lfo = cosine.slow(15);
const lfo2 = sine.slow(16);
const filter1 = x=>x.cutoff(lfo2.range(300,3000));
const filter2 = x=>x.hcutoff(lfo.range(1000,6000)).cutoff(4000)
@@ -619,7 +462,9 @@ stack(
).slow(2)
// strudel disable-highlighting`;
export const festivalOfFingers3 = `"[-7*3],0,2,6,[8 7]"
export const festivalOfFingers3 = `// licensed with 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))
@@ -635,33 +480,9 @@ export const festivalOfFingers3 = `"[-7*3],0,2,6,[8 7]"
.note().piano()
//.pianoroll({maxMidi:160})`;
export const bossa = `
const scales = sequence('C minor', ['D locrian', 'G phrygian'], 'Bb2 minor', ['C locrian','F phrygian']).slow(4)
stack(
"<Cm7 [Dm7b5 G7b9] Bbm7 [Cm7b5 F7b9]>".fast(2).struct("x ~ x@3 x ~ x ~ ~ ~ x ~ x@3".late(1/8)).early(1/8).slow(2).voicings(),
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
).note().piano().slow(2)`;
/*
export const customTrigger = `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))
).s("<sawtooth square>/2")
.onTrigger((t,hap,ct)=>{
const ac = Tone.getContext().rawContext;
t = ac.currentTime + t - ct;
const { freq, s, gain = 1 } = hap.value;
const master = ac.createGain();
master.gain.value = 0.1 * gain;
master.connect(ac.destination);
const o = ac.createOscillator();
o.type = s || 'triangle';
o.frequency.value = Number(freq);
o.connect(master);
o.start(t);
o.stop(t + hap.duration);
}).stack(s("bd(3,8),hh*4,~ sd").webdirt())`; */
export const meltingsubmarine = `samples({
export const meltingsubmarine = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
@@ -701,68 +522,9 @@ stack(
)
.slow(3/2)`;
export const swimmingWithSoundfonts = `stack(
n(
"~",
"~",
"~",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [F5@2 C6] A5 G5",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [F5@2 C6] A5 G5",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2",
"A5 [F5@2 C5] A5 F5",
"Ab5 [F5@2 Ab5] G5@2",
"A5 [F5@2 C5] A5 F5",
"Ab5 [F5@2 C5] C6@2",
"A5 [F5@2 C5] [D5@2 F5] F5",
"[C5@2 F5] [Bb5 A5 G5] F5@2"
).s('Sitar: Ethnic'),
n(
"[F4,Bb4,D5] [[D4,G4,Bb4]@2 [Bb3,D4,F4]] [[G3,C4,E4]@2 [[Ab3,F4] [A3,Gb4]]] [Bb3,E4,G4]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, Bb3, Db3] [F3, Bb3, Db3]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]",
"[~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]",
"[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]",
"[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]",
"[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]",
"[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]",
"[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]",
"[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]"
).s('Kalimba: Ethnic'),
n(
"[G3 G3 C3 E3]",
"[F2 D2 G2 C2]",
"[F2 D2 G2 C2]",
"[F2 A2 Bb2 B2]",
"[A2 Ab2 G2 C2]",
"[F2 A2 Bb2 B2]",
"[G2 C2 F2 F2]",
"[F2 A2 Bb2 B2]",
"[A2 Ab2 G2 C2]",
"[F2 A2 Bb2 B2]",
"[G2 C2 F2 F2]",
"[Bb2 Bb2 A2 A2]",
"[Ab2 Ab2 G2 [C2 D2 E2]]",
"[Bb2 Bb2 A2 A2]",
"[Ab2 Ab2 G2 [C2 D2 E2]]",
"[F2 A2 Bb2 B2]",
"[G2 C2 F2 F2]"
).s('Acoustic Bass: Bass')
).slow(51)
`;
export const outroMusic = `samples({
export const outroMusic = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'],
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
@@ -789,7 +551,9 @@ export const outroMusic = `samples({
//.pianoroll({autorange:1,vertical:1,fold:0})
`;
export const bassFuge = `samples({ flbass: ['00_c2_finger_long_neck.wav','01_c2_finger_short_neck.wav','02_c2_finger_long_bridge.wav','03_c2_finger_short_bridge.wav','04_c2_pick_long.wav','05_c2_pick_short.wav','06_c2_palm_mute.wav'] },
export const bassFuge = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({ flbass: ['00_c2_finger_long_neck.wav','01_c2_finger_short_neck.wav','02_c2_finger_long_bridge.wav','03_c2_finger_short_bridge.wav','04_c2_pick_long.wav','05_c2_pick_short.wav','06_c2_palm_mute.wav'] },
'github:cleary/samples-flbass/main/')
samples({
bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0A0D0.wav','bd/BT0A0A7.wav'],
@@ -813,18 +577,9 @@ x=>x.add(7).color('steelblue')
.stack(s("bd:1*2,~ sd:0,[~ hh:0]*2"))
.pianoroll({vertical:1})`;
export const bossaRandom = `const chords = "<Am7 Am7 Dm7 E7>"
const roots = chords.rootNotes(2)
stack(
chords.voicings(['F4', 'A5']).struct(
\` x@2 ~ x ~ ~ ~ x |
x? ~ ~ x@3 ~ x |
x? ~ ~ x ~ x@3\`),
roots.struct("x [~ x?0.2] x [~ x?] | x!4 | x@2 ~ ~ ~ x x x").transpose("0 7")
).slow(2).pianoroll().note().piano()`;
export const chop = `samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' })
export const chop = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' })
s("p")
.loopAt(32,1)
@@ -835,14 +590,18 @@ s("p")
.sustain(.6)
`;
export const delay = `stack(
export const delay = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
stack(
s("bd <sd cp>")
.delay("<0 .5>")
.delaytime(".16 | .33")
.delayfeedback(".6 | .8")
).sometimes(x=>x.speed("-1"))`;
export const orbit = `stack(
export const orbit = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
stack(
s("bd <sd cp>")
.delay(.5)
.delaytime(.33)
@@ -854,7 +613,9 @@ export const orbit = `stack(
.orbit(2)
).sometimes(x=>x.speed("-1"))`;
export const belldub = `samples({ bell: {b4:'https://cdn.freesound.org/previews/339/339809_5121236-lq.mp3'}})
export const belldub = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({ bell: {b4:'https://cdn.freesound.org/previews/339/339809_5121236-lq.mp3'}})
// "Hand Bells, B, Single.wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org
stack(
// bass
@@ -885,7 +646,9 @@ stack(
.mask("<1 0>/8")
).slow(5)`;
export const dinofunk = `samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
export const dinofunk = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3',
dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}})
stack(
@@ -903,7 +666,9 @@ note("Abm7".voicings(['c3','a4']).struct("x(3,8,1)".slow(2))),
note("<b4 eb4>").s('dino').delay(.8).slow(8).room(.5)
)`;
export const sampleDemo = `stack(
export const sampleDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
stack(
// percussion
s("[woodblock:1 woodblock:2*2] snare_rim:0,gong/8,brakedrum:1(3,8),~@3 cowbell:3")
.sometimes(x=>x.speed(2)),
@@ -917,7 +682,9 @@ export const sampleDemo = `stack(
.release(.1).room(.5)
)`;
export const holyflute = `"c3 eb3(3,8) c4/2 g3*2"
export const holyflute = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
"c3 eb3(3,8) c4/2 g3*2"
.superimpose(
x=>x.slow(2).add(12),
x=>x.slow(4).sub(5)
@@ -928,7 +695,9 @@ export const holyflute = `"c3 eb3(3,8) c4/2 g3*2"
.pianoroll({fold:0,autorange:0,vertical:0,cycles:12,smear:0,minMidi:40})
`;
export const flatrave = `stack(
export const flatrave = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
stack(
s("bd*2,~ [cp,sd]").bank('RolandTR909'),
s("hh:1*4").sometimes(fast("2"))
@@ -949,7 +718,9 @@ export const flatrave = `stack(
.decay(.05).sustain(0).delay(.2).degradeBy(.5).mask("<0 1>/16")
)`;
export const amensister = `await samples('github:tidalcycles/Dirt-Samples/master')
export const amensister = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
await samples('github:tidalcycles/Dirt-Samples/master')
stack(
// amen
@@ -981,7 +752,9 @@ stack(
n("0 1").s("east").delay(.5).degradeBy(.8).speed(rand.range(.5,1.5))
).reset("<x@7 x(5,8)>")`;
export const juxUndTollerei = `note("c3 eb3 g3 bb3").palindrome()
export const juxUndTollerei = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
note("c3 eb3 g3 bb3").palindrome()
.s('sawtooth')
.jux(x=>x.rev().color('green').s('sawtooth'))
.off(1/4, x=>x.add(note("<7 12>/2")).slow(2).late(.005).s('triangle'))
@@ -991,3 +764,75 @@ export const juxUndTollerei = `note("c3 eb3 g3 bb3").palindrome()
.room(.6)
.delay(.5).delaytime(.1).delayfeedback(.4)
.pianoroll()`;
export const csoundDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
await csound\`
instr CoolSynth
iduration = p3
ifreq = p4
igain = p5
ioct = octcps(ifreq)
kpwm = oscili(.05, 8)
asig = vco2(igain, ifreq, 4, .5 + kpwm)
asig += vco2(igain, ifreq * 2)
idepth = 2
acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) ; filter envelope
asig = zdf_2pole(asig, cpsoct(ioct + acut + 2), 0.5)
iattack = .01
isustain = .5
idecay = .1
irelease = .1
asig *= linsegr:a(0, iattack, 1, idecay, isustain, iduration, isustain, irelease, 0)
out(asig, asig)
endin\`
"<0 2 [4 6](3,4,1) 3*2>"
.off(1/4, add(2))
.off(1/2, add(6))
.scale('D minor')
.note()
//.pianoroll()
.csound('CoolSynth')`;
export const loungeSponge = `
// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// by Felix Roos
// livecode.orc by Steven Yi
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
stack(
note("<C^7 A7 Dm7 Fm7>/2".voicings())
.cutoff(sine.range(500,2000).round().slow(16))
.euclidLegato(3,8).csound('FM1')
,
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>"))
.slow(2).scale('A4 minor'))
.legato(.25).csound('SynHarp')
,
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
)`;
export const arpoon = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// "Arpoon" by Felix Roos
await samples('github:tidalcycles/Dirt-Samples/master')
"<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings()
.arp("[0,3] 2 [1,3] 2".fast(3)).legato(2)
.add(perlin.range(0,0.2)).sub("<0 -12>/8")
.note().cutoff(perlin.range(500,4000)).resonance(12)
.gain("<.5 .8>*16")
.decay(.16).sustain(0.5)
.delay(.2)
.room(.5).pan(sine.range(.3,.6))
.s('piano').clip(1)
.stack("<<A1 C2>!2 F2 [F2 E2]>".add.out("0 -5".fast(2)).add("0,.12").note().s('sawtooth').clip(1).cutoff(300))
.slow(4)
.stack(s("bd*4, [~ [hh hh? hh?]]*2,~ [sd ~ [sd:2? bd?]]").arp("0|1").bank('RolandTR909').gain(.5).slow(2))
`;
+4 -4
View File
@@ -1,4 +1,4 @@
import { evalScope, controls } from '@strudel.cycles/core';
import core from '@strudel.cycles/core';
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
import { samples } from '@strudel.cycles/webaudio';
import { prebake } from '../repl/src/prebake.mjs';
@@ -7,9 +7,9 @@ fetch('https://strudel.tidalcycles.org/EmuSP12.json')
.then((res) => res.json())
.then((json) => samples(json, 'https://strudel.tidalcycles.org/EmuSP12/'));
evalScope(
controls,
import('@strudel.cycles/core'),
core.evalScope(
core,
// import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
+1 -1
View File
@@ -18,7 +18,7 @@ function renderAsMDX(name) {
}
return `### ${item.longname}
${item.description.replaceAll(/\{\@link ([a-zA-Z]+)?\#?([a-zA-Z]*)\}/g, (_, a, b) => {
${item.description.replaceAll(/\{@link ([a-zA-Z]+)?#?([a-zA-Z]*)\}/g, (_, a, b) => {
// console.log(_, 'a', a, 'b', b);
return `<a href="#${a}${b ? `-${b}` : ''}">${a}${b ? `#${b}` : ''}</a>`;
})}
@@ -1,5 +1,22 @@
// Vitest Snapshot v1
exports[`runs examples > example "_apply" example index 0 1`] = `
[
"0/1 -> 1/1: {\\"note\\":\\"C3\\"}",
"0/1 -> 1/1: {\\"note\\":\\"Eb3\\"}",
"0/1 -> 1/1: {\\"note\\":\\"G3\\"}",
"1/1 -> 2/1: {\\"note\\":\\"Eb3\\"}",
"1/1 -> 2/1: {\\"note\\":\\"G3\\"}",
"1/1 -> 2/1: {\\"note\\":\\"Bb3\\"}",
"2/1 -> 3/1: {\\"note\\":\\"G3\\"}",
"2/1 -> 3/1: {\\"note\\":\\"Bb3\\"}",
"2/1 -> 3/1: {\\"note\\":\\"D4\\"}",
"3/1 -> 4/1: {\\"note\\":\\"C3\\"}",
"3/1 -> 4/1: {\\"note\\":\\"Eb3\\"}",
"3/1 -> 4/1: {\\"note\\":\\"G3\\"}",
]
`;
exports[`runs examples > example "accelerate" example index 0 1`] = `
[
"0/1 -> 2/1: {\\"s\\":\\"sax\\",\\"accelerate\\":0}",
@@ -215,23 +232,6 @@ exports[`runs examples > example "amp" example index 0 1`] = `
]
`;
exports[`runs examples > example "apply" example index 0 1`] = `
[
"0/1 -> 1/1: {\\"note\\":\\"C3\\"}",
"0/1 -> 1/1: {\\"note\\":\\"Eb3\\"}",
"0/1 -> 1/1: {\\"note\\":\\"G3\\"}",
"1/1 -> 2/1: {\\"note\\":\\"Eb3\\"}",
"1/1 -> 2/1: {\\"note\\":\\"G3\\"}",
"1/1 -> 2/1: {\\"note\\":\\"Bb3\\"}",
"2/1 -> 3/1: {\\"note\\":\\"G3\\"}",
"2/1 -> 3/1: {\\"note\\":\\"Bb3\\"}",
"2/1 -> 3/1: {\\"note\\":\\"D4\\"}",
"3/1 -> 4/1: {\\"note\\":\\"C3\\"}",
"3/1 -> 4/1: {\\"note\\":\\"Eb3\\"}",
"3/1 -> 4/1: {\\"note\\":\\"G3\\"}",
]
`;
exports[`runs examples > example "bandf" example index 0 1`] = `
[
"0/1 -> 1/2: {\\"s\\":\\"bd\\",\\"bandf\\":1000}",
@@ -765,27 +765,6 @@ exports[`runs examples > example "dry" example index 0 1`] = `
]
`;
exports[`runs examples > example "each" example index 0 1`] = `
[
"0/1 -> 1/4: {\\"note\\":\\"c3\\"}",
"1/4 -> 1/2: {\\"note\\":\\"d3\\"}",
"1/2 -> 3/4: {\\"note\\":\\"e3\\"}",
"3/4 -> 1/1: {\\"note\\":\\"g3\\"}",
"1/1 -> 5/4: {\\"note\\":\\"c3\\"}",
"5/4 -> 3/2: {\\"note\\":\\"d3\\"}",
"3/2 -> 7/4: {\\"note\\":\\"e3\\"}",
"7/4 -> 2/1: {\\"note\\":\\"g3\\"}",
"2/1 -> 9/4: {\\"note\\":\\"c3\\"}",
"9/4 -> 5/2: {\\"note\\":\\"d3\\"}",
"5/2 -> 11/4: {\\"note\\":\\"e3\\"}",
"11/4 -> 3/1: {\\"note\\":\\"g3\\"}",
"15/4 -> 4/1: {\\"note\\":\\"c3\\"}",
"7/2 -> 15/4: {\\"note\\":\\"d3\\"}",
"13/4 -> 7/2: {\\"note\\":\\"e3\\"}",
"3/1 -> 13/4: {\\"note\\":\\"g3\\"}",
]
`;
exports[`runs examples > example "early" example index 0 1`] = `
[
"0/1 -> 1/2: {\\"s\\":\\"bd\\"}",
@@ -807,36 +786,36 @@ exports[`runs examples > example "echo" example index 0 1`] = `
[
"0/1 -> 1/2: {\\"s\\":\\"bd\\"}",
"1/2 -> 1/1: {\\"s\\":\\"sd\\"}",
"-4166666666666667/12500000000000000 -> 8333333333333333/50000000000000000: {\\"s\\":\\"sd\\"}",
"8333333333333333/50000000000000000 -> 8333333333333333/12500000000000000: {\\"s\\":\\"bd\\"}",
"8333333333333333/12500000000000000 -> 7291666666666667/6250000000000000: {\\"s\\":\\"sd\\"}",
"-4166666666666667/25000000000000000 -> 8333333333333333/25000000000000000: {\\"s\\":\\"sd\\"}",
"8333333333333333/25000000000000000 -> 5208333333333333/6250000000000000: {\\"s\\":\\"bd\\"}",
"5208333333333333/6250000000000000 -> 8333333333333333/6250000000000000: {\\"s\\":\\"sd\\"}",
"-1/3 -> 1/6: {\\"s\\":\\"sd\\"}",
"1/6 -> 2/3: {\\"s\\":\\"bd\\"}",
"2/3 -> 7/6: {\\"s\\":\\"sd\\"}",
"-1/6 -> 1/3: {\\"s\\":\\"sd\\"}",
"1/3 -> 5/6: {\\"s\\":\\"bd\\"}",
"5/6 -> 4/3: {\\"s\\":\\"sd\\"}",
"1/1 -> 3/2: {\\"s\\":\\"bd\\"}",
"3/2 -> 2/1: {\\"s\\":\\"sd\\"}",
"8333333333333333/12500000000000000 -> 7291666666666667/6250000000000000: {\\"s\\":\\"sd\\"}",
"7291666666666667/6250000000000000 -> 5208333333333333/3125000000000000: {\\"s\\":\\"bd\\"}",
"5208333333333333/3125000000000000 -> 6770833333333333/3125000000000000: {\\"s\\":\\"sd\\"}",
"5208333333333333/6250000000000000 -> 8333333333333333/6250000000000000: {\\"s\\":\\"sd\\"}",
"8333333333333333/6250000000000000 -> 5729166666666667/3125000000000000: {\\"s\\":\\"bd\\"}",
"5729166666666667/3125000000000000 -> 7291666666666667/3125000000000000: {\\"s\\":\\"sd\\"}",
"2/3 -> 7/6: {\\"s\\":\\"sd\\"}",
"7/6 -> 5/3: {\\"s\\":\\"bd\\"}",
"5/3 -> 13/6: {\\"s\\":\\"sd\\"}",
"5/6 -> 4/3: {\\"s\\":\\"sd\\"}",
"4/3 -> 11/6: {\\"s\\":\\"bd\\"}",
"11/6 -> 7/3: {\\"s\\":\\"sd\\"}",
"2/1 -> 5/2: {\\"s\\":\\"bd\\"}",
"5/2 -> 3/1: {\\"s\\":\\"sd\\"}",
"5208333333333333/3125000000000000 -> 6770833333333333/3125000000000000: {\\"s\\":\\"sd\\"}",
"6770833333333333/3125000000000000 -> 8333333333333333/3125000000000000: {\\"s\\":\\"bd\\"}",
"8333333333333333/3125000000000000 -> 4947916666666667/1562500000000000: {\\"s\\":\\"sd\\"}",
"5729166666666667/3125000000000000 -> 7291666666666667/3125000000000000: {\\"s\\":\\"sd\\"}",
"7291666666666667/3125000000000000 -> 8854166666666667/3125000000000000: {\\"s\\":\\"bd\\"}",
"8854166666666667/3125000000000000 -> 5208333333333333/1562500000000000: {\\"s\\":\\"sd\\"}",
"5/3 -> 13/6: {\\"s\\":\\"sd\\"}",
"13/6 -> 8/3: {\\"s\\":\\"bd\\"}",
"8/3 -> 19/6: {\\"s\\":\\"sd\\"}",
"11/6 -> 7/3: {\\"s\\":\\"sd\\"}",
"7/3 -> 17/6: {\\"s\\":\\"bd\\"}",
"17/6 -> 10/3: {\\"s\\":\\"sd\\"}",
"3/1 -> 7/2: {\\"s\\":\\"bd\\"}",
"7/2 -> 4/1: {\\"s\\":\\"sd\\"}",
"8333333333333333/3125000000000000 -> 4947916666666667/1562500000000000: {\\"s\\":\\"sd\\"}",
"4947916666666667/1562500000000000 -> 5729166666666667/1562500000000000: {\\"s\\":\\"bd\\"}",
"5729166666666667/1562500000000000 -> 6510416666666667/1562500000000000: {\\"s\\":\\"sd\\"}",
"8854166666666667/3125000000000000 -> 5208333333333333/1562500000000000: {\\"s\\":\\"sd\\"}",
"5208333333333333/1562500000000000 -> 5989583333333333/1562500000000000: {\\"s\\":\\"bd\\"}",
"5989583333333333/1562500000000000 -> 6770833333333333/1562500000000000: {\\"s\\":\\"sd\\"}",
"8/3 -> 19/6: {\\"s\\":\\"sd\\"}",
"19/6 -> 11/3: {\\"s\\":\\"bd\\"}",
"11/3 -> 25/6: {\\"s\\":\\"sd\\"}",
"17/6 -> 10/3: {\\"s\\":\\"sd\\"}",
"10/3 -> 23/6: {\\"s\\":\\"bd\\"}",
"23/6 -> 13/3: {\\"s\\":\\"sd\\"}",
]
`;
@@ -1530,27 +1509,6 @@ exports[`runs examples > example "every" example index 0 1`] = `
]
`;
exports[`runs examples > example "every" example index 0 2`] = `
[
"3/4 -> 1/1: {\\"note\\":\\"c3\\"}",
"1/2 -> 3/4: {\\"note\\":\\"d3\\"}",
"1/4 -> 1/2: {\\"note\\":\\"e3\\"}",
"0/1 -> 1/4: {\\"note\\":\\"g3\\"}",
"1/1 -> 5/4: {\\"note\\":\\"c3\\"}",
"5/4 -> 3/2: {\\"note\\":\\"d3\\"}",
"3/2 -> 7/4: {\\"note\\":\\"e3\\"}",
"7/4 -> 2/1: {\\"note\\":\\"g3\\"}",
"2/1 -> 9/4: {\\"note\\":\\"c3\\"}",
"9/4 -> 5/2: {\\"note\\":\\"d3\\"}",
"5/2 -> 11/4: {\\"note\\":\\"e3\\"}",
"11/4 -> 3/1: {\\"note\\":\\"g3\\"}",
"3/1 -> 13/4: {\\"note\\":\\"c3\\"}",
"13/4 -> 7/2: {\\"note\\":\\"d3\\"}",
"7/2 -> 15/4: {\\"note\\":\\"e3\\"}",
"15/4 -> 4/1: {\\"note\\":\\"g3\\"}",
]
`;
exports[`runs examples > example "fast" example index 0 1`] = `
[
"0/1 -> 1/4: {\\"s\\":\\"bd\\"}",
@@ -1593,6 +1551,27 @@ exports[`runs examples > example "fastcat" example index 0 1`] = `
]
`;
exports[`runs examples > example "firstOf" example index 0 1`] = `
[
"3/4 -> 1/1: {\\"note\\":\\"c3\\"}",
"1/2 -> 3/4: {\\"note\\":\\"d3\\"}",
"1/4 -> 1/2: {\\"note\\":\\"e3\\"}",
"0/1 -> 1/4: {\\"note\\":\\"g3\\"}",
"1/1 -> 5/4: {\\"note\\":\\"c3\\"}",
"5/4 -> 3/2: {\\"note\\":\\"d3\\"}",
"3/2 -> 7/4: {\\"note\\":\\"e3\\"}",
"7/4 -> 2/1: {\\"note\\":\\"g3\\"}",
"2/1 -> 9/4: {\\"note\\":\\"c3\\"}",
"9/4 -> 5/2: {\\"note\\":\\"d3\\"}",
"5/2 -> 11/4: {\\"note\\":\\"e3\\"}",
"11/4 -> 3/1: {\\"note\\":\\"g3\\"}",
"3/1 -> 13/4: {\\"note\\":\\"c3\\"}",
"13/4 -> 7/2: {\\"note\\":\\"d3\\"}",
"7/2 -> 15/4: {\\"note\\":\\"e3\\"}",
"15/4 -> 4/1: {\\"note\\":\\"g3\\"}",
]
`;
exports[`runs examples > example "freq" example index 0 1`] = `
[
"0/1 -> 1/4: {\\"freq\\":220,\\"s\\":\\"superzow\\"}",
@@ -1793,6 +1772,27 @@ exports[`runs examples > example "iterBack" example index 0 1`] = `
]
`;
exports[`runs examples > example "lastOf" example index 0 1`] = `
[
"0/1 -> 1/4: {\\"note\\":\\"c3\\"}",
"1/4 -> 1/2: {\\"note\\":\\"d3\\"}",
"1/2 -> 3/4: {\\"note\\":\\"e3\\"}",
"3/4 -> 1/1: {\\"note\\":\\"g3\\"}",
"1/1 -> 5/4: {\\"note\\":\\"c3\\"}",
"5/4 -> 3/2: {\\"note\\":\\"d3\\"}",
"3/2 -> 7/4: {\\"note\\":\\"e3\\"}",
"7/4 -> 2/1: {\\"note\\":\\"g3\\"}",
"2/1 -> 9/4: {\\"note\\":\\"c3\\"}",
"9/4 -> 5/2: {\\"note\\":\\"d3\\"}",
"5/2 -> 11/4: {\\"note\\":\\"e3\\"}",
"11/4 -> 3/1: {\\"note\\":\\"g3\\"}",
"15/4 -> 4/1: {\\"note\\":\\"c3\\"}",
"7/2 -> 15/4: {\\"note\\":\\"d3\\"}",
"13/4 -> 7/2: {\\"note\\":\\"e3\\"}",
"3/1 -> 13/4: {\\"note\\":\\"g3\\"}",
]
`;
exports[`runs examples > example "late" example index 0 1`] = `
[
"0/1 -> 1/2: {\\"s\\":\\"bd\\"}",