Compare commits

..

54 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
Felix Roos 4ec1145dce Publish
- @strudel.cycles/core@0.4.1
 - @strudel.cycles/eval@0.4.1
 - @strudel.cycles/midi@0.4.1
 - @strudel.cycles/mini@0.4.1
 - @strudel.cycles/osc@0.3.1
 - @strudel.cycles/react@0.4.2
 - @strudel.cycles/soundfonts@0.4.2
 - @strudel.cycles/tonal@0.4.1
 - @strudel.cycles/tone@0.4.1
 - @strudel.cycles/transpiler@0.4.1
 - @strudel.cycles/webaudio@0.4.2
 - @strudel.cycles/webdirt@0.4.1
 - @strudel.cycles/xen@0.4.1
2022-11-17 11:09:54 +01:00
96 changed files with 6221 additions and 12758 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>
+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; */
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/core",
"version": "0.4.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/core",
"version": "0.4.0",
"version": "0.4.1",
"description": "Port of Tidal Cycles to JavaScript",
"main": "index.mjs",
"type": "module",
+218 -148
View File
@@ -14,14 +14,18 @@ import { compose, removeUndefineds, flatten, id, listRange, curry, mod, numeralA
import drawLine from './drawLine.mjs';
import { logger } from './logger.mjs';
// monster object collecting everything to be exported
const pattern = {};
let stringParser;
// parser is expected to turn a string into a pattern
// if set, the reify function will parse all strings with it
// intended to use with mini to automatically interpret all strings as mini notation
export const setStringParser = (parser) => (stringParser = parser);
pattern['setStringParser'] = (parser) => (stringParser = parser);
/** @class Class representing a pattern. */
export class Pattern {
class Pattern {
_Pattern = true; // this property is used to detect if a pattern that fails instanceof Pattern is an instance of another Pattern
/**
* Create a pattern. As an end user, you will most likely not create a Pattern directly.
@@ -32,10 +36,9 @@ export class Pattern {
this.query = query;
}
//////////////////////////////////////////////////////////////////////
// Haskell-style functor, applicative and monadic operations
/**
* Returns a new pattern, with the function applied to the value of
* each hap. It has the alias {@link Pattern#fmap}.
@@ -164,7 +167,6 @@ export class Pattern {
return new Pattern(query);
}
bindWhole(choose_whole, func) {
const pat_val = this;
const query = function (state) {
@@ -205,7 +207,7 @@ export class Pattern {
}
outerBind(func) {
return this.bindWhole((a, _) => a, func);
return this.bindWhole((a) => a, func);
}
outerJoin() {
@@ -313,7 +315,7 @@ export class Pattern {
//////////////////////////////////////////////////////////////////////
// Utility methods mainly for internal use
/**
* Query haps inside the given time span.
*
@@ -534,7 +536,7 @@ export class Pattern {
// removes continuous haps that don't have a 'whole' timespan
return this.filterHaps((hap) => hap.whole);
}
/**
* Queries the pattern for the first cycle, returning Haps. Mainly of use when
* debugging a pattern.
@@ -940,8 +942,8 @@ export class Pattern {
//binary_pat = sequence(binary_pat)
const true_pat = binary_pat.filterValues(id);
const false_pat = binary_pat.filterValues((val) => !val);
const with_pat = true_pat.fmap((_) => (y) => y).appRight(func(this));
const without_pat = false_pat.fmap((_) => (y) => y).appRight(this);
const with_pat = true_pat.fmap(() => (y) => y).appRight(func(this));
const without_pat = false_pat.fmap(() => (y) => y).appRight(this);
return stack(with_pat, without_pat);
}
@@ -1007,7 +1009,7 @@ export class Pattern {
every(n, func) {
return this.firstOf(n, func);
}
/**
* Returns a new pattern where every other cycle is played once, twice as
* fast, and offset in time by one quarter of a cycle. Creates a kind of
@@ -1027,6 +1029,7 @@ export class Pattern {
* @example
* note("c3 d3 e3 g3").rev()
*/
rev() {
const pat = this;
const query = function (state) {
@@ -1048,10 +1051,9 @@ export class Pattern {
}
palindrome() {
return this.every(2, rev);
return this.every(2, pattern.rev);
}
juxBy(by, func) {
by /= 2;
const elem_or = function (dict, key, dflt) {
@@ -1184,8 +1186,12 @@ export class Pattern {
* note("0 1 2 3".scale('A minor')).iter(4)
*/
_iter(times, back = false) {
times = Fraction(times)
return slowcat(...listRange(0, times.sub(1)).map((i) => (back ? this.late(Fraction(i).div(times)) : this.early(Fraction(i).div(times)))));
times = Fraction(times);
return slowcat(
...listRange(0, times.sub(1)).map((i) =>
back ? this.late(Fraction(i).div(times)) : this.early(Fraction(i).div(times)),
),
);
}
/**
@@ -1231,10 +1237,10 @@ export class Pattern {
on = Boolean(parseInt(on));
return on ? silence : this;
}
//////////////////////////////////////////////////////////////////////
// Control-related methods, which manipulate patterns of objects
/**
* Cuts each sample into the given number of parts, allowing you to explore a technique known as 'granular synthesis'.
* It turns a pattern of samples into a pattern of parts of samples.
@@ -1282,17 +1288,11 @@ export class Pattern {
//////////////////////////////////////////////////////////////////////
// Context methods - ones that deal with metadata
_color(color) {
return this.withContext((context) => ({ ...context, color }));
}
log() {
return this.withHap((e) => {
return e.setContext({ ...e.context, logs: (e.context?.logs || []).concat([e.show()]) });
});
}
/**
*
* Sets the velocity from 0 to 1. Is multiplied together with gain.
@@ -1363,9 +1363,53 @@ export class Pattern {
_legato(value) {
return this.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
}
}
//////////////////////////////////////////////////////////////////////
// functions relating to chords/patterns of lists
// returns Array<Hap[]> where each list of haps satisfies eq
function groupHapsBy(eq, haps) {
let groups = [];
haps.forEach((hap) => {
const match = groups.findIndex(([other]) => eq(hap, other));
if (match === -1) {
groups.push([hap]);
} else {
groups[match].push(hap);
}
});
return groups;
}
pattern['Pattern'] = Pattern;
// congruent haps = haps with equal spans
const congruent = (a, b) => a.spanEquals(b);
// Pattern<Hap<T>> -> Pattern<Hap<T[]>>
// returned pattern contains arrays of congruent haps
Pattern.prototype.collect = function () {
return this.withHaps((haps) =>
groupHapsBy(congruent, haps).map((_haps) => new Hap(_haps[0].whole, _haps[0].part, _haps, {})),
);
};
// applies func to each array of congruent haps
Pattern.prototype.arpWith = function (func) {
return this.collect()
.fmap((v) => reify(func(v)))
.squeezeJoin()
.withHap((h) => new Hap(h.whole, h.part, h.value.value, h.combineContext(h.value)));
};
// applies pattern of indices to each array of congruent haps
Pattern.prototype.arp = function (pat) {
return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length]));
};
//////////////////////////////////////////////////////////////////////
// compose matrix functions
// TODO - adopt value.mjs fully..
function _composeOp(a, b, func) {
function _nonFunctionObject(x) {
@@ -1388,7 +1432,7 @@ function _composeOp(a, b, func) {
// pattern composers
const composers = {
set: [(a, b) => b],
keep: [(a, b) => a],
keep: [(a) => a],
keepif: [(a, b) => (b ? a : undefined)],
// numerical functions
@@ -1463,53 +1507,48 @@ function _composeOp(a, b, func) {
// generate methods to do what and how
for (const [what, [op, preprocess]] of Object.entries(composers)) {
for (const how of hows) {
Pattern.prototype[what + how] = function (...other) {
var pat = this;
other = sequence(other);
if (preprocess) {
pat = preprocess(pat);
other = preprocess(other);
}
var result;
// hack to remove undefs when doing 'keepif'
if (what === 'keepif') {
// avoid union, as we want to throw away the value of 'b' completely
result = pat['_op' + how](other, (a) => (b) => op(a, b));
result = result.removeUndefineds();
} else {
result = pat['_op' + how](other, (a) => (b) => _composeOp(a, b, op));
}
return result;
};
if (how === 'Squeeze') {
// support 'squeezeIn' longhand
Pattern.prototype[what + 'SqueezeIn'] = Pattern.prototype[what + how];
}
if (how === 'In') {
// set 'in' to default, but with magic properties to pick a different 'how'
Object.defineProperty(Pattern.prototype, what, {
// a getter that returns a function, so 'pat' can be
// accessed by closures that are methods of that function..
get: function() {
const pat = this;
// wrap the 'in' function as default behaviour
const wrapper = (...other) => pat[what + "In"](...other);
// add methods to that function to pick alternative behaviours
for (const wraphow of hows) {
wrapper[wraphow.toLowerCase()] = (...other) => pat[what + wraphow](...other);
}
return wrapper;
Object.defineProperty(Pattern.prototype, what, {
// a getter that returns a function, so 'pat' can be
// accessed by closures that are methods of that function..
get: function() {
const pat = this;
// wrap the 'in' function as default behaviour
const wrapper = (...other) => pat[what]['in'](...other);
// add methods to that function for each behaviour
for (const how of hows) {
wrapper[how.toLowerCase()] = function (...other) {
var howpat = pat;
other = sequence(other);
if (preprocess) {
howpat = preprocess(howpat);
other = preprocess(other);
}
var result;
// hack to remove undefs when doing 'keepif'
if (what === 'keepif') {
// avoid union, as we want to throw away the value of 'b' completely
result = howpat['_op' + how](other, (a) => (b) => op(a, b));
result = result.removeUndefineds();
} else {
result = howpat['_op' + how](other, (a) => (b) => _composeOp(a, b, op));
}
return result;
}
});
} else {
// default what to 'set', e.g. squeeze = setSqueeze
if (what === 'set') {
Pattern.prototype[how.toLowerCase()] = Pattern.prototype[what + how];
}
wrapper.squeezein = wrapper.squeeze
return wrapper;
}
});
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
for (const how of hows) {
Pattern.prototype[how.toLowerCase()] = function (...args) { return this.set[how.toLowerCase()](args) };
curry_toplevel(how.toLowerCase(), 2);
}
curry_toplevel(what, 2);
}
// binary composers
@@ -1524,14 +1563,22 @@ function _composeOp(a, b, func) {
* .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
* .slow(4)
*/
Pattern.prototype.struct = Pattern.prototype.keepifOut;
Pattern.prototype.structAll = Pattern.prototype.keepOut;
Pattern.prototype.mask = Pattern.prototype.keepifIn;
Pattern.prototype.maskAll = Pattern.prototype.keepIn;
Pattern.prototype.reset = Pattern.prototype.keepifTrig;
Pattern.prototype.resetAll = Pattern.prototype.keepTrig;
Pattern.prototype.restart = Pattern.prototype.keepifTrigzero;
Pattern.prototype.restartAll = Pattern.prototype.keepTrigzero;
Pattern.prototype.struct = function(...args) { return this.keepif.out(...args) }
curry_toplevel('struct', 2);
Pattern.prototype.structAll = function(...args) { return this.keep.out(...args) }
curry_toplevel('structAll', 2);
Pattern.prototype.mask = function(...args) { return this.keepif.in(...args) }
curry_toplevel('mask', 2);
Pattern.prototype.maskAll = function(...args) { return this.keep.in(...args) }
curry_toplevel('maskAll', 2);
Pattern.prototype.reset = function(...args) { return this.keepif.trig(...args) }
curry_toplevel('reset', 2);
Pattern.prototype.resetAll = function(...args) { return this.keep.trig(...args) }
curry_toplevel('resetAll', 2);
Pattern.prototype.restart = function(...args) { return this.keepif.trigzero(...args) }
curry_toplevel('restart', 2);
Pattern.prototype.restartAll = function(...args) { return this.keep.trigzero(...args) }
curry_toplevel('restartAll', 2);
})();
// methods of Pattern that get callable factories
@@ -1556,10 +1603,6 @@ Pattern.prototype.patternified = [
'velocity',
];
// aliases
export const polyrhythm = stack;
export const pr = stack;
// methods that create patterns, which are added to patternified Pattern methods
Pattern.prototype.factories = {
pure,
@@ -1577,10 +1620,39 @@ Pattern.prototype.factories = {
};
// the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts)
function curry_toplevel(name, arity=1) {
//if (name in pattern) {
// throw(`${name} already exists`);
//}
if (arity == 1) {
pattern[name] = (pat) => pat[name]();
}
else if (arity == 2) {
pattern[name] = curry((a, pat) => pat[name](a));
}
else if (arity == 3) {
pattern[name] = curry((a, b, pat) => pat[name](a, b));
}
else if (arity == 4) {
pattern[name] = curry((a, b, c, pat) => pat[name](a, b, c));
}
}
for (const x of [[1, ['inv', 'invert', 'rev']],
[2, ['chop','chunk','chunkBack', 'mask','struct','superimpose'].concat(Pattern.prototype.patternified)],
[3, ['every','juxBy','off','range','rangex','range2','when']],
[4, ['echo']]
]) {
for (const name of x[1]) {
curry_toplevel(name, x[0]);
}
}
// Elemental patterns
// Nothing
export const silence = new Pattern((_) => []);
const silence = new Pattern(() => []);
pattern['silence'] = silence;
/** A discrete value that repeats once per cycle.
*
@@ -1588,27 +1660,32 @@ export const silence = new Pattern((_) => []);
* @example
* pure('e4') // "e4"
*/
export function pure(value) {
function pure(value) {
function query(state) {
return state.span.spanCycles.map((subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value));
}
return new Pattern(query);
}
pattern['pure'] = pure;
export function isPattern(thing) {
function isPattern(thing) {
// thing?.constructor?.name !== 'Pattern' // <- this will fail when code is mangled
const is = thing instanceof Pattern || thing?._Pattern;
if (!thing instanceof Pattern) {
// TODO: find out how to check wrong core dependency. below will never work !thing === 'undefined'
// wrapping it in (..) will result other checks to log that warning (e.g. isPattern('kalimba'))
/* if (!thing instanceof Pattern) {
console.warn(
`Found Pattern that fails "instanceof Pattern" check.
This may happen if you are using multiple versions of @strudel.cycles/core.
Please check by running "npm ls @strudel.cycles/core".`,
);
}
console.log(thing);
} */
return is;
}
pattern['isPattern'] = isPattern;
export function reify(thing) {
function reify(thing) {
// Turns something into a pattern, unless it's already a pattern
if (isPattern(thing)) {
return thing;
@@ -1618,6 +1695,7 @@ export function reify(thing) {
}
return pure(thing);
}
pattern['reify'] = reify;
/** The given items are played at the same time at the same length.
*
@@ -1625,12 +1703,20 @@ export function reify(thing) {
* @example
* stack(g3, b3, [e4, d4]).note() // "g3,b3,[e4,d4]".note()
*/
export function stack(...pats) {
function stack(...pats) {
// Array test here is to avoid infinite recursions..
pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
const query = (state) => flatten(pats.map((pat) => pat.query(state)));
return new Pattern(query);
}
pattern['stack'] = stack;
function polyrhythm(...pats) {
return stack(...pats);
}
function pr(...pats) {
return stack(...pats);
}
pattern['pr'] = pattern['polyrhythm'] = pattern['stack'];
/** Concatenation: combines a list of patterns, switching between them successively, one per cycle:
*
@@ -1641,7 +1727,7 @@ export function stack(...pats) {
* slowcat(e5, b4, [d5, c5])
*
*/
export function slowcat(...pats) {
function slowcat(...pats) {
// Array test here is to avoid infinite recursions..
pats = pats.map((pat) => (Array.isArray(pat) ? sequence(...pat) : reify(pat)));
@@ -1661,12 +1747,13 @@ export function slowcat(...pats) {
};
return new Pattern(query).splitQueries();
}
pattern['slowcat'] = slowcat;
/** Concatenation: combines a list of patterns, switching between them successively, one per cycle. Unlike slowcat, this version will skip cycles.
* @param {...any} items - The items to concatenate
* @return {Pattern}
*/
export function slowcatPrime(...pats) {
function slowcatPrime(...pats) {
pats = pats.map(reify);
const query = function (state) {
const pat_n = Math.floor(state.span.begin) % pats.length;
@@ -1675,6 +1762,7 @@ export function slowcatPrime(...pats) {
};
return new Pattern(query).splitQueries();
}
pattern['slowcatPrime'] = slowcatPrime;
/** Concatenation: as with {@link slowcat}, but squashes a cycle from each pattern into one cycle
*
@@ -1687,9 +1775,10 @@ export function slowcatPrime(...pats) {
* // sequence(e5, b4, [d5, c5])
* // seq(e5, b4, [d5, c5])
*/
export function fastcat(...pats) {
function fastcat(...pats) {
return slowcat(...pats)._fast(pats.length);
}
pattern['fastcat'] = fastcat;
/** The given items are con**cat**enated, where each one takes one cycle. Synonym: slowcat
*
@@ -1699,16 +1788,17 @@ export function fastcat(...pats) {
* cat(e5, b4, [d5, c5]).note() // "<e5 b4 [d5 c5]>".note()
*
*/
export function cat(...pats) {
function cat(...pats) {
return slowcat(...pats);
}
pattern['cat'] = cat;
/** Like {@link seq}, but each step has a length, relative to the whole.
* @return {Pattern}
* @example
* timeCat([3,e3],[1, g3]).note() // "e3@3 g3".note()
*/
export function timeCat(...timepats) {
function timeCat(...timepats) {
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
let begin = Fraction(0);
const pats = [];
@@ -1719,20 +1809,23 @@ export function timeCat(...timepats) {
}
return stack(...pats);
}
pattern['timeCat'] = timeCat;
/** See {@link fastcat} */
export function sequence(...pats) {
function sequence(...pats) {
return fastcat(...pats);
}
pattern['sequence'] = sequence;
/** Like **cat**, but the items are crammed into one cycle. Synonyms: fastcat, sequence
* @example
* seq(e5, b4, [d5, c5]).note() // "e5 b4 [d5 c5]".note()
*
*/
export function seq(...pats) {
function seq(...pats) {
return fastcat(...pats);
}
pattern['seq'] = seq;
function _sequenceCount(x) {
if (Array.isArray(x)) {
@@ -1747,7 +1840,7 @@ function _sequenceCount(x) {
return [reify(x), 1];
}
export function polymeterSteps(steps, ...args) {
function polymeterSteps(steps, ...args) {
const seqs = args.map((a) => _sequenceCount(a));
if (seqs.length == 0) {
return silence;
@@ -1758,7 +1851,7 @@ export function polymeterSteps(steps, ...args) {
const pats = [];
for (const seq of seqs) {
if (seq[1] == 0) {
next;
continue;
}
if (steps == seq[1]) {
pats.push(seq[0]);
@@ -1768,57 +1861,27 @@ export function polymeterSteps(steps, ...args) {
}
return stack(...pats);
}
pattern['polymeterSteps'] = polymeterSteps;
export function polymeter(...args) {
function polymeter(...args) {
return polymeterSteps(0, ...args);
}
pattern['polymeter'] = polymeter;
// alias
export function pm(...args) {
polymeter(...args);
function pm(...args) {
return polymeter(...args);
}
export const add = curry((a, pat) => pat.add(a));
export const chop = curry((a, pat) => pat.chop(a));
export const chunk = curry((a, pat) => pat.chunk(a));
export const chunkBack = curry((a, pat) => pat.chunkBack(a));
export const div = curry((a, pat) => pat.div(a));
export const early = curry((a, pat) => pat.early(a));
export const echo = curry((a, b, c, pat) => pat.echo(a, b, c));
export const every = curry((i, f, pat) => pat.every(i, f));
export const fast = curry((a, pat) => pat.fast(a));
export const inv = (pat) => pat.inv();
export const invert = (pat) => pat.invert();
export const iter = curry((a, pat) => pat.iter(a));
export const iterBack = curry((a, pat) => pat.iterBack(a));
export const jux = curry((f, pat) => pat.jux(f));
export const juxBy = curry((by, f, pat) => pat.juxBy(by, f));
export const late = curry((a, pat) => pat.late(a));
export const linger = curry((a, pat) => pat.linger(a));
export const mask = curry((a, pat) => pat.mask(a));
export const mul = curry((a, pat) => pat.mul(a));
export const off = curry((t, f, pat) => pat.off(t, f));
export const ply = curry((a, pat) => pat.ply(a));
export const range = curry((a, b, pat) => pat.range(a, b));
export const rangex = curry((a, b, pat) => pat.rangex(a, b));
export const range2 = curry((a, b, pat) => pat.range2(a, b));
export const rev = (pat) => pat.rev();
export const slow = curry((a, pat) => pat.slow(a));
export const struct = curry((a, pat) => pat.struct(a));
export const sub = curry((a, pat) => pat.sub(a));
export const superimpose = curry((array, pat) => pat.superimpose(...array));
export const set = curry((a, pat) => pat.set(a));
export const when = curry((binary, f, pat) => pat.when(binary, f));
pattern['pm'] = polymeter;
// problem: curried functions with spread arguments must have pat at the beginning
// with this, we cannot keep the pattern open at the end.. solution for now: use array to keep using pat as last arg
// these are the core composable functions. they are extended with Pattern.prototype.define below
Pattern.prototype.composable = { fast, slow, early, late, superimpose };
Pattern.prototype.composable = {fast: pattern.fast, slow: pattern.slow, early: pattern.early, late: pattern.late, superimpose: pattern.superimpose };
// adds Pattern.prototype.composable to given function as child functions
// then you can do transpose(2).late(0.2) instead of x => x.transpose(2).late(0.2)
export function makeComposable(func) {
function makeComposable(func) {
Object.entries(Pattern.prototype.composable).forEach(([functionName, composable]) => {
// compose with dot
func[functionName] = (...args) => {
@@ -1831,25 +1894,31 @@ export function makeComposable(func) {
});
return func;
}
pattern['makeComposable'] = makeComposable;
export const patternify2 = (f) => (pata, patb, pat) =>
const patternify2 = (f) => (pata, patb, pat) =>
pata
.fmap((a) => (b) => f.call(pat, a, b))
.appLeft(patb)
.innerJoin();
export const patternify3 = (f) => (pata, patb, patc, pat) =>
pattern['patternify2'] = patternify2;
const patternify3 = (f) => (pata, patb, patc, pat) =>
pata
.fmap((a) => (b) => (c) => f.call(pat, a, b, c))
.appLeft(patb)
.appLeft(patc)
.innerJoin();
export const patternify4 = (f) => (pata, patb, patc, patd, pat) =>
pattern['patternify3'] = patternify3;
const patternify4 = (f) => (pata, patb, patc, patd, pat) =>
pata
.fmap((a) => (b) => (c) => (d) => f.call(pat, a, b, c, d))
.appLeft(patb)
.appLeft(patc)
.appLeft(patd)
.innerJoin();
pattern['patternify4'] = patternify4;
Pattern.prototype.echo = function (...args) {
args = args.map(reify);
@@ -1901,6 +1970,7 @@ Pattern.prototype.range2 = function (...args) {
};
// call this after all Pattern.prototype.define calls have been executed! (right before evaluate)
// (but isn't this called for every define call anyway?)
Pattern.prototype.bootstrap = function () {
// makeComposable(Pattern.prototype);
const bootstrapped = Object.fromEntries(
@@ -1916,10 +1986,9 @@ Pattern.prototype.bootstrap = function () {
this.patternified.forEach((prop) => {
// the following will patternify all functions in Pattern.prototype.patternified
Pattern.prototype[prop] = function (...args) {
return this.patternify(x => x.innerJoin(), Pattern.prototype['_' + prop])(...args);
return this.patternify((x) => x.innerJoin(), Pattern.prototype['_' + prop])(...args);
};
/*
const func = Pattern.prototype['_' + prop];
Pattern.prototype[prop] = function (...args) {
@@ -1930,22 +1999,22 @@ Pattern.prototype.bootstrap = function () {
// a getter that returns a function, so 'pat' can be
// accessed by closures that are methods of that function..
get: function() {
const pat = this;
// wrap the default behaviour
const pat = this;
// wrap the default behaviour
const wrapper = pat.patternify(x => x.innerJoin(), func);
// add the variants
// add the variants
wrapper['in'] = pat.patternify(x => x.innerJoin(), func);
wrapper['out'] = pat.patternify(x => x.outerJoin(), func);
wrapper['trig'] = pat.patternify(x => x.trigJoin(), func);
wrapper['trigzero'] = pat.patternify(x => x.trigzeroJoin(), func);
wrapper['squeeze'] = pat.patternify(x => x.squeezeJoin(), func);
return wrapper;
return wrapper;
}
});
*/
// with the following, you can do, e.g. `stack(c3).fast.slowcat(1, 2, 4, 8)` instead of `stack(c3).fast(slowcat(1, 2, 4, 8))`
// TODO: find a way to implement below outside of constructor (code only worked there)
/* Object.assign(
@@ -1978,4 +2047,5 @@ Pattern.prototype.define = (name, func, options = {}) => {
// Pattern.prototype.define('early', (a, pat) => pat.early(a), { patternified: true, composable: true });
Pattern.prototype.define('hush', (pat) => pat.hush(), { patternified: false, composable: true });
Pattern.prototype.define('bypass', (pat) => pat.bypass(on), { patternified: true, composable: true });
export default pattern;
+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}`);
+2 -1
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';
+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';
+37 -36
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,11 +44,7 @@ import {
square2,
tri,
tri2,
id,
ply,
rev,
time,
} from '../index.mjs';
time} = core;
import { steady } from '../signal.mjs';
@@ -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'],
[
+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
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/eval",
"version": "0.4.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/eval",
"version": "0.4.0",
"version": "0.4.1",
"description": "Code evaluator for strudel",
"main": "index.mjs",
"type": "module",
@@ -28,7 +28,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.0",
"@strudel.cycles/core": "^0.4.1",
"estraverse": "^5.3.0",
"shift-ast": "^7.0.0",
"shift-codegen": "^8.1.0",
+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;
+1 -1
View File
@@ -8,7 +8,7 @@ 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 () => {
+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:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/midi",
"version": "0.4.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/midi",
"version": "0.4.0",
"version": "0.4.1",
"description": "Midi API for strudel",
"main": "index.mjs",
"repository": {
@@ -21,7 +21,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/tone": "^0.4.0",
"@strudel.cycles/tone": "^0.4.1",
"tone": "^14.7.77",
"webmidi": "^3.0.21"
}
+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':
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/mini",
"version": "0.4.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/mini",
"version": "0.4.0",
"version": "0.4.1",
"description": "Mini notation for strudel",
"main": "index.mjs",
"type": "module",
@@ -26,9 +26,9 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.0",
"@strudel.cycles/eval": "^0.4.0",
"@strudel.cycles/tone": "^0.4.0"
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/eval": "^0.4.1",
"@strudel.cycles/tone": "^0.4.1"
},
"devDependencies": {
"peggy": "^2.0.1"
+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() {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/osc",
"version": "0.3.0",
"version": "0.3.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/osc",
"version": "0.3.0",
"version": "0.3.1",
"description": "OSC messaging for strudel",
"main": "osc.mjs",
"scripts": {
+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'),
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/react",
"version": "0.4.1",
"version": "0.4.2",
"description": "React components for strudel",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
@@ -39,10 +39,10 @@
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@codemirror/lang-javascript": "^6.1.1",
"@strudel.cycles/core": "^0.4.0",
"@strudel.cycles/tone": "^0.4.0",
"@strudel.cycles/transpiler": "^0.4.0",
"@strudel.cycles/webaudio": "^0.4.1",
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/tone": "^0.4.1",
"@strudel.cycles/transpiler": "^0.4.1",
"@strudel.cycles/webaudio": "^0.4.2",
"@uiw/codemirror-themes": "^4.12.4",
"@uiw/react-codemirror": "^4.12.4",
"react-hook-inview": "^4.5.0"
+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]) {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/soundfonts",
"version": "0.4.1",
"version": "0.4.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/soundfonts",
"version": "0.4.1",
"version": "0.4.2",
"description": "Soundsfont support for strudel",
"main": "index.mjs",
"type": "module",
@@ -22,8 +22,8 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.0",
"@strudel.cycles/webaudio": "^0.4.1",
"@strudel.cycles/core": "^0.4.1",
"@strudel.cycles/webaudio": "^0.4.2",
"sfumato": "^0.1.2",
"soundfont2": "^0.4.0"
},
+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
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tonal",
"version": "0.4.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tonal",
"version": "0.4.0",
"version": "0.4.1",
"description": "Tonal functions for strudel",
"main": "index.mjs",
"type": "module",
@@ -25,8 +25,8 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.0",
"@tonaljs/tonal": "^4.6.5",
"@strudel.cycles/core": "^0.4.1",
"@tonaljs/tonal": "^4.7.2",
"chord-voicings": "^0.0.1",
"webmidi": "^3.0.21"
}
+2 -1
View File
@@ -7,7 +7,8 @@ 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', () => {
+2 -1
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) {
+5 -3
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) =>
@@ -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;
});
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tone",
"version": "0.4.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/tone",
"version": "0.4.0",
"version": "0.4.1",
"description": "Tone.js API for strudel",
"main": "index.mjs",
"type": "module",
@@ -22,7 +22,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.0",
"@strudel.cycles/core": "^0.4.1",
"tone": "^14.7.77"
}
}
+2 -1
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', () => {
+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 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/transpiler",
"version": "0.4.0",
"version": "0.4.1",
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
"main": "index.mjs",
"scripts": {
@@ -24,7 +24,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.0",
"@strudel.cycles/core": "^0.4.1",
"acorn": "^8.8.1",
"escodegen": "^2.0.0",
"estree-walker": "^3.0.1"
+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;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.4.1",
"version": "0.4.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webaudio",
"version": "0.4.1",
"version": "0.4.2",
"description": "Web Audio helpers for Strudel",
"main": "index.mjs",
"type": "module",
@@ -30,6 +30,6 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.0"
"@strudel.cycles/core": "^0.4.1"
}
}
+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>
+2 -3
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';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webdirt",
"version": "0.4.0",
"version": "0.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/webdirt",
"version": "0.4.0",
"version": "0.4.1",
"description": "WebDirt integration for Strudel",
"main": "index.mjs",
"type": "module",
@@ -22,7 +22,7 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.0",
"@strudel.cycles/core": "^0.4.1",
"WebDirt": "github:dktr0/WebDirt"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@strudel.cycles/xen",
"version": "0.4.0",
"version": "0.4.1",
"description": "Xenharmonic API for strudel",
"main": "index.mjs",
"scripts": {
@@ -24,6 +24,6 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
"@strudel.cycles/core": "^0.4.0"
"@strudel.cycles/core": "^0.4.1"
}
}
+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,
+74 -401
View File
@@ -7,7 +7,7 @@ exports[`renders tunes > tune: amensister 1`] = `
"3/8 -> 1/2: {\\"n\\":1,\\"s\\":\\"amencutup\\",\\"room\\":0.5}",
"3/4 -> 7/8: {\\"n\\":3,\\"s\\":\\"amencutup\\",\\"room\\":0.5}",
"7/8 -> 1/1: {\\"n\\":3,\\"s\\":\\"amencutup\\",\\"room\\":0.5}",
"1/4 -> 3/8: {\\"n\\":1,\\"speed\\":-2,\\"delay\\":0.5,\\"s\\":\\"amencutup\\",\\"room\\":0.5}",
"1/4 -> 3/8: {\\"n\\":1,\\"speed\\":2,\\"delay\\":0.5,\\"s\\":\\"amencutup\\",\\"room\\":0.5}",
"0/1 -> 1/8: {\\"note\\":\\"Eb1\\",\\"s\\":\\"sawtooth\\",\\"decay\\":0.1,\\"sustain\\":0,\\"gain\\":0.4,\\"cutoff\\":300.0066107586751,\\"resonance\\":10}",
"0/1 -> 1/8: {\\"note\\":\\"F1\\",\\"s\\":\\"sawtooth\\",\\"decay\\":0.1,\\"sustain\\":0,\\"gain\\":0.4,\\"cutoff\\":300.0066107586751,\\"resonance\\":10}",
"1/4 -> 3/8: {\\"note\\":\\"A1\\",\\"s\\":\\"sawtooth\\",\\"decay\\":0.1,\\"sustain\\":0,\\"gain\\":0.4,\\"cutoff\\":300.7878869297153,\\"resonance\\":10}",
@@ -39,6 +39,35 @@ exports[`renders tunes > tune: amensister 1`] = `
]
`;
exports[`renders tunes > tune: arpoon 1`] = `
[
"0/1 -> 2/3: {\\"note\\":55.000528662214684,\\"cutoff\\":509.2515887569149,\\"resonance\\":12,\\"gain\\":0.5,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.5249999999999999,\\"s\\":\\"piano\\",\\"clip\\":1}",
"0/1 -> 2/3: {\\"note\\":55.000528662214684,\\"cutoff\\":509.2515887569149,\\"resonance\\":12,\\"gain\\":0.8,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.5249999999999999,\\"s\\":\\"piano\\",\\"clip\\":1}",
"0/1 -> 2/3: {\\"note\\":55.000528662214684,\\"cutoff\\":509.2515887569149,\\"resonance\\":12,\\"gain\\":0.5,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.5249999999999999,\\"s\\":\\"piano\\",\\"clip\\":1}",
"0/1 -> 2/3: {\\"note\\":64.00052866221468,\\"cutoff\\":509.2515887569149,\\"resonance\\":12,\\"gain\\":0.5,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.5249999999999999,\\"s\\":\\"piano\\",\\"clip\\":1}",
"0/1 -> 2/3: {\\"note\\":64.00052866221468,\\"cutoff\\":509.2515887569149,\\"resonance\\":12,\\"gain\\":0.8,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.5249999999999999,\\"s\\":\\"piano\\",\\"clip\\":1}",
"0/1 -> 2/3: {\\"note\\":64.00052866221468,\\"cutoff\\":509.2515887569149,\\"resonance\\":12,\\"gain\\":0.5,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.5249999999999999,\\"s\\":\\"piano\\",\\"clip\\":1}",
"1/3 -> 1/1: {\\"note\\":60.003688107962134,\\"cutoff\\":564.5418893373399,\\"resonance\\":12,\\"gain\\":0.8,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.5799038105676657,\\"s\\":\\"piano\\",\\"clip\\":1}",
"1/3 -> 1/1: {\\"note\\":60.003688107962134,\\"cutoff\\":564.5418893373399,\\"resonance\\":12,\\"gain\\":0.5,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.5799038105676657,\\"s\\":\\"piano\\",\\"clip\\":1}",
"1/3 -> 1/1: {\\"note\\":60.003688107962134,\\"cutoff\\":564.5418893373399,\\"resonance\\":12,\\"gain\\":0.8,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.5799038105676657,\\"s\\":\\"piano\\",\\"clip\\":1}",
"2/3 -> 4/3: {\\"note\\":59.010756146386846,\\"cutoff\\":688.232561769837,\\"resonance\\":12,\\"gain\\":0.5,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.6,\\"s\\":\\"piano\\",\\"clip\\":1}",
"2/3 -> 4/3: {\\"note\\":59.010756146386846,\\"cutoff\\":688.232561769837,\\"resonance\\":12,\\"gain\\":0.8,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.6,\\"s\\":\\"piano\\",\\"clip\\":1}",
"2/3 -> 4/3: {\\"note\\":59.010756146386846,\\"cutoff\\":688.232561769837,\\"resonance\\":12,\\"gain\\":0.5,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.6,\\"s\\":\\"piano\\",\\"clip\\":1}",
"2/3 -> 4/3: {\\"note\\":59.010756146386846,\\"cutoff\\":688.232561769837,\\"resonance\\":12,\\"gain\\":0.8,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.6,\\"s\\":\\"piano\\",\\"clip\\":1}",
"2/3 -> 4/3: {\\"note\\":64.01075614638685,\\"cutoff\\":688.232561769837,\\"resonance\\":12,\\"gain\\":0.5,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.6,\\"s\\":\\"piano\\",\\"clip\\":1}",
"2/3 -> 4/3: {\\"note\\":64.01075614638685,\\"cutoff\\":688.232561769837,\\"resonance\\":12,\\"gain\\":0.8,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.6,\\"s\\":\\"piano\\",\\"clip\\":1}",
"2/3 -> 4/3: {\\"note\\":64.01075614638685,\\"cutoff\\":688.232561769837,\\"resonance\\":12,\\"gain\\":0.5,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.6,\\"s\\":\\"piano\\",\\"clip\\":1}",
"2/3 -> 4/3: {\\"note\\":64.01075614638685,\\"cutoff\\":688.232561769837,\\"resonance\\":12,\\"gain\\":0.8,\\"decay\\":0.16,\\"sustain\\":0.5,\\"delay\\":0.2,\\"room\\":0.5,\\"pan\\":0.6,\\"s\\":\\"piano\\",\\"clip\\":1}",
"0/1 -> 1/1: {\\"note\\":33,\\"s\\":\\"sawtooth\\",\\"clip\\":1,\\"cutoff\\":300}",
"0/1 -> 1/1: {\\"note\\":33.12,\\"s\\":\\"sawtooth\\",\\"clip\\":1,\\"cutoff\\":300}",
"0/1 -> 1/2: {\\"s\\":\\"bd\\",\\"bank\\":\\"RolandTR909\\",\\"gain\\":0.5}",
"1/2 -> 1/1: {\\"s\\":\\"bd\\",\\"bank\\":\\"RolandTR909\\",\\"gain\\":0.5}",
"1/2 -> 2/3: {\\"s\\":\\"hh\\",\\"bank\\":\\"RolandTR909\\",\\"gain\\":0.5}",
"2/3 -> 5/6: {\\"s\\":\\"hh\\",\\"bank\\":\\"RolandTR909\\",\\"gain\\":0.5}",
"5/6 -> 1/1: {\\"s\\":\\"hh\\",\\"bank\\":\\"RolandTR909\\",\\"gain\\":0.5}",
]
`;
exports[`renders tunes > tune: barryHarris 1`] = `
[
"0/1 -> 2/1: {\\"note\\":\\"C3\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.4722222222222222}",
@@ -318,36 +347,6 @@ exports[`renders tunes > tune: blippyRhodes 1`] = `
]
`;
exports[`renders tunes > tune: bossa 1`] = `
[
"0/1 -> 1/4: {\\"note\\":\\"Bb3\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5185185185185186}",
"0/1 -> 1/4: {\\"note\\":\\"D4\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.537037037037037}",
"0/1 -> 1/4: {\\"note\\":\\"Eb4\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5416666666666667}",
"0/1 -> 1/4: {\\"note\\":\\"G4\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5601851851851851}",
"1/2 -> 5/4: {\\"note\\":\\"Bb3\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5185185185185186}",
"1/2 -> 5/4: {\\"note\\":\\"D4\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.537037037037037}",
"1/2 -> 5/4: {\\"note\\":\\"Eb4\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5416666666666667}",
"1/2 -> 5/4: {\\"note\\":\\"G4\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5601851851851851}",
"0/1 -> 1/2: {\\"note\\":\\"C2\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.41666666666666663}",
"3/4 -> 7/8: {\\"note\\":\\"G2\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.44907407407407407}",
]
`;
exports[`renders tunes > tune: bossaRandom 1`] = `
[
"0/1 -> 1/4: {\\"note\\":\\"G4\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5601851851851851}",
"0/1 -> 1/4: {\\"note\\":\\"B4\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5787037037037037}",
"0/1 -> 1/4: {\\"note\\":\\"C5\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5833333333333333}",
"0/1 -> 1/4: {\\"note\\":\\"E5\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.6018518518518519}",
"3/4 -> 1/1: {\\"note\\":\\"G4\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5601851851851851}",
"3/4 -> 1/1: {\\"note\\":\\"B4\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5787037037037037}",
"3/4 -> 1/1: {\\"note\\":\\"C5\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.5833333333333333}",
"3/4 -> 1/1: {\\"note\\":\\"E5\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.6018518518518519}",
"0/1 -> 1/2: {\\"note\\":\\"A2\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.45833333333333337}",
"1/2 -> 1/1: {\\"note\\":\\"A2\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.45833333333333337}",
]
`;
exports[`renders tunes > tune: bridgeIsOver 1`] = `
[
"0/1 -> 5/26: {\\"note\\":\\"B2\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.46759259259259256}",
@@ -1409,6 +1408,20 @@ exports[`renders tunes > tune: chop 1`] = `
]
`;
exports[`renders tunes > tune: csoundDemo 1`] = `
[
"0/1 -> 1/1: {\\"note\\":\\"D3\\"}",
"-1/4 -> 1/4: {\\"note\\":\\"Bb3\\"}",
"1/4 -> 5/4: {\\"note\\":\\"F3\\"}",
"0/1 -> 1/2: {\\"note\\":\\"F4\\"}",
"1/2 -> 3/2: {\\"note\\":\\"C4\\"}",
"-1/4 -> 1/4: {\\"note\\":\\"A4\\"}",
"1/4 -> 3/4: {\\"note\\":\\"A4\\"}",
"1/4 -> 3/4: {\\"note\\":\\"A4\\"}",
"3/4 -> 7/4: {\\"note\\":\\"E4\\"}",
]
`;
exports[`renders tunes > tune: delay 1`] = `
[
"0/1 -> 1/2: {\\"s\\":\\"bd\\",\\"delay\\":0,\\"delaytime\\":0.16,\\"delayfeedback\\":0.8,\\"speed\\":-1}",
@@ -7244,257 +7257,6 @@ exports[`renders tunes > tune: giantSteps 1`] = `
]
`;
exports[`renders tunes > tune: giantStepsReggae 1`] = `
[
"0/1 -> 25/32: {\\"note\\":\\"F#5\\"}",
"25/32 -> 25/16: {\\"note\\":\\"D5\\"}",
"25/64 -> 75/128: {\\"note\\":\\"A#3\\"}",
"25/64 -> 75/128: {\\"note\\":\\"C#4\\"}",
"25/64 -> 75/128: {\\"note\\":\\"D#4\\"}",
"25/64 -> 75/128: {\\"note\\":\\"F#4\\"}",
"0/1 -> 25/64: {\\"note\\":\\"B2\\"}",
"25/32 -> 75/64: {\\"note\\":\\"D2\\"}",
"25/32 -> 25/16: {\\"note\\":\\"D5\\"}",
"25/16 -> 75/32: {\\"note\\":\\"B4\\"}",
"75/64 -> 175/128: {\\"note\\":\\"F#3\\"}",
"75/64 -> 175/128: {\\"note\\":\\"B3\\"}",
"75/64 -> 175/128: {\\"note\\":\\"C4\\"}",
"75/64 -> 175/128: {\\"note\\":\\"E4\\"}",
"125/64 -> 275/128: {\\"note\\":\\"F#3\\"}",
"125/64 -> 275/128: {\\"note\\":\\"A3\\"}",
"125/64 -> 275/128: {\\"note\\":\\"B3\\"}",
"125/64 -> 275/128: {\\"note\\":\\"D4\\"}",
"25/32 -> 75/64: {\\"note\\":\\"D2\\"}",
"25/16 -> 125/64: {\\"note\\":\\"G2\\"}",
"25/16 -> 75/32: {\\"note\\":\\"B4\\"}",
"75/32 -> 25/8: {\\"note\\":\\"G4\\"}",
"125/64 -> 275/128: {\\"note\\":\\"F#3\\"}",
"125/64 -> 275/128: {\\"note\\":\\"A3\\"}",
"125/64 -> 275/128: {\\"note\\":\\"B3\\"}",
"125/64 -> 275/128: {\\"note\\":\\"D4\\"}",
"175/64 -> 375/128: {\\"note\\":\\"Ab3\\"}",
"175/64 -> 375/128: {\\"note\\":\\"C4\\"}",
"175/64 -> 375/128: {\\"note\\":\\"D4\\"}",
"175/64 -> 375/128: {\\"note\\":\\"G4\\"}",
"75/32 -> 175/64: {\\"note\\":\\"D2\\"}",
"75/32 -> 25/8: {\\"note\\":\\"G4\\"}",
"25/8 -> 75/16: {\\"note\\":\\"Bb4\\"}",
"225/64 -> 475/128: {\\"note\\":\\"G3\\"}",
"225/64 -> 475/128: {\\"note\\":\\"Bb3\\"}",
"225/64 -> 475/128: {\\"note\\":\\"D4\\"}",
"225/64 -> 475/128: {\\"note\\":\\"F4\\"}",
"25/8 -> 225/64: {\\"note\\":\\"Eb2\\"}",
"125/32 -> 275/64: {\\"note\\":\\"Bb2\\"}",
"25/8 -> 75/16: {\\"note\\":\\"Bb4\\"}",
"75/16 -> 175/32: {\\"note\\":\\"B4\\"}",
"275/64 -> 575/128: {\\"note\\":\\"G3\\"}",
"275/64 -> 575/128: {\\"note\\":\\"Bb3\\"}",
"275/64 -> 575/128: {\\"note\\":\\"D4\\"}",
"275/64 -> 575/128: {\\"note\\":\\"F4\\"}",
"125/32 -> 275/64: {\\"note\\":\\"Bb2\\"}",
"75/16 -> 325/64: {\\"note\\":\\"A2\\"}",
"75/16 -> 175/32: {\\"note\\":\\"B4\\"}",
"175/32 -> 25/4: {\\"note\\":\\"A4\\"}",
"325/64 -> 675/128: {\\"note\\":\\"G3\\"}",
"325/64 -> 675/128: {\\"note\\":\\"B3\\"}",
"325/64 -> 675/128: {\\"note\\":\\"C4\\"}",
"325/64 -> 675/128: {\\"note\\":\\"E4\\"}",
"375/64 -> 775/128: {\\"note\\":\\"F#3\\"}",
"375/64 -> 775/128: {\\"note\\":\\"B3\\"}",
"375/64 -> 775/128: {\\"note\\":\\"C4\\"}",
"375/64 -> 775/128: {\\"note\\":\\"E4\\"}",
"75/16 -> 325/64: {\\"note\\":\\"A2\\"}",
"175/32 -> 375/64: {\\"note\\":\\"D2\\"}",
"175/32 -> 25/4: {\\"note\\":\\"A4\\"}",
"25/4 -> 225/32: {\\"note\\":\\"D5\\"}",
"375/64 -> 775/128: {\\"note\\":\\"F#3\\"}",
"375/64 -> 775/128: {\\"note\\":\\"B3\\"}",
"375/64 -> 775/128: {\\"note\\":\\"C4\\"}",
"375/64 -> 775/128: {\\"note\\":\\"E4\\"}",
"425/64 -> 875/128: {\\"note\\":\\"F#3\\"}",
"425/64 -> 875/128: {\\"note\\":\\"A3\\"}",
"425/64 -> 875/128: {\\"note\\":\\"B3\\"}",
"425/64 -> 875/128: {\\"note\\":\\"D4\\"}",
"25/4 -> 425/64: {\\"note\\":\\"G2\\"}",
"25/4 -> 225/32: {\\"note\\":\\"D5\\"}",
"225/32 -> 125/16: {\\"note\\":\\"Bb4\\"}",
"125/16 -> 275/32: {\\"note\\":\\"G4\\"}",
"475/64 -> 975/128: {\\"note\\":\\"Ab3\\"}",
"475/64 -> 975/128: {\\"note\\":\\"C4\\"}",
"475/64 -> 975/128: {\\"note\\":\\"D4\\"}",
"475/64 -> 975/128: {\\"note\\":\\"G4\\"}",
"225/32 -> 475/64: {\\"note\\":\\"Bb2\\"}",
"125/16 -> 525/64: {\\"note\\":\\"Eb2\\"}",
"125/16 -> 275/32: {\\"note\\":\\"G4\\"}",
"275/32 -> 75/8: {\\"note\\":\\"Eb4\\"}",
"525/64 -> 1075/128: {\\"note\\":\\"G3\\"}",
"525/64 -> 1075/128: {\\"note\\":\\"Bb3\\"}",
"525/64 -> 1075/128: {\\"note\\":\\"D4\\"}",
"525/64 -> 1075/128: {\\"note\\":\\"F4\\"}",
"575/64 -> 1175/128: {\\"note\\":\\"E3\\"}",
"575/64 -> 1175/128: {\\"note\\":\\"G#3\\"}",
"575/64 -> 1175/128: {\\"note\\":\\"A#3\\"}",
"575/64 -> 1175/128: {\\"note\\":\\"D#4\\"}",
"125/16 -> 525/64: {\\"note\\":\\"Eb2\\"}",
"275/32 -> 575/64: {\\"note\\":\\"F#2\\"}",
"275/32 -> 75/8: {\\"note\\":\\"Eb4\\"}",
"75/8 -> 175/16: {\\"note\\":\\"F#4\\"}",
"575/64 -> 1175/128: {\\"note\\":\\"E3\\"}",
"575/64 -> 1175/128: {\\"note\\":\\"G#3\\"}",
"575/64 -> 1175/128: {\\"note\\":\\"A#3\\"}",
"575/64 -> 1175/128: {\\"note\\":\\"D#4\\"}",
"625/64 -> 1275/128: {\\"note\\":\\"A#3\\"}",
"625/64 -> 1275/128: {\\"note\\":\\"C#4\\"}",
"625/64 -> 1275/128: {\\"note\\":\\"D#4\\"}",
"625/64 -> 1275/128: {\\"note\\":\\"F#4\\"}",
"75/8 -> 625/64: {\\"note\\":\\"B2\\"}",
"75/8 -> 175/16: {\\"note\\":\\"F#4\\"}",
"175/16 -> 375/32: {\\"note\\":\\"G4\\"}",
"675/64 -> 1375/128: {\\"note\\":\\"A#3\\"}",
"675/64 -> 1375/128: {\\"note\\":\\"C#4\\"}",
"675/64 -> 1375/128: {\\"note\\":\\"D#4\\"}",
"675/64 -> 1375/128: {\\"note\\":\\"F#4\\"}",
"325/32 -> 675/64: {\\"note\\":\\"F#2\\"}",
"175/16 -> 725/64: {\\"note\\":\\"F2\\"}",
"175/16 -> 375/32: {\\"note\\":\\"G4\\"}",
"375/32 -> 25/2: {\\"note\\":\\"F4\\"}",
"725/64 -> 1475/128: {\\"note\\":\\"Ab3\\"}",
"725/64 -> 1475/128: {\\"note\\":\\"C4\\"}",
"725/64 -> 1475/128: {\\"note\\":\\"Eb4\\"}",
"725/64 -> 1475/128: {\\"note\\":\\"G4\\"}",
"175/16 -> 725/64: {\\"note\\":\\"F2\\"}",
"375/32 -> 775/64: {\\"note\\":\\"Bb2\\"}",
"375/32 -> 25/2: {\\"note\\":\\"F4\\"}",
"25/2 -> 225/16: {\\"note\\":\\"Bb4\\"}",
"775/64 -> 1575/128: {\\"note\\":\\"Ab3\\"}",
"775/64 -> 1575/128: {\\"note\\":\\"C4\\"}",
"775/64 -> 1575/128: {\\"note\\":\\"D4\\"}",
"775/64 -> 1575/128: {\\"note\\":\\"G4\\"}",
"825/64 -> 1675/128: {\\"note\\":\\"G3\\"}",
"825/64 -> 1675/128: {\\"note\\":\\"Bb3\\"}",
"825/64 -> 1675/128: {\\"note\\":\\"D4\\"}",
"825/64 -> 1675/128: {\\"note\\":\\"F4\\"}",
"375/32 -> 775/64: {\\"note\\":\\"Bb2\\"}",
"25/2 -> 825/64: {\\"note\\":\\"Eb2\\"}",
"25/2 -> 225/16: {\\"note\\":\\"Bb4\\"}",
"825/64 -> 1675/128: {\\"note\\":\\"G3\\"}",
"825/64 -> 1675/128: {\\"note\\":\\"Bb3\\"}",
"825/64 -> 1675/128: {\\"note\\":\\"D4\\"}",
"825/64 -> 1675/128: {\\"note\\":\\"F4\\"}",
"875/64 -> 1775/128: {\\"note\\":\\"G3\\"}",
"875/64 -> 1775/128: {\\"note\\":\\"Bb3\\"}",
"875/64 -> 1775/128: {\\"note\\":\\"D4\\"}",
"875/64 -> 1775/128: {\\"note\\":\\"F4\\"}",
"425/32 -> 875/64: {\\"note\\":\\"Bb2\\"}",
"25/2 -> 225/16: {\\"note\\":\\"Bb4\\"}",
"225/16 -> 475/32: {\\"note\\":\\"B4\\"}",
"475/32 -> 125/8: {\\"note\\":\\"A4\\"}",
"925/64 -> 1875/128: {\\"note\\":\\"G3\\"}",
"925/64 -> 1875/128: {\\"note\\":\\"B3\\"}",
"925/64 -> 1875/128: {\\"note\\":\\"C4\\"}",
"925/64 -> 1875/128: {\\"note\\":\\"E4\\"}",
"225/16 -> 925/64: {\\"note\\":\\"A2\\"}",
"475/32 -> 975/64: {\\"note\\":\\"D2\\"}",
"475/32 -> 125/8: {\\"note\\":\\"A4\\"}",
"125/8 -> 275/16: {\\"note\\":\\"D5\\"}",
"975/64 -> 1975/128: {\\"note\\":\\"F#3\\"}",
"975/64 -> 1975/128: {\\"note\\":\\"B3\\"}",
"975/64 -> 1975/128: {\\"note\\":\\"C4\\"}",
"975/64 -> 1975/128: {\\"note\\":\\"E4\\"}",
"475/32 -> 975/64: {\\"note\\":\\"D2\\"}",
"125/8 -> 1025/64: {\\"note\\":\\"G2\\"}",
"125/8 -> 275/16: {\\"note\\":\\"D5\\"}",
"1025/64 -> 2075/128: {\\"note\\":\\"F#3\\"}",
"1025/64 -> 2075/128: {\\"note\\":\\"A3\\"}",
"1025/64 -> 2075/128: {\\"note\\":\\"B3\\"}",
"1025/64 -> 2075/128: {\\"note\\":\\"D4\\"}",
"1075/64 -> 2175/128: {\\"note\\":\\"F#3\\"}",
"1075/64 -> 2175/128: {\\"note\\":\\"A3\\"}",
"1075/64 -> 2175/128: {\\"note\\":\\"B3\\"}",
"1075/64 -> 2175/128: {\\"note\\":\\"D4\\"}",
"125/8 -> 1025/64: {\\"note\\":\\"G2\\"}",
"525/32 -> 1075/64: {\\"note\\":\\"D2\\"}",
"125/8 -> 275/16: {\\"note\\":\\"D5\\"}",
"275/16 -> 575/32: {\\"note\\":\\"D#5\\"}",
"575/32 -> 75/4: {\\"note\\":\\"C#5\\"}",
"1125/64 -> 2275/128: {\\"note\\":\\"E3\\"}",
"1125/64 -> 2275/128: {\\"note\\":\\"G#3\\"}",
"1125/64 -> 2275/128: {\\"note\\":\\"B3\\"}",
"1125/64 -> 2275/128: {\\"note\\":\\"D#4\\"}",
"275/16 -> 1125/64: {\\"note\\":\\"C#2\\"}",
"575/32 -> 1175/64: {\\"note\\":\\"F#2\\"}",
"575/32 -> 75/4: {\\"note\\":\\"C#5\\"}",
"75/4 -> 325/16: {\\"note\\":\\"F#5\\"}",
"1175/64 -> 2375/128: {\\"note\\":\\"E3\\"}",
"1175/64 -> 2375/128: {\\"note\\":\\"G#3\\"}",
"1175/64 -> 2375/128: {\\"note\\":\\"A#3\\"}",
"1175/64 -> 2375/128: {\\"note\\":\\"D#4\\"}",
"575/32 -> 1175/64: {\\"note\\":\\"F#2\\"}",
"75/4 -> 1225/64: {\\"note\\":\\"B2\\"}",
"75/4 -> 325/16: {\\"note\\":\\"F#5\\"}",
"1225/64 -> 2475/128: {\\"note\\":\\"A#3\\"}",
"1225/64 -> 2475/128: {\\"note\\":\\"C#4\\"}",
"1225/64 -> 2475/128: {\\"note\\":\\"D#4\\"}",
"1225/64 -> 2475/128: {\\"note\\":\\"F#4\\"}",
"1275/64 -> 2575/128: {\\"note\\":\\"A#3\\"}",
"1275/64 -> 2575/128: {\\"note\\":\\"C#4\\"}",
"1275/64 -> 2575/128: {\\"note\\":\\"D#4\\"}",
"1275/64 -> 2575/128: {\\"note\\":\\"F#4\\"}",
"75/4 -> 1225/64: {\\"note\\":\\"B2\\"}",
"625/32 -> 1275/64: {\\"note\\":\\"F#2\\"}",
"75/4 -> 325/16: {\\"note\\":\\"F#5\\"}",
"325/16 -> 675/32: {\\"note\\":\\"G5\\"}",
"1275/64 -> 2575/128: {\\"note\\":\\"A#3\\"}",
"1275/64 -> 2575/128: {\\"note\\":\\"C#4\\"}",
"1275/64 -> 2575/128: {\\"note\\":\\"D#4\\"}",
"1275/64 -> 2575/128: {\\"note\\":\\"F#4\\"}",
"1325/64 -> 2675/128: {\\"note\\":\\"Ab3\\"}",
"1325/64 -> 2675/128: {\\"note\\":\\"C4\\"}",
"1325/64 -> 2675/128: {\\"note\\":\\"Eb4\\"}",
"1325/64 -> 2675/128: {\\"note\\":\\"G4\\"}",
"325/16 -> 1325/64: {\\"note\\":\\"F2\\"}",
"325/16 -> 675/32: {\\"note\\":\\"G5\\"}",
"675/32 -> 175/8: {\\"note\\":\\"F5\\"}",
"175/8 -> 375/16: {\\"note\\":\\"Bb5\\"}",
"1375/64 -> 2775/128: {\\"note\\":\\"Ab3\\"}",
"1375/64 -> 2775/128: {\\"note\\":\\"C4\\"}",
"1375/64 -> 2775/128: {\\"note\\":\\"D4\\"}",
"1375/64 -> 2775/128: {\\"note\\":\\"G4\\"}",
"675/32 -> 1375/64: {\\"note\\":\\"Bb2\\"}",
"175/8 -> 1425/64: {\\"note\\":\\"Eb2\\"}",
"175/8 -> 375/16: {\\"note\\":\\"Bb5\\"}",
"1425/64 -> 2875/128: {\\"note\\":\\"G3\\"}",
"1425/64 -> 2875/128: {\\"note\\":\\"Bb3\\"}",
"1425/64 -> 2875/128: {\\"note\\":\\"D4\\"}",
"1425/64 -> 2875/128: {\\"note\\":\\"F4\\"}",
"175/8 -> 1425/64: {\\"note\\":\\"Eb2\\"}",
"725/32 -> 1475/64: {\\"note\\":\\"Bb2\\"}",
"175/8 -> 375/16: {\\"note\\":\\"Bb5\\"}",
"375/16 -> 775/32: {\\"note\\":\\"F#5\\"}",
"1475/64 -> 2975/128: {\\"note\\":\\"G3\\"}",
"1475/64 -> 2975/128: {\\"note\\":\\"Bb3\\"}",
"1475/64 -> 2975/128: {\\"note\\":\\"D4\\"}",
"1475/64 -> 2975/128: {\\"note\\":\\"F4\\"}",
"1525/64 -> 3075/128: {\\"note\\":\\"E3\\"}",
"1525/64 -> 3075/128: {\\"note\\":\\"G#3\\"}",
"1525/64 -> 3075/128: {\\"note\\":\\"B3\\"}",
"1525/64 -> 3075/128: {\\"note\\":\\"D#4\\"}",
"725/32 -> 1475/64: {\\"note\\":\\"Bb2\\"}",
"375/16 -> 1525/64: {\\"note\\":\\"C#2\\"}",
"375/16 -> 775/32: {\\"note\\":\\"F#5\\"}",
"775/32 -> 3125/128: {\\"note\\":\\"F#5\\"}",
"1525/64 -> 3075/128: {\\"note\\":\\"E3\\"}",
"1525/64 -> 3075/128: {\\"note\\":\\"G#3\\"}",
"1525/64 -> 3075/128: {\\"note\\":\\"B3\\"}",
"1525/64 -> 3075/128: {\\"note\\":\\"D#4\\"}",
"1575/64 -> 3175/128: {\\"note\\":\\"E3\\"}",
"1575/64 -> 3175/128: {\\"note\\":\\"G#3\\"}",
"1575/64 -> 3175/128: {\\"note\\":\\"A#3\\"}",
"1575/64 -> 3175/128: {\\"note\\":\\"D#4\\"}",
"775/32 -> 1575/64: {\\"note\\":\\"F#2\\"}",
]
`;
exports[`renders tunes > tune: goodTimes 1`] = `
[
"0/1 -> 1/2: {\\"note\\":\\"F3\\",\\"clip\\":1,\\"s\\":\\"piano\\",\\"release\\":0.1,\\"pan\\":0.49537037037037035}",
@@ -8335,6 +8097,36 @@ exports[`renders tunes > tune: juxUndTollerei 1`] = `
]
`;
exports[`renders tunes > tune: loungeSponge 1`] = `
[
"0/1 -> 3/8: {\\"note\\":\\"B3\\",\\"cutoff\\":1396}",
"0/1 -> 3/8: {\\"note\\":\\"D4\\",\\"cutoff\\":1396}",
"0/1 -> 3/8: {\\"note\\":\\"E4\\",\\"cutoff\\":1396}",
"0/1 -> 3/8: {\\"note\\":\\"G4\\",\\"cutoff\\":1396}",
"3/8 -> 3/4: {\\"note\\":\\"B3\\",\\"cutoff\\":1396}",
"3/8 -> 3/4: {\\"note\\":\\"D4\\",\\"cutoff\\":1396}",
"3/8 -> 3/4: {\\"note\\":\\"E4\\",\\"cutoff\\":1396}",
"3/8 -> 3/4: {\\"note\\":\\"G4\\",\\"cutoff\\":1396}",
"3/4 -> 1/1: {\\"note\\":\\"B3\\",\\"cutoff\\":1396}",
"3/4 -> 1/1: {\\"note\\":\\"D4\\",\\"cutoff\\":1396}",
"3/4 -> 1/1: {\\"note\\":\\"E4\\",\\"cutoff\\":1396}",
"3/4 -> 1/1: {\\"note\\":\\"G4\\",\\"cutoff\\":1396}",
"0/1 -> 1/4: {\\"note\\":\\"C2\\",\\"gain\\":1}",
"1/4 -> 1/2: {\\"note\\":\\"C2\\",\\"gain\\":4}",
"1/2 -> 3/4: {\\"note\\":\\"C2\\",\\"gain\\":1}",
"3/4 -> 1/1: {\\"note\\":\\"C2\\",\\"gain\\":4}",
"0/1 -> 3/16: {\\"note\\":\\"A4\\"}",
"3/4 -> 15/16: {\\"note\\":\\"A5\\"}",
"-1/4 -> -1/16: {\\"note\\":\\"E5\\"}",
"1/2 -> 11/16: {\\"note\\":\\"C5\\"}",
"0/1 -> 1/2: {\\"s\\":\\"bd\\",\\"bank\\":\\"RolandTR909\\"}",
"1/2 -> 1/1: {\\"s\\":\\"bd\\",\\"bank\\":\\"RolandTR909\\"}",
"1/4 -> 1/2: {\\"s\\":\\"hh\\",\\"bank\\":\\"RolandTR909\\"}",
"3/4 -> 1/1: {\\"s\\":\\"hh\\",\\"bank\\":\\"RolandTR909\\"}",
"1/2 -> 1/1: {\\"s\\":\\"cp\\",\\"bank\\":\\"RolandTR909\\"}",
]
`;
exports[`renders tunes > tune: meltingsubmarine 1`] = `
[
"0/1 -> 3/2: {\\"s\\":\\"bd\\",\\"speed\\":0.7519542165100574}",
@@ -9570,125 +9362,6 @@ exports[`renders tunes > tune: swimming 1`] = `
]
`;
exports[`renders tunes > tune: swimmingWithSoundfonts 1`] = `
[
"0/1 -> 3/4: {\\"n\\":\\"F4\\",\\"s\\":\\"Kalimba: Ethnic\\"}",
"0/1 -> 3/4: {\\"n\\":\\"Bb4\\",\\"s\\":\\"Kalimba: Ethnic\\"}",
"0/1 -> 3/4: {\\"n\\":\\"D5\\",\\"s\\":\\"Kalimba: Ethnic\\"}",
"3/4 -> 5/4: {\\"n\\":\\"D4\\",\\"s\\":\\"Kalimba: Ethnic\\"}",
"3/4 -> 5/4: {\\"n\\":\\"G4\\",\\"s\\":\\"Kalimba: Ethnic\\"}",
"3/4 -> 5/4: {\\"n\\":\\"Bb4\\",\\"s\\":\\"Kalimba: Ethnic\\"}",
"0/1 -> 3/4: {\\"n\\":\\"G3\\",\\"s\\":\\"Acoustic Bass: Bass\\"}",
"3/4 -> 3/2: {\\"n\\":\\"G3\\",\\"s\\":\\"Acoustic Bass: Bass\\"}",
]
`;
exports[`renders tunes > tune: tetrisMini 1`] = `
[
"0/1 -> 1/2: {\\"note\\":\\"e5\\"}",
"1/2 -> 3/4: {\\"note\\":\\"b4\\"}",
"3/4 -> 1/1: {\\"note\\":\\"c5\\"}",
"0/1 -> 1/4: {\\"note\\":\\"e2\\"}",
"1/4 -> 1/2: {\\"note\\":\\"e3\\"}",
"1/2 -> 3/4: {\\"note\\":\\"e2\\"}",
"3/4 -> 1/1: {\\"note\\":\\"e3\\"}",
"1/1 -> 3/2: {\\"note\\":\\"d5\\"}",
"3/2 -> 7/4: {\\"note\\":\\"c5\\"}",
"7/4 -> 2/1: {\\"note\\":\\"b4\\"}",
"1/1 -> 5/4: {\\"note\\":\\"e2\\"}",
"5/4 -> 3/2: {\\"note\\":\\"e3\\"}",
"3/2 -> 7/4: {\\"note\\":\\"e2\\"}",
"7/4 -> 2/1: {\\"note\\":\\"e3\\"}",
"2/1 -> 5/2: {\\"note\\":\\"a4\\"}",
"5/2 -> 11/4: {\\"note\\":\\"a4\\"}",
"11/4 -> 3/1: {\\"note\\":\\"c5\\"}",
"2/1 -> 9/4: {\\"note\\":\\"a2\\"}",
"9/4 -> 5/2: {\\"note\\":\\"a3\\"}",
"5/2 -> 11/4: {\\"note\\":\\"a2\\"}",
"11/4 -> 3/1: {\\"note\\":\\"a3\\"}",
"3/1 -> 7/2: {\\"note\\":\\"e5\\"}",
"7/2 -> 15/4: {\\"note\\":\\"d5\\"}",
"15/4 -> 4/1: {\\"note\\":\\"c5\\"}",
"3/1 -> 13/4: {\\"note\\":\\"a2\\"}",
"13/4 -> 7/2: {\\"note\\":\\"a3\\"}",
"7/2 -> 15/4: {\\"note\\":\\"a2\\"}",
"15/4 -> 4/1: {\\"note\\":\\"a3\\"}",
"4/1 -> 9/2: {\\"note\\":\\"b4\\"}",
"19/4 -> 5/1: {\\"note\\":\\"c5\\"}",
"4/1 -> 17/4: {\\"note\\":\\"g#2\\"}",
"17/4 -> 9/2: {\\"note\\":\\"g#3\\"}",
"9/2 -> 19/4: {\\"note\\":\\"g#2\\"}",
"19/4 -> 5/1: {\\"note\\":\\"g#3\\"}",
"5/1 -> 11/2: {\\"note\\":\\"d5\\"}",
"11/2 -> 6/1: {\\"note\\":\\"e5\\"}",
"5/1 -> 21/4: {\\"note\\":\\"e2\\"}",
"21/4 -> 11/2: {\\"note\\":\\"e3\\"}",
"11/2 -> 23/4: {\\"note\\":\\"e2\\"}",
"23/4 -> 6/1: {\\"note\\":\\"e3\\"}",
"6/1 -> 13/2: {\\"note\\":\\"c5\\"}",
"13/2 -> 7/1: {\\"note\\":\\"a4\\"}",
"6/1 -> 25/4: {\\"note\\":\\"a2\\"}",
"25/4 -> 13/2: {\\"note\\":\\"a3\\"}",
"13/2 -> 27/4: {\\"note\\":\\"a2\\"}",
"27/4 -> 7/1: {\\"note\\":\\"a3\\"}",
"7/1 -> 15/2: {\\"note\\":\\"a4\\"}",
"7/1 -> 29/4: {\\"note\\":\\"a2\\"}",
"29/4 -> 15/2: {\\"note\\":\\"a3\\"}",
"15/2 -> 31/4: {\\"note\\":\\"b1\\"}",
"31/4 -> 8/1: {\\"note\\":\\"c2\\"}",
"33/4 -> 17/2: {\\"note\\":\\"d5\\"}",
"35/4 -> 9/1: {\\"note\\":\\"f5\\"}",
"8/1 -> 33/4: {\\"note\\":\\"d2\\"}",
"33/4 -> 17/2: {\\"note\\":\\"d3\\"}",
"17/2 -> 35/4: {\\"note\\":\\"d2\\"}",
"35/4 -> 9/1: {\\"note\\":\\"d3\\"}",
"9/1 -> 19/2: {\\"note\\":\\"a5\\"}",
"19/2 -> 39/4: {\\"note\\":\\"g5\\"}",
"39/4 -> 10/1: {\\"note\\":\\"f5\\"}",
"9/1 -> 37/4: {\\"note\\":\\"d2\\"}",
"37/4 -> 19/2: {\\"note\\":\\"d3\\"}",
"19/2 -> 39/4: {\\"note\\":\\"d2\\"}",
"39/4 -> 10/1: {\\"note\\":\\"d3\\"}",
"10/1 -> 21/2: {\\"note\\":\\"e5\\"}",
"43/4 -> 11/1: {\\"note\\":\\"c5\\"}",
"10/1 -> 41/4: {\\"note\\":\\"c2\\"}",
"41/4 -> 21/2: {\\"note\\":\\"c3\\"}",
"21/2 -> 43/4: {\\"note\\":\\"c2\\"}",
"43/4 -> 11/1: {\\"note\\":\\"c3\\"}",
"11/1 -> 23/2: {\\"note\\":\\"e5\\"}",
"23/2 -> 47/4: {\\"note\\":\\"d5\\"}",
"47/4 -> 12/1: {\\"note\\":\\"c5\\"}",
"11/1 -> 45/4: {\\"note\\":\\"c2\\"}",
"45/4 -> 23/2: {\\"note\\":\\"c3\\"}",
"23/2 -> 47/4: {\\"note\\":\\"c2\\"}",
"47/4 -> 12/1: {\\"note\\":\\"c3\\"}",
"12/1 -> 25/2: {\\"note\\":\\"b4\\"}",
"25/2 -> 51/4: {\\"note\\":\\"b4\\"}",
"51/4 -> 13/1: {\\"note\\":\\"c5\\"}",
"12/1 -> 49/4: {\\"note\\":\\"b1\\"}",
"49/4 -> 25/2: {\\"note\\":\\"b2\\"}",
"25/2 -> 51/4: {\\"note\\":\\"b1\\"}",
"51/4 -> 13/1: {\\"note\\":\\"b2\\"}",
"13/1 -> 27/2: {\\"note\\":\\"d5\\"}",
"27/2 -> 14/1: {\\"note\\":\\"e5\\"}",
"13/1 -> 53/4: {\\"note\\":\\"e2\\"}",
"53/4 -> 27/2: {\\"note\\":\\"e3\\"}",
"27/2 -> 55/4: {\\"note\\":\\"e2\\"}",
"55/4 -> 14/1: {\\"note\\":\\"e3\\"}",
"14/1 -> 29/2: {\\"note\\":\\"c5\\"}",
"29/2 -> 15/1: {\\"note\\":\\"a4\\"}",
"14/1 -> 57/4: {\\"note\\":\\"a1\\"}",
"57/4 -> 29/2: {\\"note\\":\\"a2\\"}",
"29/2 -> 59/4: {\\"note\\":\\"a1\\"}",
"59/4 -> 15/1: {\\"note\\":\\"a2\\"}",
"15/1 -> 31/2: {\\"note\\":\\"a4\\"}",
"15/1 -> 61/4: {\\"note\\":\\"a1\\"}",
"61/4 -> 31/2: {\\"note\\":\\"a2\\"}",
"31/2 -> 63/4: {\\"note\\":\\"a1\\"}",
"63/4 -> 16/1: {\\"note\\":\\"a2\\"}",
]
`;
exports[`renders tunes > tune: undergroundPlumber 1`] = `
[
"0/1 -> 3/16: {\\"s\\":\\"bd\\",\\"gain\\":0.7}",
+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>`;
})}
@@ -232,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}",
@@ -782,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\\"}",
@@ -1547,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\\"}",
-24
View File
@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
-33
View File
@@ -1,33 +0,0 @@
# Strudel Workshop
This may become a resource to teach strudel in workshop format.
## Install
From the root of the strudel repo:
```sh
npm i # install strudel project dependencies
cd workshop && npm i # install workshop dependencies
npm run dev # run dev server
```
This assumes you have node 16+ installed.
## How to write
While running the dev server, go to `workshop.mdx` to write!
When the file is saved, the browser will hot reload the file and reveal the changes instantly.
You can use normal markdown + JSX, which enables using the MiniRepl component.
You could also add any react component and import it to the mdx file.
## Build
Running
```sh
npm run build
```
..will output a single `index.html` file to `dist`. This file can either be opened directly in the browser (locally) or uploaded to some server.
-12
View File
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Strudel Workshop</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
-5982
View File
File diff suppressed because it is too large Load Diff
-29
View File
@@ -1,29 +0,0 @@
{
"name": "workshop",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --open",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@mdx-js/mdx": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@tailwindcss/typography": "^0.5.8",
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
"@vitejs/plugin-react": "^2.2.0",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",
"tailwindcss": "^3.2.4",
"vite": "^3.2.3",
"vite-plugin-mdx": "^3.5.11",
"vite-plugin-singlefile": "^0.13.1"
}
}
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
-16
View File
@@ -1,16 +0,0 @@
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
import Workshop from './workshop.mdx';
initAudioOnFirstClick();
function App() {
return (
<div className="bg-slate-900 w-screen h-screen overflow-auto">
<main className="p-4 pl-6 max-w-3xl prose prose-invert">
<Workshop />
</main>
</div>
);
}
export default App;
-22
View File
@@ -1,22 +0,0 @@
import { evalScope, controls } from '@strudel.cycles/core';
import { MiniRepl as _MiniRepl } from '@strudel.cycles/react';
import { samples } from '@strudel.cycles/webaudio';
import '@strudel.cycles/react/dist/style.css';
samples('github:tidalcycles/Dirt-Samples/master')
evalScope(
controls,
import('@strudel.cycles/core'),
// import('@strudel.cycles/tone'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/midi'),
// import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/osc'),
);
export function MiniRepl({ tune }) {
return <_MiniRepl tune={tune} hideOutsideView={true} />;
}
-3
View File
@@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
-11
View File
@@ -1,11 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root'),
);
-49
View File
@@ -1,49 +0,0 @@
import { MiniRepl } from './MiniRepl';
# Workshop
> This is a starter project for any type of teaching material using strudel!
## What is Strudel?
1. A pastry made with fruit or cheese rolled up in layers of thin sheets of dough and then baked.
2. a pastry made from multiple, thin layers of dough rolled up and filled with fruit, etc.
3. thin sheet of filled dough rolled and baked
## Some REPLs:
Small REPL:
<MiniRepl tune={`s("bd sd,hh*8")`} />
Big REPL:
<MiniRepl
tune={`await 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'],
}, 'github:tidalcycles/Dirt-Samples/master/');
stack(
s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
.speed(perlin.range(.7,.9)) // random sample speed variation
,"<a1 b1\*2 a1(3,8) e2>" // bassline
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
.add(perlin.range(0,.5)) // random pitch variation
.superimpose(add(.05)) // add second, slightly detuned voice
.n() // wrap in "n"
.decay(.15).sustain(0) // make each note of equal length
.s('sawtooth') // waveform
.gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings() // chords
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
.add(perlin.range(0,.5)) // random pitch variation
.n() // wrap in "n"
.s('sawtooth') // waveform
.gain(.16) // turn down
.cutoff(500) // fixed cutoff
.attack(1) // slowly fade in
)
.slow(3/2)`}
/>
-8
View File
@@ -1,8 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,mdx}'],
theme: {
extend: {},
},
plugins: [require('@tailwindcss/typography')],
};
-25
View File
@@ -1,25 +0,0 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import _mdx from 'vite-plugin-mdx';
import { viteSingleFile } from 'vite-plugin-singlefile';
// import remarkToc from 'remark-toc';
// import rehypeSlug from 'rehype-slug';
// import rehypeAutolinkHeadings from 'rehype-autolink-headings';
const mdx = _mdx.default || _mdx;
// `options` are passed to `@mdx-js/mdx`
const options = {
// See https://mdxjs.com/advanced/plugins
remarkPlugins: [
// remarkToc,
// E.g. `remark-frontmatter`
],
rehypePlugins: [
// rehypeSlug, rehypeAutolinkHeadings
],
};
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), mdx(options), viteSingleFile()],
});