Merge branch 'main' into stateful-events

This commit is contained in:
Alex McLean
2022-02-23 20:20:35 +00:00
committed by GitHub
36 changed files with 2275 additions and 916 deletions
+1
View File
@@ -1936,6 +1936,7 @@ function peg$parse(input, options) {
this.type_ = "element";
this.source_ = source;
this.options_ = options;
this.location_ = location();
}
var CommandStub = function(name, options)
+54 -2
View File
@@ -107,7 +107,7 @@ class TimeSpan {
}
return result;
}
get midpoint() {
midpoint() {
return this.begin.add(this.end.sub(this.begin).div(Fraction(2)));
}
equals(other) {
@@ -175,6 +175,13 @@ class Pattern {
_withEvents(func) {
return new Pattern((span) => func(this.query(span)));
}
withLocation(location) {
return this.fmap((value) => {
value = typeof value === "object" && !Array.isArray(value) ? value : {value};
const locations = (value.locations || []).concat([location]);
return {...value, locations};
});
}
withValue(func) {
return new Pattern((span) => this.query(span).map((hap) => hap.withValue(func)));
}
@@ -312,6 +319,7 @@ class Pattern {
_patternify(func) {
const pat = this;
const patterned = function(...args) {
args = args.map((arg) => arg.constructor?.name === "Pattern" ? arg.fmap((value) => value.value || value) : arg);
const pat_arg = sequence(...args);
return pat_arg.fmap((arg) => func.call(pat, arg)).outerJoin();
};
@@ -428,6 +436,9 @@ class Pattern {
edit(...funcs) {
return stack(...funcs.map((func) => func(this)));
}
pipe(func) {
return func(this);
}
_bypass(on2) {
on2 = Boolean(parseInt(on2));
return on2 ? silence : this;
@@ -448,6 +459,24 @@ function pure(value) {
function steady(value) {
return new Pattern((span) => Hap(void 0, span, value));
}
export const signal = (func) => {
const query = (span) => [new Hap(void 0, span, func(span.midpoint()))];
return new Pattern(query);
};
const _toBipolar = (pat) => pat.fmap((x) => x * 2 - 1);
const _fromBipolar = (pat) => pat.fmap((x) => (x + 1) / 2);
export const sine2 = signal((t) => Math.sin(Math.PI * 2 * t));
export const sine = _fromBipolar(sine2);
export const cosine2 = sine2._early(0.25);
export const cosine = sine._early(0.25);
export const saw = signal((t) => t % 1);
export const saw2 = _toBipolar(saw);
export const isaw = signal((t) => 1 - t % 1);
export const isaw2 = _toBipolar(isaw);
export const tri2 = fastcat(isaw2, saw2);
export const tri = fastcat(isaw, saw);
export const square = signal((t) => Math.floor(t * 2 % 2));
export const square2 = _toBipolar(square);
function reify(thing) {
if (thing?.constructor?.name == "Pattern") {
return thing;
@@ -596,6 +625,28 @@ Pattern.prototype.bootstrap = () => {
}));
return bootstrapped;
};
function withLocationOffset(pat, offset) {
return pat.fmap((value) => {
value = typeof value === "object" && !Array.isArray(value) ? value : {value};
let locations = value.locations || [];
locations = locations.map(({start, end}) => {
const colOffset = start.line === 1 ? offset.start.column : 0;
return {
start: {
...start,
line: start.line - 1 + (offset.start.line - 1) + 1,
column: start.column - 1 + colOffset
},
end: {
...end,
line: end.line - 1 + (offset.start.line - 1) + 1,
column: end.column - 1 + colOffset
}
};
});
return {...value, locations};
});
}
export {
Fraction,
TimeSpan,
@@ -633,5 +684,6 @@ export {
struct,
mask,
invert,
inv
inv,
withLocationOffset
};
+9 -4
View File
@@ -1,6 +1,6 @@
import React, {useCallback, useLayoutEffect, useRef} from "../_snowpack/pkg/react.js";
import React, {useCallback, useLayoutEffect, useRef, useState} from "../_snowpack/pkg/react.js";
import * as Tone from "../_snowpack/pkg/tone.js";
import CodeMirror from "./CodeMirror.js";
import CodeMirror, {markEvent} from "./CodeMirror.js";
import cx from "./cx.js";
import {evaluate} from "./evaluate.js";
import logo from "./logo.svg.proxy.js";
@@ -28,9 +28,11 @@ function getRandomTune() {
}
const randomTune = getRandomTune();
function App() {
const [editor, setEditor] = useState();
const {setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog} = useRepl({
tune: decoded || randomTune,
defaultSynth
defaultSynth,
onEvent: useCallback(markEvent(editor), [editor])
});
const logBox = useRef();
useLayoutEffect(() => {
@@ -95,10 +97,13 @@ function App() {
className: cx("h-full bg-[#2A3236]", error ? "focus:ring-red-500" : "focus:ring-slate-800")
}, /* @__PURE__ */ React.createElement(CodeMirror, {
value: code,
editorDidMount: setEditor,
options: {
mode: "javascript",
theme: "material",
lineNumbers: true
lineNumbers: true,
styleSelectedText: true,
cursorBlinkRate: 0
},
onChange: (_2, __, value) => setCode(value)
}), /* @__PURE__ */ React.createElement("span", {
+17 -4
View File
@@ -2,17 +2,30 @@ import React from "../_snowpack/pkg/react.js";
import {Controlled as CodeMirror2} from "../_snowpack/pkg/react-codemirror2.js";
import "../_snowpack/pkg/codemirror/mode/javascript/javascript.js";
import "../_snowpack/pkg/codemirror/mode/pegjs/pegjs.js";
import "../_snowpack/pkg/codemirror/theme/material.css.proxy.js";
import "../_snowpack/pkg/codemirror/lib/codemirror.css.proxy.js";
export default function CodeMirror({value, onChange, options}) {
import "../_snowpack/pkg/codemirror/theme/material.css.proxy.js";
export default function CodeMirror({value, onChange, options, editorDidMount}) {
options = options || {
mode: "javascript",
theme: "material",
lineNumbers: true
lineNumbers: true,
styleSelectedText: true,
cursorBlinkRate: 500
};
return /* @__PURE__ */ React.createElement(CodeMirror2, {
value,
options,
onBeforeChange: onChange
onBeforeChange: onChange,
editorDidMount
});
}
export const markEvent = (editor) => (event) => {
const locs = event.value.locations;
if (!locs || !editor) {
return;
}
const marks = locs.map(({start, end}) => editor.getDoc().markText({line: start.line - 1, ch: start.column}, {line: end.line - 1, ch: end.column}, {css: "background-color: #FFCA28; color: black"}));
setTimeout(() => {
marks.forEach((mark) => mark.clear());
}, event.duration * 0.9 * 1e3);
};
+7 -6
View File
@@ -3,7 +3,6 @@ import "./tone.js";
import "./midi.js";
import "./voicings.js";
import "./tonal.js";
import "./groove.js";
import shapeshifter from "./shapeshifter.js";
import {minify} from "./parse.js";
import * as Tone from "../_snowpack/pkg/tone.js";
@@ -27,10 +26,12 @@ export const evaluate = (code) => {
if (typeof evaluated === "function") {
evaluated = evaluated();
}
const pattern = minify(evaluated);
if (pattern?.constructor?.name !== "Pattern") {
const message = `got "${typeof pattern}" instead of pattern`;
throw new Error(message + (typeof pattern === "function" ? ", did you forget to call a function?" : "."));
if (typeof evaluated === "string") {
evaluated = strudel.withLocationOffset(minify(evaluated), {start: {line: 1, column: -1}});
}
return {mode: "javascript", pattern};
if (evaluated?.constructor?.name !== "Pattern") {
const message = `got "${typeof evaluated}" instead of pattern`;
throw new Error(message + (typeof evaluated === "function" ? ", did you forget to call a function?" : "."));
}
return {mode: "javascript", pattern: evaluated};
};
-6
View File
@@ -1,6 +0,0 @@
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
const Pattern = _Pattern;
Pattern.prototype.groove = function(groove) {
return groove.fmap(() => (v) => v).appLeft(this);
};
Pattern.prototype.define("groove", (groove, pat) => pat.groove(groove), {composable: true});
+11 -1
View File
@@ -1,6 +1,7 @@
import * as krill from "../_snowpack/link/repl/krill-parser.js";
import * as strudel from "../_snowpack/link/strudel.js";
import {Scale, Note, Interval} from "../_snowpack/pkg/@tonaljs/tonal.js";
import {addMiniLocations} from "./shapeshifter.js";
const {pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence} = strudel;
const applyOptions = (parent) => (pat, i) => {
const ast = parent.source_[i];
@@ -39,6 +40,7 @@ function resolveReplications(ast) {
{
type_: "element",
source_: child.source_,
location_: child.location_,
options_: {
operator: {
type_: "stretch",
@@ -80,7 +82,15 @@ export function patternifyAST(ast) {
return silence;
}
if (typeof ast.source_ !== "object") {
return ast.source_;
if (!addMiniLocations) {
return ast.source_;
}
if (!ast.location_) {
console.warn("no location for", ast);
return ast.source_;
}
const {start, end} = ast.location_;
return pure(ast.source_).withLocation({start, end});
}
return patternifyAST(ast.source_);
case "stretch":
+320 -8
View File
@@ -1,30 +1,342 @@
import { parseScript } from './shift-parser/index.js'; // npm module does not work in the browser
import { parseScriptWithLocation } from './shift-parser/index.js'; // npm module does not work in the browser
import traverser from './shift-traverser/index.js'; // npm module does not work in the browser
const { replace } = traverser;
import { LiteralStringExpression, IdentifierExpression } from '../_snowpack/pkg/shift-ast.js';
import {
LiteralStringExpression,
IdentifierExpression,
CallExpression,
StaticMemberExpression,
Script,
} from '../_snowpack/pkg/shift-ast.js';
import codegen from '../_snowpack/pkg/shift-codegen.js';
import * as strudel from '../_snowpack/link/strudel.js';
const { Pattern } = strudel;
const isNote = (name) => /^[a-gC-G][bs]?[0-9]$/.test(name);
const addLocations = true;
export const addMiniLocations = true;
/*
not supported for highlighting:
- 'b3'.p
- mini('b3') / m('b3')
- 'b3'.m / 'b3'.mini
*/
export default (code) => {
const ast = parseScript(code);
const shifted = replace(ast, {
const ast = parseScriptWithLocation(code);
const artificialNodes = [];
const parents = [];
const shifted = replace(ast.tree, {
enter(node, parent) {
// replace identifiers that are a note with a note string
parents.push(parent);
const isSynthetic = parents.some((p) => artificialNodes.includes(p));
if (isSynthetic) {
return node;
}
// replace template string `xxx` with 'xxx'.m
if (isBackTickString(node)) {
const minified = getMinified(node.elements[0].rawValue);
return wrapLocationOffset(minified, node, ast.locations, artificialNodes);
}
// allows to use top level strings, which are normally directives... but we don't need directives
if (node.type === 'Script' && node.directives.length === 1 && !node.statements.length) {
const minified = getMinified(node.directives[0].rawValue);
const wrapped = wrapLocationOffset(minified, node.directives[0], ast.locations, artificialNodes);
return new Script({ directives: [], statements: [wrapped] });
}
// replace double quote string "xxx" with 'xxx'.m
if (isStringWithDoubleQuotes(node, ast.locations, code)) {
const minified = getMinified(node.value);
return wrapLocationOffset(minified, node, ast.locations, artificialNodes);
}
// replace double quote string "xxx" with 'xxx'.m
if (isStringWithDoubleQuotes(node, ast.locations, code)) {
const minified = getMinified(node.value);
return wrapLocationOffset(minified, node, ast.locations, artificialNodes);
}
// operator overloading => still not done
const operators = {
'*': 'fast',
'/': 'slow',
'&': 'stack',
'&&': 'append',
};
if (
node.type === 'BinaryExpression' &&
operators[node.operator] &&
['LiteralNumericExpression', 'LiteralStringExpression', 'IdentifierExpression'].includes(node.right?.type) &&
canBeOverloaded(node.left)
) {
let arg = node.left;
if (node.left.type === 'IdentifierExpression') {
arg = wrapFunction('reify', node.left);
}
return new CallExpression({
callee: new StaticMemberExpression({
property: operators[node.operator],
object: wrapFunction('reify', arg),
}),
arguments: [node.right],
});
}
const isMarkable = isPatternArg(parents) || hasModifierCall(parent);
// add to location to pure(x) calls
if (node.type === 'CallExpression' && node.callee.name === 'pure') {
return reifyWithLocation(node.arguments[0].name, node.arguments[0], ast.locations, artificialNodes);
}
// replace pseudo note variables
if (node.type === 'IdentifierExpression') {
if (isNote(node.name)) {
const value = node.name[1] === 's' ? node.name.replace('s', '#') : node.name;
if (addLocations && isMarkable) {
return reifyWithLocation(value, node, ast.locations, artificialNodes);
}
return new LiteralStringExpression({ value });
}
if (node.name === 'r') {
return new IdentifierExpression({ name: 'silence' });
}
}
if (addLocations && node.type === 'LiteralStringExpression' && isMarkable) {
// console.log('add', node);
return reifyWithLocation(node.value, node, ast.locations, artificialNodes);
}
if (!addMiniLocations) {
return wrapFunction('reify', node);
}
// mini notation location handling
const miniFunctions = ['mini', 'm'];
const isAlreadyWrapped = parent?.type === 'CallExpression' && parent.callee.name === 'withLocationOffset';
if (node.type === 'CallExpression' && miniFunctions.includes(node.callee.name) && !isAlreadyWrapped) {
// mini('c3')
if (node.arguments.length > 1) {
// TODO: transform mini(...args) to cat(...args.map(mini)) ?
console.warn('multi arg mini locations not supported yet...');
return node;
}
return wrapLocationOffset(node, node.arguments, ast.locations, artificialNodes);
}
if (node.type === 'StaticMemberExpression' && miniFunctions.includes(node.property) && !isAlreadyWrapped) {
// 'c3'.mini or 'c3'.m
return wrapLocationOffset(node, node.object, ast.locations, artificialNodes);
}
return node;
},
leave() {
parents.pop();
},
});
return codegen(shifted);
};
// TODO: turn x.groove['[~ x]*2'] into x.groove('[~ x]*2'.m)
// and ['c1*2'].xx into 'c1*2'.m.xx ??
// or just all templated strings?? x.groove(`[~ x]*2`)
function wrapFunction(name, ...args) {
return new CallExpression({
callee: new IdentifierExpression({ name }),
arguments: args,
});
}
function getMinified(value) {
return new StaticMemberExpression({
object: new LiteralStringExpression({ value }),
property: 'm',
});
}
function isBackTickString(node) {
return node.type === 'TemplateExpression' && node.elements.length === 1;
}
function isStringWithDoubleQuotes(node, locations, code) {
if (node.type !== 'LiteralStringExpression') {
return false;
}
const loc = locations.get(node);
const snippet = code.slice(loc.start.offset, loc.end.offset);
return snippet[0] === '"'; // we can trust the end is also ", as the parsing did not fail
}
// returns true if the given parents belong to a pattern argument node
// this is used to check if a node should receive a location for highlighting
function isPatternArg(parents) {
if (!parents.length) {
return false;
}
const ancestors = parents.slice(0, -1);
const parent = parents[parents.length - 1];
if (isPatternFactory(parent)) {
return true;
}
if (parent?.type === 'ArrayExpression') {
return isPatternArg(ancestors);
}
return false;
}
function hasModifierCall(parent) {
// TODO: modifiers are more than composables, for example every is not composable but should be seen as modifier..
// need all prototypes of Pattern
return (
parent?.type === 'StaticMemberExpression' && Object.keys(Pattern.prototype.composable).includes(parent.property)
);
}
function isPatternFactory(node) {
return node?.type === 'CallExpression' && Object.keys(Pattern.prototype.factories).includes(node.callee.name);
}
function canBeOverloaded(node) {
return (node.type === 'IdentifierExpression' && isNote(node.name)) || isPatternFactory(node);
// TODO: support sequence(c3).transpose(3).x.y.z
}
// turn node into withLocationOffset(node, location)
function wrapLocationOffset(node, stringNode, locations, artificialNodes) {
// console.log('wrapppp', stringNode);
const expression = {
type: 'CallExpression',
callee: {
type: 'IdentifierExpression',
name: 'withLocationOffset',
},
arguments: [node, getLocationObject(stringNode, locations)],
};
artificialNodes.push(expression);
// console.log('wrapped', codegen(expression));
return expression;
}
// turns node in reify(value).withLocation(location), where location is the node's location in the source code
// with this, the reified pattern can pass its location to the event, to know where to highlight when it's active
function reifyWithLocation(value, node, locations, artificialNodes) {
const withLocation = new CallExpression({
callee: new StaticMemberExpression({
object: wrapFunction('reify', new LiteralStringExpression({ value })),
property: 'withLocation',
}),
arguments: [getLocationObject(node, locations)],
});
artificialNodes.push(withLocation);
return withLocation;
}
// returns ast for source location object
function getLocationObject(node, locations) {
/*const locationAST = parseScript(
"x=" + JSON.stringify(ast.locations.get(node))
).statements[0].expression.expression;
console.log("locationAST", locationAST);*/
/*const callAST = parseScript(
`reify(${node.name}).withLocation(${JSON.stringify(
ast.locations.get(node)
)})`
).statements[0].expression;*/
const loc = locations.get(node);
return {
type: 'ObjectExpression',
properties: [
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'start',
},
expression: {
type: 'ObjectExpression',
properties: [
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'line',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.start.line,
},
},
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'column',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.start.column,
},
},
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'offset',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.start.offset,
},
},
],
},
},
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'end',
},
expression: {
type: 'ObjectExpression',
properties: [
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'line',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.end.line,
},
},
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'column',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.end.column,
},
},
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'offset',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.end.offset,
},
},
],
},
},
],
};
}
+9 -2
View File
@@ -2,7 +2,7 @@ import {Note, Interval, Scale} from "../_snowpack/pkg/@tonaljs/tonal.js";
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
const Pattern = _Pattern;
function toNoteEvent(event) {
if (typeof event === "string") {
if (typeof event === "string" || typeof event === "number") {
return {value: event};
}
if (event.value) {
@@ -53,13 +53,20 @@ Pattern.prototype._mapNotes = function(func) {
Pattern.prototype._transpose = function(intervalOrSemitones) {
return this._mapNotes(({value, scale}) => {
const interval = !isNaN(Number(intervalOrSemitones)) ? Interval.fromSemitones(intervalOrSemitones) : String(intervalOrSemitones);
if (typeof value === "number") {
const semitones = typeof interval === "string" ? Interval.semitones(interval) || 0 : interval;
return {value: value + semitones};
}
return {value: Note.transpose(value, interval), scale};
});
};
Pattern.prototype._scaleTranspose = function(offset) {
return this._mapNotes(({value, scale}) => {
if (!scale) {
throw new Error("can only use scaleOffset after .scale");
throw new Error("can only use scaleTranspose after .scale");
}
if (typeof value !== "string") {
throw new Error("can only use scaleTranspose with notes");
}
return {value: scaleTranspose(scale, Number(offset), value), scale};
});
+176 -221
View File
@@ -1,14 +1,14 @@
export const timeCatMini = `stack(
'c3@3 [eb3, g3, [c4 d4]/2]'.mini,
'c2 g2'.mini,
'[eb4@5 [f4 eb4 d4]@3] [eb4 c4]/2'.mini.slow(8)
"c3@3 [eb3, g3, [c4 d4]/2]",
"c2 g2",
"[eb4@5 [f4 eb4 d4]@3] [eb4 c4]/2".slow(8)
)`;
export const timeCat = `stack(
timeCat([3, c3], [1, stack(eb3, g3, m(c4, d4).slow(2))]),
m(c2, g2),
timeCat([3, c3], [1, stack(eb3, g3, cat(c4, d4).slow(2))]),
cat(c2, g2),
sequence(
timeCat([5, eb4], [3, m(f4, eb4, d4)]),
m(eb4, c4).slow(2)
timeCat([5, eb4], [3, cat(f4, eb4, d4)]),
cat(eb4, c4).slow(2)
).slow(4)
)`;
export const shapeShifted = `stack(
@@ -54,47 +54,47 @@ export const tetrisWithFunctions = `stack(sequence(
)
).slow(16)`;
export const tetris = `stack(
mini(
'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 ~'
cat(
"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 ~"
),
mini(
'e2 e3 e2 e3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 a2 a3',
'g#2 g#3 g#2 g#3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 b1 c2',
'd2 d3 d2 d3 d2 d3 d2 d3',
'c2 c3 c2 c3 c2 c3 c2 c3',
'b1 b2 b1 b2 e2 e3 e2 e3',
'a1 a2 a1 a2 a1 a2 a1 a2'
cat(
"e2 e3 e2 e3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 a2 a3",
"g#2 g#3 g#2 g#3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 b1 c2",
"d2 d3 d2 d3 d2 d3 d2 d3",
"c2 c3 c2 c3 c2 c3 c2 c3",
"b1 b2 b1 b2 e2 e3 e2 e3",
"a1 a2 a1 a2 a1 a2 a1 a2",
)
).slow(16)`;
export const tetrisRev = `stack(
mini(
'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 ~'
cat(
"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 ~",
).rev(),
mini(
'e2 e3 e2 e3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 a2 a3',
'g#2 g#3 g#2 g#3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 b1 c2',
'd2 d3 d2 d3 d2 d3 d2 d3',
'c2 c3 c2 c3 c2 c3 c2 c3',
'b1 b2 b1 b2 e2 e3 e2 e3',
'a1 a2 a1 a2 a1 a2 a1 a2'
cat(
"e2 e3 e2 e3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 a2 a3",
"g#2 g#3 g#2 g#3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 b1 c2",
"d2 d3 d2 d3 d2 d3 d2 d3",
"c2 c3 c2 c3 c2 c3 c2 c3",
"b1 b2 b1 b2 e2 e3 e2 e3",
"a1 a2 a1 a2 a1 a2 a1 a2",
).rev()
).slow(16)`;
export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
@@ -112,7 +112,7 @@ export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[[d2 d3]*4]
[[c2 c3]*4]
[[b1 b2]*2 [e2 e3]*2]
[[a1 a2]*4]\`.mini.slow(16)
[[a1 a2]*4]\`.slow(16)
`;
export const spanish = `slowcat(
stack(c4,eb4,g4),
@@ -120,123 +120,123 @@ export const spanish = `slowcat(
stack(ab3,c4,eb4),
stack(g3,b3,d4)
)`;
export const whirlyStrudel = `mini("[e4 [b2 b3] c4]")
export const whirlyStrudel = `sequence(e4, [b2, b3], c4)
.every(4, fast(2))
.every(3, slow(1.5))
.fast(slowcat(1.25, 1, 1.5))
.every(2, _ => mini("e4 ~ e3 d4 ~"))`;
.every(2, _ => sequence(e4, r, e3, d4, r))`;
export const swimming = `stack(
mini(
'~',
'~',
'~',
'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'
cat(
"~",
"~",
"~",
"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"
),
mini(
'[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]]'
cat(
"[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]]"
),
mini(
'[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]'
cat(
"[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]"
)
).slow(51);
`;
export const giantSteps = `stack(
// melody
mini(
'[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]',
cat(
"[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]",
),
// chords
mini(
'[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]'
cat(
"[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]"
).voicings(['E3', 'G4']),
// bass
mini(
'[B2 D2] [G2 Bb2] [Eb2 Bb3] [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 Bb3] [C#2 F#2]'
cat(
"[B2 D2] [G2 Bb2] [Eb2 Bb3] [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 Bb3] [C#2 F#2]"
)
).slow(20);`;
export const giantStepsReggae = `stack(
// melody
mini(
'[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]]',
cat(
"[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
mini(
'[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]'
cat(
"[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]"
)
.groove('~ [x ~]'.m.fast(4*8))
.struct("~ [x ~]".fast(4*8))
.voicings(['E3', 'G4']),
// bass
mini(
'[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]'
cat(
"[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]"
)
.groove('x ~'.m.fast(4*8))
.struct("x ~".fast(4*8))
).slow(25)`;
export const transposedChordsHacked = `stack(
'c2 eb2 g2'.mini,
'Cm7'.pure.voicings(['g2','c4']).slow(2)
"c2 eb2 g2",
"Cm7".voicings(['g2','c4']).slow(2)
).transpose(
slowcat(1, 2, 3, 2).slow(2)
).transpose(5)`;
@@ -244,25 +244,25 @@ export const scaleTranspose = `stack(f2, f3, c4, ab4)
.scale(sequence('F minor', 'F harmonic minor').slow(4))
.scaleTranspose(sequence(0, -1, -2, -3).slow(4))
.transpose(sequence(0, 1).slow(16))`;
export const groove = `stack(
'c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]'.mini,
'[C^7 A7] [Dm7 G7]'.mini.groove('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'.mini)
export const struct = `stack(
"c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]",
"[C^7 A7] [Dm7 G7]".struct("[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2")
.voicings(['G3','A4'])
).slow(4)`;
export const magicSofa = `stack(
'<C^7 F^7 ~> <Dm7 G7 A7 ~>'.m
"<C^7 F^7 ~> <Dm7 G7 A7 ~>"
.every(2, fast(2))
.voicings(),
'<c2 f2 g2> <d2 g2 a2 e2>'.m
"<c2 f2 g2> <d2 g2 a2 e2>"
).slow(1).transpose.slowcat(0, 2, 3, 4)`;
export const confusedPhoneDynamic = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
export const confusedPhoneDynamic = `stack("[g2 ~@1.3] [c3 ~@1.3]".slow(2))
.superimpose(
...[-12,7,10,12,24].slice(0,5).map((t,i,{length}) => x => transpose(t,x).late(i/length))
)
.scale(sequence('C dorian', 'C mixolydian').slow(4))
.scaleTranspose(slowcat(0,1,2,1).slow(2))
.synth('triangle').gain(0.5).filter(1500)`;
export const confusedPhone = `'[g2 ~@1.3] [c3 ~@1.3]'.mini
export const confusedPhone = `"[g2 ~@1.3] [c3 ~@1.3]"
.superimpose(
transpose(-12).late(0),
transpose(7).late(0.1),
@@ -280,14 +280,14 @@ export const zeldasRescue = `stack(
[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
[B3@2 D4] [A4@2 G4] D5@2
[D5@2 [C5 B4]] [[C5 B4] G4@2] [C5@2 [B4 A4]] [[B4 A4] E4@2]
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`.mini,
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`,
// bass
\`[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[D2 A2] C3@2] [[C2 G2] B2@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[Eb2 Bb2] Db3@2] [[D2 A2] C3 [F3,G2]]\`.mini
[[F2 C3] E3@2] [[E2 B2] D3@2] [[Eb2 Bb2] Db3@2] [[D2 A2] C3 [F3,G2]]\`
).transpose(12).slow(48).tone(
new PolySynth().chain(
new Gain(0.3),
@@ -296,9 +296,9 @@ export const zeldasRescue = `stack(
Destination)
)`;
export const technoDrums = `stack(
'c1*2'.m.tone(new Tone.MembraneSynth().toDestination()),
'~ x'.m.tone(new Tone.NoiseSynth().toDestination()),
'[~ c4]*2'.m.tone(new Tone.MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),Destination))
"c1*2".tone(new Tone.MembraneSynth().toDestination()),
"~ x".tone(new Tone.NoiseSynth().toDestination()),
"[~ c4]*2".tone(new Tone.MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),Destination))
)`;
export const loungerave = `() => {
const delay = new FeedbackDelay(1/8, .2).chain(vol(0.5), out);
@@ -309,22 +309,22 @@ export const loungerave = `() => {
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
'c1*2'.m.tone(kick).bypass('<0@7 1>/8'.m),
'~ <x!7 [x@3 x]>'.m.tone(snare).bypass('<0@7 1>/4'.m),
'[~ c4]*2'.m.tone(hihat)
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose('<0 1>/8'.m).transpose(1);
const thru = (x) => x.transpose("<0 1>/8").transpose(1);
const synths = stack(
'<C2 Bb1 Ab1 [G1 [G2 G1]]>/2'.m.groove('[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2'.m).edit(thru).tone(bass),
'<Cm7 Bb7 Fm7 G7b9>/2'.m.groove('~ [x@0.1 ~]'.m).voicings().edit(thru).every(2, early(1/4)).tone(keys).bypass('<0@7 1>/8'.m.early(1/4))
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2").edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b9>/2".struct("~ [x@0.1 ~]").voicings().edit(thru).every(2, early(1/4)).tone(keys).bypass("<0@7 1>/8".early(1/4))
)
return stack(
drums,
synths
)
//.bypass('<0 1>*4'.m)
//.early('0.25 0'.m);
//.bypass("<0 1>*4")
//.early("0.25 0");
}`;
export const caverave = `() => {
const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
@@ -335,86 +335,41 @@ export const caverave = `() => {
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
'c1*2'.m.tone(kick).bypass('<0@7 1>/8'.m),
'~ <x!7 [x@3 x]>'.m.tone(snare).bypass('<0@7 1>/4'.m),
'[~ c4]*2'.m.tone(hihat)
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose('<0 1>/8'.m).transpose(-1);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
'<eb4 d4 c4 b3>/2'.m.scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).groove('[~ x]*2'.m)
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).struct("[~ x]*2")
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass('<1 0>/16'.m),
'<C2 Bb1 Ab1 [G1 [G2 G1]]>/2'.m.groove('[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2'.m.fast(2)).edit(thru).tone(bass),
'<Cm7 Bb7 Fm7 G7b13>/2'.m.groove('~ [x@0.1 ~]'.m.fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass('<0@7 1>/8'.m.early(1/4))
).edit(thru).tone(keys).bypass("<1 0>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass("<0@7 1>/8".early(1/4))
)
return stack(
drums.fast(2),
synths
).slow(2);
}`;
export const caveravefuture = `() => {
const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
\`c1*2\`.tone(kick).bypass(\`<0@7 1>/8\`),
\`~ <x!7 [x@3 x]>\`.tone(snare).bypass(\`<0@7 1>/4\`),
\`[~ c4]*2\`.tone(hihat)
);
const thru = (x) => x.transpose(\`<0 1>/8\`).transpose(-1);
const synths = stack(
\`<eb4 d4 c4 b3>/2\`.scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).groove(\`[~ x]*2\`)
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass(\`<1 0>/16\`),
\`<C2 Bb1 Ab1 [G1 [G2 G1]]>/2\`.groove(\`x [~ x] <[~ [~ x]]!3 [x x]>@2\`).edit(thru).tone(bass),
\`<Cm7 Bb7 Fm7 G7b13>/2\`.groove(\`~ [x@0.5 ~]\`.fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass(\`<0@7 1>/8\`.early(1/4)),
)
export const callcenterhero = `()=>{
const bpm = 90;
const lead = polysynth().set({...osc('sine4'),...adsr(.004)}).chain(vol(0.15),out)
const bass = fmsynth({...osc('sawtooth6'),...adsr(0.05,.6,0.8,0.1)}).chain(vol(0.6), out);
const s = scale(slowcat('F3 minor', 'Ab3 major', 'Bb3 dorian', 'C4 phrygian dominant').slow(4));
return stack(
drums.fast(2),
synths
).slow(2);
}`;
export const caveravefuture2 = `const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
"<eb4 d4 c4 b3>/2".scale(timeCat([3, 'C minor'], [1, 'C melodic minor']).slow(8)).groove("[~ x]*2")
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass("<1 0>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".groove("x [~ x] <[~ [~ x]]!3 [x x]>@2").edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".groove("~ [x@0.5 ~]".fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass("<0@7 1>/8".early(1/4)),
)
$: stack(
drums.fast(2),
synths
).slow(2);
"0 2".struct("<x ~> [x ~]").edit(s).scaleTranspose(stack(0,2)).tone(lead),
"<6 7 9 7>".struct("[~ [x ~]*2]*2").edit(s).scaleTranspose("[0,2] [2,4]".fast(2).every(4,rev)).tone(lead),
"-14".struct("[~ x@0.8]*2".early(0.01)).edit(s).tone(bass),
"c2*2".tone(membrane().chain(vol(0.6), out)),
"~ c2".tone(noise().chain(vol(0.2), out)),
"c4*4".tone(metal(adsr(0,.05,0)).chain(vol(0.03), out))
)
.slow(120 / bpm)
}
`;
+12 -4
View File
@@ -6,14 +6,16 @@ import usePostMessage from "./usePostMessage.js";
let s4 = () => {
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
};
function useRepl({tune, defaultSynth, autolink = true}) {
function useRepl({tune, defaultSynth, autolink = true, onEvent}) {
const id = useMemo(() => s4(), []);
const [code, setCode] = useState(tune);
const [activeCode, setActiveCode] = useState();
const [log, setLog] = useState("");
const [error, setError] = useState();
const [hash, setHash] = useState("");
const [pattern, setPattern] = useState();
const dirty = code !== activeCode;
const dirty = code !== activeCode || error;
const generateHash = () => encodeURIComponent(btoa(code));
const activateCode = (_code = code) => {
!cycle.started && cycle.start();
broadcast({type: "start", from: id});
@@ -27,9 +29,12 @@ function useRepl({tune, defaultSynth, autolink = true}) {
if (autolink) {
window.location.hash = "#" + encodeURIComponent(btoa(code));
}
setHash(generateHash());
setError(void 0);
setActiveCode(_code);
} catch (err) {
err.message = "evaluation error: " + err.message;
console.warn(err);
setError(err);
}
};
@@ -43,6 +48,7 @@ function useRepl({tune, defaultSynth, autolink = true}) {
const cycle = useCycle({
onEvent: useCallback((time, event) => {
try {
onEvent?.(event);
if (!event.value?.onTrigger) {
const note = event.value?.value || event.value;
if (!isNote(note)) {
@@ -62,11 +68,12 @@ function useRepl({tune, defaultSynth, autolink = true}) {
err.message = "unplayable event: " + err?.message;
pushLog(err.message);
}
}, []),
}, [onEvent]),
onQuery: useCallback((span) => {
try {
return pattern?.query(span) || [];
} catch (err) {
err.message = "query error: " + err.message;
setError(err);
return [];
}
@@ -99,7 +106,8 @@ function useRepl({tune, defaultSynth, autolink = true}) {
togglePlay,
activateCode,
activeCode,
pushLog
pushLog,
hash
};
}
export default useRepl;
+1 -1
View File
@@ -18,7 +18,7 @@ Pattern.prototype.voicings = function(range) {
range = ["F3", "A4"];
}
return this.fmapNested((event) => {
lastVoicing = getVoicing(event.value, lastVoicing, range);
lastVoicing = getVoicing(event.value?.value || event.value, lastVoicing, range);
return stack(...lastVoicing);
});
};
+51
View File
@@ -958,6 +958,9 @@ select {
.flex {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.contents {
display: contents;
}
@@ -967,6 +970,9 @@ select {
.h-full {
height: 100%;
}
.h-5 {
height: 1.25rem;
}
.min-h-screen {
min-height: 100vh;
}
@@ -979,6 +985,9 @@ select {
.w-16 {
width: 4rem;
}
.w-5 {
width: 1.25rem;
}
.max-w-3xl {
max-width: 48rem;
}
@@ -991,6 +1000,22 @@ select {
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
@-webkit-keyframes pulse {
50% {
opacity: .5;
}
}
@keyframes pulse {
50% {
opacity: .5;
}
}
.animate-pulse {
-webkit-animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
.cursor-not-allowed {
cursor: not-allowed;
}
@@ -1024,9 +1049,15 @@ select {
.overflow-auto {
overflow: auto;
}
.overflow-hidden {
overflow: hidden;
}
.whitespace-pre {
white-space: pre;
}
.rounded-md {
border-radius: 0.375rem;
}
.border {
border-width: 1px;
}
@@ -1036,6 +1067,12 @@ select {
.border-b {
border-bottom-width: 1px;
}
.border-t {
border-top-width: 1px;
}
.border-r {
border-right-width: 1px;
}
.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
@@ -1074,10 +1111,16 @@ select {
.p-2 {
padding: 0.5rem;
}
.p-1 {
padding: 0.25rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.pr-2 {
padding-right: 0.5rem;
}
.text-right {
text-align: right;
}
@@ -1089,6 +1132,10 @@ select {
font-size: 0.75rem;
line-height: 1rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-gray-100 {
--tw-text-opacity: 1;
color: rgb(243 244 246 / var(--tw-text-opacity));
@@ -1105,6 +1152,10 @@ select {
--tw-text-opacity: 1;
color: rgb(148 163 184 / var(--tw-text-opacity));
}
.text-red-200 {
--tw-text-opacity: 1;
color: rgb(254 202 202 / var(--tw-text-opacity));
}
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -695,12 +695,16 @@ Ensure the default browser behavior of the `hidden` attribute.
display: block;
}.flex {
display: flex;
}.inline-flex {
display: inline-flex;
}.contents {
display: contents;
}.h-16 {
height: 4rem;
}.h-full {
height: 100%;
}.h-5 {
height: 1.25rem;
}.min-h-screen {
min-height: 100vh;
}.min-h-\[200px\] {
@@ -709,6 +713,8 @@ Ensure the default browser behavior of the `hidden` attribute.
width: 100%;
}.w-16 {
width: 4rem;
}.w-5 {
width: 1.25rem;
}.max-w-3xl {
max-width: 48rem;
}.flex-none {
@@ -717,6 +723,19 @@ Ensure the default browser behavior of the `hidden` attribute.
flex-grow: 1;
}.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}@-webkit-keyframes pulse {
50% {
opacity: .5;
}
}@keyframes pulse {
50% {
opacity: .5;
}
}.animate-pulse {
-webkit-animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}.cursor-not-allowed {
cursor: not-allowed;
}.flex-col {
@@ -741,14 +760,22 @@ Ensure the default browser behavior of the `hidden` attribute.
margin-bottom: calc(0px * var(--tw-space-y-reverse));
}.overflow-auto {
overflow: auto;
}.overflow-hidden {
overflow: hidden;
}.whitespace-pre {
white-space: pre;
}.rounded-md {
border-radius: 0.375rem;
}.border {
border-width: 1px;
}.border-0 {
border-width: 0px;
}.border-b {
border-bottom-width: 1px;
}.border-t {
border-top-width: 1px;
}.border-r {
border-right-width: 1px;
}.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
@@ -777,9 +804,13 @@ Ensure the default browser behavior of the `hidden` attribute.
padding: 1rem;
}.p-2 {
padding: 0.5rem;
}.p-1 {
padding: 0.25rem;
}.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}.pr-2 {
padding-right: 0.5rem;
}.text-right {
text-align: right;
}.text-2xl {
@@ -788,6 +819,9 @@ Ensure the default browser behavior of the `hidden` attribute.
}.text-xs {
font-size: 0.75rem;
line-height: 1rem;
}.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}.text-gray-100 {
--tw-text-opacity: 1;
color: rgb(243 244 246 / var(--tw-text-opacity));
@@ -800,6 +834,9 @@ Ensure the default browser behavior of the `hidden` attribute.
}.text-slate-400 {
--tw-text-opacity: 1;
color: rgb(148 163 184 / var(--tw-text-opacity));
}.text-red-200 {
--tw-text-opacity: 1;
color: rgb(254 202 202 / var(--tw-text-opacity));
}.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}.react-codemirror2,
@@ -822,148 +859,6 @@ Ensure the default browser behavior of the `hidden` attribute.
--tw-ring-color: rgb(30 41 59 / var(--tw-ring-opacity));
}
/*
Name: material
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
.cm-s-material.CodeMirror {
background-color: #263238;
color: #EEFFFF;
}
.cm-s-material .CodeMirror-gutters {
background: #263238;
color: #546E7A;
border: none;
}
.cm-s-material .CodeMirror-guttermarker,
.cm-s-material .CodeMirror-guttermarker-subtle,
.cm-s-material .CodeMirror-linenumber {
color: #546E7A;
}
.cm-s-material .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-material.cm-fat-cursor .CodeMirror-cursor {
background-color: #5d6d5c80 !important;
}
.cm-s-material .cm-animate-fat-cursor {
background-color: #5d6d5c80 !important;
}
.cm-s-material div.CodeMirror-selected {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material.CodeMirror-focused div.CodeMirror-selected {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-line::selection,
.cm-s-material .CodeMirror-line>span::selection,
.cm-s-material .CodeMirror-line>span>span::selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-line::-moz-selection,
.cm-s-material .CodeMirror-line>span::-moz-selection,
.cm-s-material .CodeMirror-line>span>span::-moz-selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.5);
}
.cm-s-material .cm-keyword {
color: #C792EA;
}
.cm-s-material .cm-operator {
color: #89DDFF;
}
.cm-s-material .cm-variable-2 {
color: #EEFFFF;
}
.cm-s-material .cm-variable-3,
.cm-s-material .cm-type {
color: #f07178;
}
.cm-s-material .cm-builtin {
color: #FFCB6B;
}
.cm-s-material .cm-atom {
color: #F78C6C;
}
.cm-s-material .cm-number {
color: #FF5370;
}
.cm-s-material .cm-def {
color: #82AAFF;
}
.cm-s-material .cm-string {
color: #C3E88D;
}
.cm-s-material .cm-string-2 {
color: #f07178;
}
.cm-s-material .cm-comment {
color: #546E7A;
}
.cm-s-material .cm-variable {
color: #f07178;
}
.cm-s-material .cm-tag {
color: #FF5370;
}
.cm-s-material .cm-meta {
color: #FFCB6B;
}
.cm-s-material .cm-attribute {
color: #C792EA;
}
.cm-s-material .cm-property {
color: #C792EA;
}
.cm-s-material .cm-qualifier {
color: #DECB6B;
}
.cm-s-material .cm-variable-3,
.cm-s-material .cm-type {
color: #DECB6B;
}
.cm-s-material .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-material .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
/* BASICS */
.CodeMirror {
@@ -1309,4 +1204,146 @@ div.CodeMirror-dragcursors {
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }
/*# sourceMappingURL=index.1e09ac22.css.map */
/*
Name: material
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
.cm-s-material.CodeMirror {
background-color: #263238;
color: #EEFFFF;
}
.cm-s-material .CodeMirror-gutters {
background: #263238;
color: #546E7A;
border: none;
}
.cm-s-material .CodeMirror-guttermarker,
.cm-s-material .CodeMirror-guttermarker-subtle,
.cm-s-material .CodeMirror-linenumber {
color: #546E7A;
}
.cm-s-material .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-material.cm-fat-cursor .CodeMirror-cursor {
background-color: #5d6d5c80 !important;
}
.cm-s-material .cm-animate-fat-cursor {
background-color: #5d6d5c80 !important;
}
.cm-s-material div.CodeMirror-selected {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material.CodeMirror-focused div.CodeMirror-selected {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-line::selection,
.cm-s-material .CodeMirror-line>span::selection,
.cm-s-material .CodeMirror-line>span>span::selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-line::-moz-selection,
.cm-s-material .CodeMirror-line>span::-moz-selection,
.cm-s-material .CodeMirror-line>span>span::-moz-selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.5);
}
.cm-s-material .cm-keyword {
color: #C792EA;
}
.cm-s-material .cm-operator {
color: #89DDFF;
}
.cm-s-material .cm-variable-2 {
color: #EEFFFF;
}
.cm-s-material .cm-variable-3,
.cm-s-material .cm-type {
color: #f07178;
}
.cm-s-material .cm-builtin {
color: #FFCB6B;
}
.cm-s-material .cm-atom {
color: #F78C6C;
}
.cm-s-material .cm-number {
color: #FF5370;
}
.cm-s-material .cm-def {
color: #82AAFF;
}
.cm-s-material .cm-string {
color: #C3E88D;
}
.cm-s-material .cm-string-2 {
color: #f07178;
}
.cm-s-material .cm-comment {
color: #546E7A;
}
.cm-s-material .cm-variable {
color: #f07178;
}
.cm-s-material .cm-tag {
color: #FF5370;
}
.cm-s-material .cm-meta {
color: #FFCB6B;
}
.cm-s-material .cm-attribute {
color: #C792EA;
}
.cm-s-material .cm-property {
color: #C792EA;
}
.cm-s-material .cm-qualifier {
color: #DECB6B;
}
.cm-s-material .cm-variable-3,
.cm-s-material .cm-type {
color: #DECB6B;
}
.cm-s-material .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-material .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}
/*# sourceMappingURL=index.fd7d9b66.css.map */
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico">
<link rel="stylesheet" type="text/css" href="/tutorial/index.1e09ac22.css">
<link rel="stylesheet" type="text/css" href="/tutorial/index.fd7d9b66.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Strudel REPL">
<title>Strudel Tutorial</title>
@@ -11,6 +11,6 @@
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/tutorial/index.dc15e374.js" defer=""></script>
<script src="/tutorial/index.acaced6c.js" defer=""></script>
</body>
</html>
+1
View File
@@ -1936,6 +1936,7 @@ function peg$parse(input, options) {
this.type_ = "element";
this.source_ = source;
this.options_ = options;
this.location_ = location();
}
var CommandStub = function(name, options)
+1
View File
@@ -24,6 +24,7 @@
this.type_ = "element";
this.source_ = source;
this.options_ = options;
this.location_ = location();
}
var CommandStub = function(name, options)
+7 -2
View File
@@ -1,6 +1,6 @@
import React, { useCallback, useLayoutEffect, useRef } from 'react';
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import * as Tone from 'tone';
import CodeMirror from './CodeMirror';
import CodeMirror, { markEvent } from './CodeMirror';
import cx from './cx';
import { evaluate } from './evaluate';
import logo from './logo.svg';
@@ -35,9 +35,11 @@ function getRandomTune() {
const randomTune = getRandomTune();
function App() {
const [editor, setEditor] = useState<any>();
const { setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog } = useRepl({
tune: decoded || randomTune,
defaultSynth,
onEvent: useCallback(markEvent(editor), [editor]),
});
const logBox = useRef<any>();
// scroll log box to bottom when log changes
@@ -106,10 +108,13 @@ function App() {
<div className={cx('h-full bg-[#2A3236]', error ? 'focus:ring-red-500' : 'focus:ring-slate-800')}>
<CodeMirror
value={code}
editorDidMount={setEditor}
options={{
mode: 'javascript',
theme: 'material',
lineNumbers: true,
styleSelectedText: true,
cursorBlinkRate: 0,
}}
onChange={(_: any, __: any, value: any) => setCode(value)}
/>
+28 -3
View File
@@ -2,14 +2,39 @@ import React from 'react';
import { Controlled as CodeMirror2 } from 'react-codemirror2';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/pegjs/pegjs.js';
import 'codemirror/theme/material.css';
// import 'codemirror/theme/material.css';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/material.css';
export default function CodeMirror({ value, onChange, options }: any) {
export default function CodeMirror({ value, onChange, options, editorDidMount }: any) {
options = options || {
mode: 'javascript',
theme: 'material',
lineNumbers: true,
styleSelectedText: true,
cursorBlinkRate: 500,
};
return <CodeMirror2 value={value} options={options} onBeforeChange={onChange} />;
return <CodeMirror2 value={value} options={options} onBeforeChange={onChange} editorDidMount={editorDidMount} />;
}
export const markEvent = (editor) => (event) => {
const locs = event.value.locations;
if (!locs || !editor) {
return;
}
// mark active event
const marks = locs.map(({ start, end }) =>
editor
.getDoc()
.markText(
{ line: start.line - 1, ch: start.column },
{ line: end.line - 1, ch: end.column },
{ css: 'background-color: #FFCA28; color: black' }
)
);
//Tone.Transport.schedule(() => { // problem: this can be cleared by scheduler...
setTimeout(() => {
marks.forEach((mark) => mark.clear());
// }, '+' + event.duration * 0.5);
}, event.duration * 0.9 * 1000);
};
+8 -6
View File
@@ -3,7 +3,6 @@ import './tone';
import './midi';
import './voicings';
import './tonal';
import './groove';
import shapeshifter from './shapeshifter';
import { minify } from './parse';
import * as Tone from 'tone';
@@ -32,14 +31,17 @@ Object.assign(globalThis, bootstrapped, Tone, toneHelpers);
export const evaluate: any = (code: string) => {
const shapeshifted = shapeshifter(code); // transform syntactically correct js code to semantically usable code
// console.log('shapeshifted', shapeshifted);
let evaluated = eval(shapeshifted);
if (typeof evaluated === 'function') {
evaluated = evaluated();
}
const pattern = minify(evaluated); // eval and minify (if user entered a string)
if (pattern?.constructor?.name !== 'Pattern') {
const message = `got "${typeof pattern}" instead of pattern`;
throw new Error(message + (typeof pattern === 'function' ? ', did you forget to call a function?' : '.'));
if (typeof evaluated === 'string') {
evaluated = strudel.withLocationOffset(minify(evaluated), { start: { line: 1, column: -1 } });
}
return { mode: 'javascript', pattern: pattern };
if (evaluated?.constructor?.name !== 'Pattern') {
const message = `got "${typeof evaluated}" instead of pattern`;
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
}
return { mode: 'javascript', pattern: evaluated };
};
-10
View File
@@ -1,10 +0,0 @@
import { Pattern as _Pattern } from '../../strudel.mjs';
const Pattern = _Pattern as any;
// is this the same as struct?
Pattern.prototype.groove = function (groove) {
return groove.fmap(() => (v) => v).appLeft(this);
};
Pattern.prototype.define('groove', (groove, pat) => pat.groove(groove), { composable: true });
+14 -1
View File
@@ -1,6 +1,7 @@
import * as krill from '../krill-parser';
import * as strudel from '../../strudel.mjs';
import { Scale, Note, Interval } from '@tonaljs/tonal';
import { addMiniLocations } from './shapeshifter';
const { pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence } = strudel;
@@ -49,6 +50,7 @@ function resolveReplications(ast) {
{
type_: 'element',
source_: child.source_,
location_: child.location_,
options_: {
operator: {
type_: 'stretch',
@@ -91,7 +93,18 @@ export function patternifyAST(ast: any): any {
return silence;
}
if (typeof ast.source_ !== 'object') {
return ast.source_;
if (!addMiniLocations) {
return ast.source_;
}
if (!ast.location_) {
console.warn('no location for', ast);
return ast.source_;
}
const { start, end } = ast.location_;
// return ast.source_;
// the following line expects the shapeshifter to wrap this in withLocationOffset
// because location_ is only relative to the mini string, but we need it relative to whole code
return pure(ast.source_).withLocation({ start, end });
}
return patternifyAST(ast.source_);
case 'stretch':
+320 -8
View File
@@ -1,30 +1,342 @@
import { parseScript } from './shift-parser/index.js'; // npm module does not work in the browser
import { parseScriptWithLocation } from './shift-parser/index.js'; // npm module does not work in the browser
import traverser from './shift-traverser'; // npm module does not work in the browser
const { replace } = traverser;
import { LiteralStringExpression, IdentifierExpression } from 'shift-ast';
import {
LiteralStringExpression,
IdentifierExpression,
CallExpression,
StaticMemberExpression,
Script,
} from 'shift-ast';
import codegen from 'shift-codegen';
import * as strudel from '../../strudel.mjs';
const { Pattern } = strudel;
const isNote = (name) => /^[a-gC-G][bs]?[0-9]$/.test(name);
const addLocations = true;
export const addMiniLocations = true;
/*
not supported for highlighting:
- 'b3'.p
- mini('b3') / m('b3')
- 'b3'.m / 'b3'.mini
*/
export default (code) => {
const ast = parseScript(code);
const shifted = replace(ast, {
const ast = parseScriptWithLocation(code);
const artificialNodes = [];
const parents = [];
const shifted = replace(ast.tree, {
enter(node, parent) {
// replace identifiers that are a note with a note string
parents.push(parent);
const isSynthetic = parents.some((p) => artificialNodes.includes(p));
if (isSynthetic) {
return node;
}
// replace template string `xxx` with 'xxx'.m
if (isBackTickString(node)) {
const minified = getMinified(node.elements[0].rawValue);
return wrapLocationOffset(minified, node, ast.locations, artificialNodes);
}
// allows to use top level strings, which are normally directives... but we don't need directives
if (node.type === 'Script' && node.directives.length === 1 && !node.statements.length) {
const minified = getMinified(node.directives[0].rawValue);
const wrapped = wrapLocationOffset(minified, node.directives[0], ast.locations, artificialNodes);
return new Script({ directives: [], statements: [wrapped] });
}
// replace double quote string "xxx" with 'xxx'.m
if (isStringWithDoubleQuotes(node, ast.locations, code)) {
const minified = getMinified(node.value);
return wrapLocationOffset(minified, node, ast.locations, artificialNodes);
}
// replace double quote string "xxx" with 'xxx'.m
if (isStringWithDoubleQuotes(node, ast.locations, code)) {
const minified = getMinified(node.value);
return wrapLocationOffset(minified, node, ast.locations, artificialNodes);
}
// operator overloading => still not done
const operators = {
'*': 'fast',
'/': 'slow',
'&': 'stack',
'&&': 'append',
};
if (
node.type === 'BinaryExpression' &&
operators[node.operator] &&
['LiteralNumericExpression', 'LiteralStringExpression', 'IdentifierExpression'].includes(node.right?.type) &&
canBeOverloaded(node.left)
) {
let arg = node.left;
if (node.left.type === 'IdentifierExpression') {
arg = wrapFunction('reify', node.left);
}
return new CallExpression({
callee: new StaticMemberExpression({
property: operators[node.operator],
object: wrapFunction('reify', arg),
}),
arguments: [node.right],
});
}
const isMarkable = isPatternArg(parents) || hasModifierCall(parent);
// add to location to pure(x) calls
if (node.type === 'CallExpression' && node.callee.name === 'pure') {
return reifyWithLocation(node.arguments[0].name, node.arguments[0], ast.locations, artificialNodes);
}
// replace pseudo note variables
if (node.type === 'IdentifierExpression') {
if (isNote(node.name)) {
const value = node.name[1] === 's' ? node.name.replace('s', '#') : node.name;
if (addLocations && isMarkable) {
return reifyWithLocation(value, node, ast.locations, artificialNodes);
}
return new LiteralStringExpression({ value });
}
if (node.name === 'r') {
return new IdentifierExpression({ name: 'silence' });
}
}
if (addLocations && node.type === 'LiteralStringExpression' && isMarkable) {
// console.log('add', node);
return reifyWithLocation(node.value, node, ast.locations, artificialNodes);
}
if (!addMiniLocations) {
return wrapFunction('reify', node);
}
// mini notation location handling
const miniFunctions = ['mini', 'm'];
const isAlreadyWrapped = parent?.type === 'CallExpression' && parent.callee.name === 'withLocationOffset';
if (node.type === 'CallExpression' && miniFunctions.includes(node.callee.name) && !isAlreadyWrapped) {
// mini('c3')
if (node.arguments.length > 1) {
// TODO: transform mini(...args) to cat(...args.map(mini)) ?
console.warn('multi arg mini locations not supported yet...');
return node;
}
return wrapLocationOffset(node, node.arguments, ast.locations, artificialNodes);
}
if (node.type === 'StaticMemberExpression' && miniFunctions.includes(node.property) && !isAlreadyWrapped) {
// 'c3'.mini or 'c3'.m
return wrapLocationOffset(node, node.object, ast.locations, artificialNodes);
}
return node;
},
leave() {
parents.pop();
},
});
return codegen(shifted);
};
// TODO: turn x.groove['[~ x]*2'] into x.groove('[~ x]*2'.m)
// and ['c1*2'].xx into 'c1*2'.m.xx ??
// or just all templated strings?? x.groove(`[~ x]*2`)
function wrapFunction(name, ...args) {
return new CallExpression({
callee: new IdentifierExpression({ name }),
arguments: args,
});
}
function getMinified(value) {
return new StaticMemberExpression({
object: new LiteralStringExpression({ value }),
property: 'm',
});
}
function isBackTickString(node) {
return node.type === 'TemplateExpression' && node.elements.length === 1;
}
function isStringWithDoubleQuotes(node, locations, code) {
if (node.type !== 'LiteralStringExpression') {
return false;
}
const loc = locations.get(node);
const snippet = code.slice(loc.start.offset, loc.end.offset);
return snippet[0] === '"'; // we can trust the end is also ", as the parsing did not fail
}
// returns true if the given parents belong to a pattern argument node
// this is used to check if a node should receive a location for highlighting
function isPatternArg(parents) {
if (!parents.length) {
return false;
}
const ancestors = parents.slice(0, -1);
const parent = parents[parents.length - 1];
if (isPatternFactory(parent)) {
return true;
}
if (parent?.type === 'ArrayExpression') {
return isPatternArg(ancestors);
}
return false;
}
function hasModifierCall(parent) {
// TODO: modifiers are more than composables, for example every is not composable but should be seen as modifier..
// need all prototypes of Pattern
return (
parent?.type === 'StaticMemberExpression' && Object.keys(Pattern.prototype.composable).includes(parent.property)
);
}
function isPatternFactory(node) {
return node?.type === 'CallExpression' && Object.keys(Pattern.prototype.factories).includes(node.callee.name);
}
function canBeOverloaded(node) {
return (node.type === 'IdentifierExpression' && isNote(node.name)) || isPatternFactory(node);
// TODO: support sequence(c3).transpose(3).x.y.z
}
// turn node into withLocationOffset(node, location)
function wrapLocationOffset(node, stringNode, locations, artificialNodes) {
// console.log('wrapppp', stringNode);
const expression = {
type: 'CallExpression',
callee: {
type: 'IdentifierExpression',
name: 'withLocationOffset',
},
arguments: [node, getLocationObject(stringNode, locations)],
};
artificialNodes.push(expression);
// console.log('wrapped', codegen(expression));
return expression;
}
// turns node in reify(value).withLocation(location), where location is the node's location in the source code
// with this, the reified pattern can pass its location to the event, to know where to highlight when it's active
function reifyWithLocation(value, node, locations, artificialNodes) {
const withLocation = new CallExpression({
callee: new StaticMemberExpression({
object: wrapFunction('reify', new LiteralStringExpression({ value })),
property: 'withLocation',
}),
arguments: [getLocationObject(node, locations)],
});
artificialNodes.push(withLocation);
return withLocation;
}
// returns ast for source location object
function getLocationObject(node, locations) {
/*const locationAST = parseScript(
"x=" + JSON.stringify(ast.locations.get(node))
).statements[0].expression.expression;
console.log("locationAST", locationAST);*/
/*const callAST = parseScript(
`reify(${node.name}).withLocation(${JSON.stringify(
ast.locations.get(node)
)})`
).statements[0].expression;*/
const loc = locations.get(node);
return {
type: 'ObjectExpression',
properties: [
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'start',
},
expression: {
type: 'ObjectExpression',
properties: [
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'line',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.start.line,
},
},
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'column',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.start.column,
},
},
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'offset',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.start.offset,
},
},
],
},
},
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'end',
},
expression: {
type: 'ObjectExpression',
properties: [
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'line',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.end.line,
},
},
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'column',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.end.column,
},
},
{
type: 'DataProperty',
name: {
type: 'StaticPropertyName',
value: 'offset',
},
expression: {
type: 'LiteralNumericExpression',
value: loc.end.offset,
},
},
],
},
},
],
};
}
+10 -3
View File
@@ -4,12 +4,12 @@ import { Pattern as _Pattern } from '../../strudel.mjs';
const Pattern = _Pattern as any;
export declare interface NoteEvent {
value: string;
value: string | number;
scale?: string;
}
function toNoteEvent(event: string | NoteEvent): NoteEvent {
if (typeof event === 'string') {
if (typeof event === 'string' || typeof event === 'number') {
return { value: event };
}
if (event.value) {
@@ -73,6 +73,10 @@ Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones as number)
: String(intervalOrSemitones);
if (typeof value === 'number') {
const semitones = typeof interval === 'string' ? Interval.semitones(interval) || 0 : interval;
return { value: value + semitones };
}
return { value: Note.transpose(value, interval), scale };
});
};
@@ -86,7 +90,10 @@ Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
Pattern.prototype._scaleTranspose = function (offset: number | string) {
return this._mapNotes(({ value, scale }: NoteEvent) => {
if (!scale) {
throw new Error('can only use scaleOffset after .scale');
throw new Error('can only use scaleTranspose after .scale');
}
if (typeof value !== 'string') {
throw new Error('can only use scaleTranspose with notes');
}
return { value: scaleTranspose(scale, Number(offset), value), scale };
});
+178 -246
View File
@@ -1,15 +1,15 @@
export const timeCatMini = `stack(
'c3@3 [eb3, g3, [c4 d4]/2]'.mini,
'c2 g2'.mini,
'[eb4@5 [f4 eb4 d4]@3] [eb4 c4]/2'.mini.slow(8)
"c3@3 [eb3, g3, [c4 d4]/2]",
"c2 g2",
"[eb4@5 [f4 eb4 d4]@3] [eb4 c4]/2".slow(8)
)`;
export const timeCat = `stack(
timeCat([3, c3], [1, stack(eb3, g3, m(c4, d4).slow(2))]),
m(c2, g2),
timeCat([3, c3], [1, stack(eb3, g3, cat(c4, d4).slow(2))]),
cat(c2, g2),
sequence(
timeCat([5, eb4], [3, m(f4, eb4, d4)]),
m(eb4, c4).slow(2)
timeCat([5, eb4], [3, cat(f4, eb4, d4)]),
cat(eb4, c4).slow(2)
).slow(4)
)`;
@@ -58,48 +58,48 @@ export const tetrisWithFunctions = `stack(sequence(
).slow(16)`;
export const tetris = `stack(
mini(
'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 ~'
cat(
"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 ~"
),
mini(
'e2 e3 e2 e3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 a2 a3',
'g#2 g#3 g#2 g#3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 b1 c2',
'd2 d3 d2 d3 d2 d3 d2 d3',
'c2 c3 c2 c3 c2 c3 c2 c3',
'b1 b2 b1 b2 e2 e3 e2 e3',
'a1 a2 a1 a2 a1 a2 a1 a2'
cat(
"e2 e3 e2 e3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 a2 a3",
"g#2 g#3 g#2 g#3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 b1 c2",
"d2 d3 d2 d3 d2 d3 d2 d3",
"c2 c3 c2 c3 c2 c3 c2 c3",
"b1 b2 b1 b2 e2 e3 e2 e3",
"a1 a2 a1 a2 a1 a2 a1 a2",
)
).slow(16)`;
export const tetrisRev = `stack(
mini(
'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 ~'
cat(
"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 ~",
).rev(),
mini(
'e2 e3 e2 e3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 a2 a3',
'g#2 g#3 g#2 g#3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 b1 c2',
'd2 d3 d2 d3 d2 d3 d2 d3',
'c2 c3 c2 c3 c2 c3 c2 c3',
'b1 b2 b1 b2 e2 e3 e2 e3',
'a1 a2 a1 a2 a1 a2 a1 a2'
cat(
"e2 e3 e2 e3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 a2 a3",
"g#2 g#3 g#2 g#3 e2 e3 e2 e3",
"a2 a3 a2 a3 a2 a3 b1 c2",
"d2 d3 d2 d3 d2 d3 d2 d3",
"c2 c3 c2 c3 c2 c3 c2 c3",
"b1 b2 b1 b2 e2 e3 e2 e3",
"a1 a2 a1 a2 a1 a2 a1 a2",
).rev()
).slow(16)`;
@@ -112,7 +112,7 @@ export const tetrisRev = `stack(
*/
/* export const tetrisMini1 =
"'[[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 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]'.mini.slow(16)";
'"[[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 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]".slow(16)';
*/
export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
@@ -129,7 +129,7 @@ export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[[d2 d3]*4]
[[c2 c3]*4]
[[b1 b2]*2 [e2 e3]*2]
[[a1 a2]*4]\`.mini.slow(16)
[[a1 a2]*4]\`.slow(16)
`;
/* export const tetrisHaskellH = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
@@ -178,134 +178,127 @@ export const spanish = `slowcat(
stack(g3,b3,d4)
)`;
export const whirlyStrudel = `mini("[e4 [b2 b3] c4]")
export const whirlyStrudel = `sequence(e4, [b2, b3], c4)
.every(4, fast(2))
.every(3, slow(1.5))
.fast(slowcat(1.25, 1, 1.5))
.every(2, _ => mini("e4 ~ e3 d4 ~"))`;
.every(2, _ => sequence(e4, r, e3, d4, r))`;
export const swimming = `stack(
mini(
'~',
'~',
'~',
'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'
cat(
"~",
"~",
"~",
"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"
),
mini(
'[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]]'
cat(
"[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]]"
),
mini(
'[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]'
cat(
"[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]"
)
).slow(51);
`;
export const giantSteps = `stack(
// melody
mini(
'[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]',
cat(
"[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]",
),
// chords
mini(
'[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]'
cat(
"[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]"
).voicings(['E3', 'G4']),
// bass
mini(
'[B2 D2] [G2 Bb2] [Eb2 Bb3] [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 Bb3] [C#2 F#2]'
cat(
"[B2 D2] [G2 Bb2] [Eb2 Bb3] [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 Bb3] [C#2 F#2]"
)
).slow(20);`;
export const giantStepsReggae = `stack(
// melody
mini(
'[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]]',
cat(
"[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
mini(
'[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]'
cat(
"[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]"
)
.groove('~ [x ~]'.m.fast(4*8))
.struct("~ [x ~]".fast(4*8))
.voicings(['E3', 'G4']),
// bass
mini(
'[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]'
cat(
"[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]"
)
.groove('x ~'.m.fast(4*8))
.struct("x ~".fast(4*8))
).slow(25)`;
/* export const transposedChords = `stack(
m('c2 eb2 g2'),
m('Cm7').voicings(['g2','c4']).slow(2)
).transpose(
slowcat(1, 2, 3, 2).slow(2)
).transpose(5)`; */
export const transposedChordsHacked = `stack(
'c2 eb2 g2'.mini,
'Cm7'.pure.voicings(['g2','c4']).slow(2)
"c2 eb2 g2",
"Cm7".voicings(['g2','c4']).slow(2)
).transpose(
slowcat(1, 2, 3, 2).slow(2)
).transpose(5)`;
@@ -315,35 +308,20 @@ export const scaleTranspose = `stack(f2, f3, c4, ab4)
.scaleTranspose(sequence(0, -1, -2, -3).slow(4))
.transpose(sequence(0, 1).slow(16))`;
/* export const groove = `stack(
m('c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]'),
m('[C^7 A7] [Dm7 G7]')
.groove(m('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'))
.voicings(['G3','A4'])
).slow(4.5)`; */
export const groove = `stack(
'c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]'.mini,
'[C^7 A7] [Dm7 G7]'.mini.groove('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'.mini)
export const struct = `stack(
"c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]",
"[C^7 A7] [Dm7 G7]".struct("[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2")
.voicings(['G3','A4'])
).slow(4)`;
/* export const magicSofa = `stack(
m('[C^7 F^7 ~]/3 [Dm7 G7 A7 ~]/4')
.every(2, fast(2))
.voicings(),
m('[c2 f2 g2]/3 [d2 g2 a2 e2]/4')
).slow(1)
.transpose.slowcat(0, 2, 3, 4)`; */
export const magicSofa = `stack(
'<C^7 F^7 ~> <Dm7 G7 A7 ~>'.m
"<C^7 F^7 ~> <Dm7 G7 A7 ~>"
.every(2, fast(2))
.voicings(),
'<c2 f2 g2> <d2 g2 a2 e2>'.m
"<c2 f2 g2> <d2 g2 a2 e2>"
).slow(1).transpose.slowcat(0, 2, 3, 4)`;
/* export const confusedPhone = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
/* export const confusedPhone = `stack("[g2 ~@1.3] [c3 ~@1.3]".slow(2))
.superimpose(
x => transpose(-12,x).late(0),
x => transpose(7,x).late(0.2),
@@ -355,7 +333,7 @@ export const magicSofa = `stack(
.scaleTranspose(slowcat(0,1,2,1).slow(2))
.synth('triangle').gain(0.5).filter(1500)`; */
export const confusedPhoneDynamic = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
export const confusedPhoneDynamic = `stack("[g2 ~@1.3] [c3 ~@1.3]".slow(2))
.superimpose(
...[-12,7,10,12,24].slice(0,5).map((t,i,{length}) => x => transpose(t,x).late(i/length))
)
@@ -363,7 +341,7 @@ export const confusedPhoneDynamic = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
.scaleTranspose(slowcat(0,1,2,1).slow(2))
.synth('triangle').gain(0.5).filter(1500)`;
export const confusedPhone = `'[g2 ~@1.3] [c3 ~@1.3]'.mini
export const confusedPhone = `"[g2 ~@1.3] [c3 ~@1.3]"
.superimpose(
transpose(-12).late(0),
transpose(7).late(0.1),
@@ -382,14 +360,14 @@ export const zeldasRescue = `stack(
[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
[B3@2 D4] [A4@2 G4] D5@2
[D5@2 [C5 B4]] [[C5 B4] G4@2] [C5@2 [B4 A4]] [[B4 A4] E4@2]
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`.mini,
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`,
// bass
\`[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[D2 A2] C3@2] [[C2 G2] B2@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[Eb2 Bb2] Db3@2] [[D2 A2] C3 [F3,G2]]\`.mini
[[F2 C3] E3@2] [[E2 B2] D3@2] [[Eb2 Bb2] Db3@2] [[D2 A2] C3 [F3,G2]]\`
).transpose(12).slow(48).tone(
new PolySynth().chain(
new Gain(0.3),
@@ -399,9 +377,9 @@ export const zeldasRescue = `stack(
)`;
export const technoDrums = `stack(
'c1*2'.m.tone(new Tone.MembraneSynth().toDestination()),
'~ x'.m.tone(new Tone.NoiseSynth().toDestination()),
'[~ c4]*2'.m.tone(new Tone.MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),Destination))
"c1*2".tone(new Tone.MembraneSynth().toDestination()),
"~ x".tone(new Tone.NoiseSynth().toDestination()),
"[~ c4]*2".tone(new Tone.MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),Destination))
)`;
export const loungerave = `() => {
@@ -413,22 +391,22 @@ export const loungerave = `() => {
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
'c1*2'.m.tone(kick).bypass('<0@7 1>/8'.m),
'~ <x!7 [x@3 x]>'.m.tone(snare).bypass('<0@7 1>/4'.m),
'[~ c4]*2'.m.tone(hihat)
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose('<0 1>/8'.m).transpose(1);
const thru = (x) => x.transpose("<0 1>/8").transpose(1);
const synths = stack(
'<C2 Bb1 Ab1 [G1 [G2 G1]]>/2'.m.groove('[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2'.m).edit(thru).tone(bass),
'<Cm7 Bb7 Fm7 G7b9>/2'.m.groove('~ [x@0.1 ~]'.m).voicings().edit(thru).every(2, early(1/4)).tone(keys).bypass('<0@7 1>/8'.m.early(1/4))
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2").edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b9>/2".struct("~ [x@0.1 ~]").voicings().edit(thru).every(2, early(1/4)).tone(keys).bypass("<0@7 1>/8".early(1/4))
)
return stack(
drums,
synths
)
//.bypass('<0 1>*4'.m)
//.early('0.25 0'.m);
//.bypass("<0 1>*4")
//.early("0.25 0");
}`;
export const caverave = `() => {
@@ -440,22 +418,22 @@ export const caverave = `() => {
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
'c1*2'.m.tone(kick).bypass('<0@7 1>/8'.m),
'~ <x!7 [x@3 x]>'.m.tone(snare).bypass('<0@7 1>/4'.m),
'[~ c4]*2'.m.tone(hihat)
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose('<0 1>/8'.m).transpose(-1);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
'<eb4 d4 c4 b3>/2'.m.scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).groove('[~ x]*2'.m)
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).struct("[~ x]*2")
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass('<1 0>/16'.m),
'<C2 Bb1 Ab1 [G1 [G2 G1]]>/2'.m.groove('[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2'.m.fast(2)).edit(thru).tone(bass),
'<Cm7 Bb7 Fm7 G7b13>/2'.m.groove('~ [x@0.1 ~]'.m.fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass('<0@7 1>/8'.m.early(1/4))
).edit(thru).tone(keys).bypass("<1 0>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass("<0@7 1>/8".early(1/4))
)
return stack(
drums.fast(2),
@@ -463,65 +441,19 @@ export const caverave = `() => {
).slow(2);
}`;
export const caveravefuture = `() => {
const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
\`c1*2\`.tone(kick).bypass(\`<0@7 1>/8\`),
\`~ <x!7 [x@3 x]>\`.tone(snare).bypass(\`<0@7 1>/4\`),
\`[~ c4]*2\`.tone(hihat)
);
const thru = (x) => x.transpose(\`<0 1>/8\`).transpose(-1);
const synths = stack(
\`<eb4 d4 c4 b3>/2\`.scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).groove(\`[~ x]*2\`)
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass(\`<1 0>/16\`),
\`<C2 Bb1 Ab1 [G1 [G2 G1]]>/2\`.groove(\`x [~ x] <[~ [~ x]]!3 [x x]>@2\`).edit(thru).tone(bass),
\`<Cm7 Bb7 Fm7 G7b13>/2\`.groove(\`~ [x@0.5 ~]\`.fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass(\`<0@7 1>/8\`.early(1/4)),
)
export const callcenterhero = `()=>{
const bpm = 90;
const lead = polysynth().set({...osc('sine4'),...adsr(.004)}).chain(vol(0.15),out)
const bass = fmsynth({...osc('sawtooth6'),...adsr(0.05,.6,0.8,0.1)}).chain(vol(0.6), out);
const s = scale(slowcat('F3 minor', 'Ab3 major', 'Bb3 dorian', 'C4 phrygian dominant').slow(4));
return stack(
drums.fast(2),
synths
).slow(2);
}`;
export const caveravefuture2 = `const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
"<eb4 d4 c4 b3>/2".scale(timeCat([3, 'C minor'], [1, 'C melodic minor']).slow(8)).groove("[~ x]*2")
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass("<1 0>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".groove("x [~ x] <[~ [~ x]]!3 [x x]>@2").edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".groove("~ [x@0.5 ~]".fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass("<0@7 1>/8".early(1/4)),
)
$: stack(
drums.fast(2),
synths
).slow(2);
"0 2".struct("<x ~> [x ~]").edit(s).scaleTranspose(stack(0,2)).tone(lead),
"<6 7 9 7>".struct("[~ [x ~]*2]*2").edit(s).scaleTranspose("[0,2] [2,4]".fast(2).every(4,rev)).tone(lead),
"-14".struct("[~ x@0.8]*2".early(0.01)).edit(s).tone(bass),
"c2*2".tone(membrane().chain(vol(0.6), out)),
"~ c2".tone(noise().chain(vol(0.2), out)),
"c4*4".tone(metal(adsr(0,.05,0)).chain(vol(0.03), out))
)
.slow(120 / bpm)
}
`;
+77 -32
View File
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import * as Tone from 'tone';
import useRepl from '../useRepl';
import CodeMirror from '../CodeMirror';
import CodeMirror, { markEvent } from '../CodeMirror';
import cx from '../cx';
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({
@@ -11,42 +11,87 @@ const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destina
},
});
function MiniRepl({ tune, height = 100 }) {
const { code, setCode, activateCode, activeCode, setPattern, error, cycle, dirty, log, togglePlay } = useRepl({
function MiniRepl({ tune, maxHeight = 500 }) {
const [editor, setEditor] = useState<any>();
const { code, setCode, activateCode, activeCode, setPattern, error, cycle, dirty, log, togglePlay, hash } = useRepl({
tune,
defaultSynth,
autolink: false,
onEvent: useCallback(markEvent(editor), [editor]),
});
const lines = code.split('\n').length;
const height = Math.min(lines * 30 + 30, maxHeight);
return (
<div className="flex space-y-0 overflow-auto" style={{ height }}>
<div className="w-16 flex flex-col">
<button
className="grow bg-slate-700 border-b border-slate-500 text-white hover:bg-slate-600 "
onClick={() => togglePlay()}
>
{cycle.started ? 'pause' : 'play'}
</button>
<button
className={cx(
'grow border-slate-500 hover:bg-slate-600',
activeCode && dirty ? 'bg-slate-700 text-white' : 'bg-slate-600 text-slate-400 cursor-not-allowed'
)}
onClick={() => activateCode()}
>
update
</button>
<div className="rounded-md overflow-hidden">
<div className="flex justify-between bg-slate-700 border-t border-slate-500">
<div className="flex">
<button
className={cx(
'w-16 flex items-center justify-center p-1 bg-slate-700 border-r border-slate-500 text-white hover:bg-slate-600',
cycle.started ? 'animate-pulse' : ''
)}
onClick={() => togglePlay()}
>
{!cycle.started ? (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
clipRule="evenodd"
/>
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
)}
</button>
<button
className={cx(
'w-16 flex items-center justify-center p-1 border-slate-500 hover:bg-slate-600',
dirty
? 'bg-slate-700 border-r border-slate-500 text-white'
: 'bg-slate-600 text-slate-400 cursor-not-allowed'
)}
onClick={() => activateCode()}
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
<div className="text-right p-1 text-sm">{error && <span className="text-red-200">{error.message}</span>}</div>{' '}
</div>
<CodeMirror
className="w-full"
value={code}
options={{
mode: 'javascript',
theme: 'material',
lineNumbers: true,
}}
onChange={(_: any, __: any, value: any) => setCode(value)}
/>
{/* <textarea className="w-full" value={code} onChange={(e) => setCode(e.target.value)} /> */}
<div className="flex space-y-0 overflow-auto" style={{ height }}>
<CodeMirror
className="w-full"
value={code}
editorDidMount={setEditor}
options={{
mode: 'javascript',
theme: 'material',
lineNumbers: true,
}}
onChange={(_: any, __: any, value: any) => setCode(value)}
/>
</div>
{/* <div className="bg-slate-700 border-t border-slate-500 content-right pr-2 text-right">
<a href={`https://strudel.tidalcycles.org/#${hash}`} className="text-white items-center inline-flex">
<span>open in REPL</span>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z" />
<path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z" />
</svg>
</a>
</div> */}
</div>
);
}
+42 -47
View File
@@ -25,29 +25,28 @@ To get a taste of what Strudel can do, check out this track:
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
'c1*2'.m.tone(kick).bypass('<0@7 1>/8'.m),
'~ <x!7 [x@3 x]>'.m.tone(snare).bypass('<0@7 1>/4'.m),
'[~ c4]*2'.m.tone(hihat)
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose('<0 1>/8'.m).transpose(-1);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
'<eb4 d4 c4 b3>/2'.m.scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).groove('[~ x]*2'.m)
"<eb4 d4 c4 b3>/2".scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).struct("[~ x]*2")
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass('<1 0>/16'.m),
'<C2 Bb1 Ab1 [G1 [G2 G1]]>/2'.m.groove('[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2'.m.fast(2)).edit(thru).tone(bass),
'<Cm7 Bb7 Fm7 G7b13>/2'.m.groove('~ [x@0.1 ~]'.m.fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass('<0@7 1>/8'.m.early(1/4))
).edit(thru).tone(keys).bypass("<1 0>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2)).edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.1 ~]".fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass("<0@7 1>/8".early(1/4))
)
return stack(
drums.fast(2),
synths
).slow(2);
}`}
height={400}
/>
[Open this track in the REPL](https://strudel.tidalcycles.org/#KCkgPT4gewogIGNvbnN0IGRlbGF5ID0gbmV3IEZlZWRiYWNrRGVsYXkoMS84LCAuNCkuY2hhaW4odm9sKDAuNSksIG91dCk7CiAgY29uc3Qga2ljayA9IG5ldyBNZW1icmFuZVN5bnRoKCkuY2hhaW4odm9sKC44KSwgb3V0KTsKICBjb25zdCBzbmFyZSA9IG5ldyBOb2lzZVN5bnRoKCkuY2hhaW4odm9sKC44KSwgb3V0KTsKICBjb25zdCBoaWhhdCA9IG5ldyBNZXRhbFN5bnRoKCkuc2V0KGFkc3IoMCwgLjA4LCAwLCAuMSkpLmNoYWluKHZvbCguMykuY29ubmVjdChkZWxheSksb3V0KTsKICBjb25zdCBiYXNzID0gbmV3IFN5bnRoKCkuc2V0KHsgLi4ub3NjKCdzYXd0b290aCcpLCAuLi5hZHNyKDAsIC4xLCAuNCkgfSkuY2hhaW4obG93cGFzcyg5MDApLCB2b2woLjUpLCBvdXQpOwogIGNvbnN0IGtleXMgPSBuZXcgUG9seVN5bnRoKCkuc2V0KHsgLi4ub3NjKCdzYXd0b290aCcpLCAuLi5hZHNyKDAsIC41LCAuMiwgLjcpIH0pLmNoYWluKGxvd3Bhc3MoMTIwMCksIHZvbCguNSksIG91dCk7CiAgCiAgY29uc3QgZHJ1bXMgPSBzdGFjaygKICAgICdjMSoyJy5tLnRvbmUoa2ljaykuYnlwYXNzKCc8MEA3IDE%2BLzgnLm0pLAogICAgJ34gPHghNyBbeEAzIHhdPicubS50b25lKHNuYXJlKS5ieXBhc3MoJzwwQDcgMT4vNCcubSksCiAgICAnW34gYzRdKjInLm0udG9uZShoaWhhdCkKICApOwogIAogIGNvbnN0IHRocnUgPSAoeCkgPT4geC50cmFuc3Bvc2UoJzwwIDE%2BLzgnLm0pLnRyYW5zcG9zZSgtMSk7CiAgY29uc3Qgc3ludGhzID0gc3RhY2soCiAgICAnPGViNCBkNCBjNCBiMz4vMicubS5zY2FsZSh0aW1lQ2F0KFszLCdDIG1pbm9yJ10sWzEsJ0MgbWVsb2RpYyBtaW5vciddKS5zbG93KDgpKS5ncm9vdmUoJ1t%2BIHhdKjInLm0pCiAgICAuZWRpdCgKICAgICAgc2NhbGVUcmFuc3Bvc2UoMCkuZWFybHkoMCksCiAgICAgIHNjYWxlVHJhbnNwb3NlKDIpLmVhcmx5KDEvOCksCiAgICAgIHNjYWxlVHJhbnNwb3NlKDcpLmVhcmx5KDEvNCksCiAgICAgIHNjYWxlVHJhbnNwb3NlKDgpLmVhcmx5KDMvOCkKICAgICkuZWRpdCh0aHJ1KS50b25lKGtleXMpLmJ5cGFzcygnPDEgMD4vMTYnLm0pLAogICAgJzxDMiBCYjEgQWIxIFtHMSBbRzIgRzFdXT4vMicubS5ncm9vdmUoJ1t4IFt%2BIHhdIDxbfiBbfiB4XV0hMyBbeCB4XT5AMl0vMicubS5mYXN0KDIpKS5lZGl0KHRocnUpLnRvbmUoYmFzcyksCiAgICAnPENtNyBCYjcgRm03IEc3YjEzPi8yJy5tLmdyb292ZSgnfiBbeEAwLjEgfl0nLm0uZmFzdCgyKSkudm9pY2luZ3MoKS5lZGl0KHRocnUpLmV2ZXJ5KDIsIGVhcmx5KDEvOCkpLnRvbmUoa2V5cykuYnlwYXNzKCc8MEA3IDE%2BLzgnLm0uZWFybHkoMS80KSkKICApCiAgcmV0dXJuIHN0YWNrKAogICAgZHJ1bXMuZmFzdCgyKSwgCiAgICBzeW50aHMKICApLnNsb3coMik7Cn0%3D)
@@ -86,7 +85,6 @@ Before diving deeper into the details, here is a flavor of how the mini language
[[a1 a2]*4]
]
]/16\``}
height={600}
/>
The snippet above is enclosed in backticks (`), which allows you to write multi-line strings.
@@ -263,7 +261,6 @@ You can nest functions inside one another:
stack(b3,d3,fs4),
stack(b3,e4,g4)
)`}
height={200}
/>
The above is equivalent to
@@ -324,7 +321,7 @@ Note that we have to wrap b4 in **pure** to be able to call a the pattern modifi
There is the shorthand **p** for this:
<MiniRepl tune={`cat(e5, b4.p.late(0.5))`} />
<MiniRepl tune={`cat(e5, b4.late(0.5))`} />
### late(cycles)
@@ -344,11 +341,11 @@ Will reverse the pattern:
Will apply the given function every n cycles:
<MiniRepl tune={`cat(e5, b4.p.every(4, late(0.5)))`} />
<MiniRepl tune={`cat(e5, pure(b4).every(4, late(0.5)))`} />
Note that late is called directly. This is a shortcut for:
<MiniRepl tune={`cat(e5, b4.p.every(4, x => x.late(0.5)))`} />
<MiniRepl tune={`cat(e5, pure(b4).every(4, x => x.late(0.5)))`} />
TODO: should the function really run the first cycle?
@@ -367,7 +364,7 @@ TODO: should the function really run the first cycle?
- append
- superimpose
- internal Pattern functions?
- groove, TODO move to core from https://github.com/tidalcycles/strudel/blob/main/repl/src/groove.ts
- struct
## Tone API
@@ -377,15 +374,14 @@ To make the sounds more interesting, we can use Tone.js instruments ands effects
<MiniRepl
tune={`stack(
"[c5 c5 bb4 c5] [~ g4 ~ g4] [c5 f5 e5 c5] ~".m
"[c5 c5 bb4 c5] [~ g4 ~ g4] [c5 f5 e5 c5] ~"
.tone(synth(adsr(0,.1,0,0)).chain(out)),
"[c2 c3]*8".m
"[c2 c3]*8"
.tone(synth({
...osc('sawtooth'),
...adsr(0,.1,0.4,0)
}).chain(lowpass(300), out))
).slow(4)`}
height={300}
/>
### tone(instrument)
@@ -393,7 +389,7 @@ To make the sounds more interesting, we can use Tone.js instruments ands effects
To change the instrument of a pattern, you can pass any [Tone.js Source](https://tonejs.github.io/docs/14.7.77/index.html) to .tone:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".m.slow(4)
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(new FMSynth().toDestination())`}
/>
@@ -418,7 +414,7 @@ const synth = (options) => new Synth(options);
Shortcut for Tone.Destination. Intended to be used with Tone's .chain:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".m.slow(4)
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(membrane().chain(out))`}
/>
@@ -429,7 +425,7 @@ This alone is not really useful, so read on..
Helper that returns a Gain Node with the given volume. Intended to be used with Tone's .chain:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".m.slow(4)
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(noise().chain(vol(0.5), out))`}
/>
@@ -438,7 +434,7 @@ Helper that returns a Gain Node with the given volume. Intended to be used with
Helper to set the waveform of a synth, monosynth or polysynth:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".m.slow(4)
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(synth(osc('sawtooth4')).chain(out))`}
/>
@@ -449,7 +445,7 @@ The base types are `sine`, `square`, `sawtooth`, `triangle`. You can also append
Helper that returns a Filter Node of type lowpass with the given cutoff. Intended to be used with Tone's .chain:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".m.slow(4)
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(synth(osc('sawtooth')).chain(lowpass(800), out))`}
/>
@@ -458,7 +454,7 @@ Helper that returns a Filter Node of type lowpass with the given cutoff. Intende
Helper that returns a Filter Node of type highpass with the given cutoff. Intended to be used with Tone's .chain:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".m.slow(4)
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(synth(osc('sawtooth')).chain(highpass(2000), out))`}
/>
@@ -467,7 +463,7 @@ Helper that returns a Filter Node of type highpass with the given cutoff. Intend
Helper to set the envelope of a Tone.js instrument. Intended to be used with Tone's .set:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".m.slow(4)
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".slow(4)
.tone(synth(adsr(0,.1,0,0)).chain(out))`}
/>
@@ -482,8 +478,8 @@ It would be great to get this to work without glitches though, because it is fun
With .synth, you can create a synth with a variable wave type:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".m.
synth('<sawtooth8 square8>'.m).slow(4)`}
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~"
.synth("<sawtooth8 square8>").slow(4)`}
/>
#### adsr(attack, decay?, sustain?, release?)
@@ -491,8 +487,8 @@ synth('<sawtooth8 square8>'.m).slow(4)`}
Chainable Envelope helper:
<MiniRepl
tune={`"[c5 c5 bb4 c5] [~ g4 ~ g4] [c5 f5 e5 c5] ~".m.slow(4).
synth('sawtooth16').adsr(0,.1,0,0)`}
tune={`"[c5 c5 bb4 c5] [~ g4 ~ g4] [c5 f5 e5 c5] ~".slow(4)
.synth('sawtooth16').adsr(0,.1,0,0)`}
/>
Due to having more than one argument, this method is not patternified.
@@ -502,8 +498,8 @@ Due to having more than one argument, this method is not patternified.
Patternified filter:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".m.
synth('sawtooth16').filter('[500 2000]*8'.m).slow(4)`}
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~"
.synth('sawtooth16').filter("[500 2000]*8").slow(4)`}
/>
#### gain(value)
@@ -511,8 +507,8 @@ synth('sawtooth16').filter('[500 2000]*8'.m).slow(4)`}
Patternified gain:
<MiniRepl
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~".m.
synth('sawtooth16').gain('[.2 .8]*8'.m).slow(4)`}
tune={`"[c4 c4 bb3 c4] [~ g3 ~ g3] [c4 f4 e4 c4] ~"
.synth('sawtooth16').gain("[.2 .8]*8").slow(4)`}
/>
#### autofilter(value)
@@ -520,8 +516,8 @@ synth('sawtooth16').gain('[.2 .8]*8'.m).slow(4)`}
Patternified autofilter:
<MiniRepl
tune={`"c2 c3".m.
synth('sawtooth16').autofilter('<1 4 8>'.m)`}
tune={`"c2 c3"
.synth('sawtooth16').autofilter("<1 4 8>"")`}
/>
## Tonal API
@@ -532,20 +528,20 @@ The Tonal API, uses [tonaljs](https://github.com/tonaljs/tonal) to provide helpe
Transposes all notes to the given number of semitones:
<MiniRepl tune={`"c2 c3".m.fast(2).transpose('<0 -2 5 3>'.m.slow(2)).transpose(0)`} />
<MiniRepl tune={`"c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).transpose(0)`} />
This method gets really exciting when we use it with a pattern as above.
Instead of numbers, scientific interval notation can be used as well:
<MiniRepl tune={`"c2 c3".m.fast(2).transpose('<1P -2M 4P 3m>'.m.slow(2)).transpose(1)`} />
<MiniRepl tune={`"c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).transpose(1)`} />
### scale(name)
Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like scaleTranpose.
<MiniRepl
tune={`"0 2 4 6 4 2".m
tune={`"0 2 4 6 4 2"
.scale(slowcat('C2 major', 'C2 minor').slow(2))`}
/>
@@ -558,16 +554,16 @@ All the available scale names can be found [here](https://github.com/tonaljs/ton
Transposes notes inside the scale by the number of steps:
<MiniRepl
tune={`"-8 [2,4,6]".m
tune={`"-8 [2,4,6]"
.scale('C4 bebop major')
.scaleTranspose('<0 -1 -2 -3 -4 -5 -6 -4>'.m)`}
.scaleTranspose("<0 -1 -2 -3 -4 -5 -6 -4>")`}
/>
### voicings(range?)
Turns chord symbols into voicings, using the smoothest voice leading possible:
<MiniRepl tune={`stack("<C^7 A7 Dm7 G7>".m.voicings(), '<C3 A2 D3 G2>'.m)`} />
<MiniRepl tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>")`} />
TODO: use voicing collection as first param + patternify.
@@ -575,16 +571,15 @@ TODO: use voicing collection as first param + patternify.
Turns chord symbols into root notes of chords in given octave.
<MiniRepl tune={`"<C^7 A7b13 Dm7 G7>".m.rootNotes(3)`} />
<MiniRepl tune={`"<C^7 A7b13 Dm7 G7>".rootNotes(3)`} />
Together with edit, groove and voicings, this can be used to create a basic backing track:
Together with edit, struct and voicings, this can be used to create a basic backing track:
<MiniRepl
tune={`"<C^7 A7b13 Dm7 G7>".m.edit(
x => x.voicings(['d3','g4']).groove('~ x'.m),
tune={`"<C^7 A7b13 Dm7 G7>".edit(
x => x.voicings(['d3','g4']).struct("~ x"),
x => x.rootNotes(2).tone(synth(osc('sawtooth4')).chain(out))
)`}
height={150}
/>
TODO: use range instead of octave.
@@ -604,7 +599,7 @@ Midi is currently not supported by the mini repl used here, but you can [open th
In the REPL, you will se a log of the available MIDI devices.
<!--<MiniRepl
tune={`stack("<C^7 A7 Dm7 G7>".m.voicings(), '<C3 A2 D3 G2>'.m)
tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>")
.midi()`}
/>-->
-1
View File
@@ -3,7 +3,6 @@ import type { ToneEventCallback } from 'tone';
import * as Tone from 'tone';
import { TimeSpan } from '../../strudel.mjs';
import type { Hap } from './types';
import usePostMessage from './usePostMessage';
export declare interface UseCycleProps {
onEvent: ToneEventCallback<any>;
+36 -26
View File
@@ -1,7 +1,6 @@
import { useCallback, useLayoutEffect, useState, useMemo, useEffect } from 'react';
import { useCallback, useState, useMemo } from 'react';
import { isNote } from 'tone';
import { evaluate } from './evaluate';
import { useWebMidi } from './midi';
import type { Pattern } from './types';
import useCycle from './useCycle';
import usePostMessage from './usePostMessage';
@@ -12,14 +11,16 @@ let s4 = () => {
.substring(1);
};
function useRepl({ tune, defaultSynth, autolink = true }) {
function useRepl({ tune, defaultSynth, autolink = true, onEvent }: any) {
const id = useMemo(() => s4(), []);
const [code, setCode] = useState<string>(tune);
const [activeCode, setActiveCode] = useState<string>();
const [log, setLog] = useState('');
const [error, setError] = useState<Error>();
const [hash, setHash] = useState('');
const [pattern, setPattern] = useState<Pattern>();
const dirty = code !== activeCode;
const dirty = code !== activeCode || error;
const generateHash = () => encodeURIComponent(btoa(code));
const activateCode = (_code = code) => {
!cycle.started && cycle.start();
broadcast({ type: 'start', from: id });
@@ -33,9 +34,12 @@ function useRepl({ tune, defaultSynth, autolink = true }) {
if (autolink) {
window.location.hash = '#' + encodeURIComponent(btoa(code));
}
setHash(generateHash());
setError(undefined);
setActiveCode(_code);
} catch (err: any) {
err.message = 'evaluation error: ' + err.message;
console.warn(err);
setError(err);
}
};
@@ -48,35 +52,40 @@ function useRepl({ tune, defaultSynth, autolink = true }) {
};
// cycle hook to control scheduling
const cycle = useCycle({
onEvent: useCallback((time, event) => {
try {
if (!event.value?.onTrigger) {
const note = event.value?.value || event.value;
if (!isNote(note)) {
throw new Error('not a note: ' + note);
}
if (defaultSynth) {
defaultSynth.triggerAttackRelease(note, event.duration, time);
} else {
throw new Error('no defaultSynth passed to useRepl.');
}
/* console.warn('no instrument chosen', event);
onEvent: useCallback(
(time, event) => {
try {
onEvent?.(event);
if (!event.value?.onTrigger) {
const note = event.value?.value || event.value;
if (!isNote(note)) {
throw new Error('not a note: ' + note);
}
if (defaultSynth) {
defaultSynth.triggerAttackRelease(note, event.duration, time);
} else {
throw new Error('no defaultSynth passed to useRepl.');
}
/* console.warn('no instrument chosen', event);
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
} else {
const { onTrigger } = event.value;
onTrigger(time, event);
} else {
const { onTrigger } = event.value;
onTrigger(time, event);
}
} catch (err: any) {
console.warn(err);
err.message = 'unplayable event: ' + err?.message;
pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event
}
} catch (err: any) {
console.warn(err);
err.message = 'unplayable event: ' + err?.message;
pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event
}
}, []),
},
[onEvent]
),
onQuery: useCallback(
(span) => {
try {
return pattern?.query(span) || [];
} catch (err: any) {
err.message = 'query error: ' + err.message;
setError(err);
return [];
}
@@ -147,6 +156,7 @@ function useRepl({ tune, defaultSynth, autolink = true }) {
activateCode,
activeCode,
pushLog,
hash,
};
}
+1 -1
View File
@@ -32,7 +32,7 @@ Pattern.prototype.voicings = function (range) {
range = ['F3', 'A4'];
}
return this.fmapNested((event) => {
lastVoicing = getVoicing(event.value, lastVoicing, range);
lastVoicing = getVoicing(event.value?.value || event.value, lastVoicing, range);
return stack(...lastVoicing);
});
};
+70 -2
View File
@@ -148,7 +148,7 @@ class TimeSpan {
return result
}
get midpoint() {
midpoint() {
return(this.begin.add((this.end.sub(this.begin)).div(Fraction(2))))
}
@@ -298,6 +298,14 @@ class Pattern {
return new Pattern(state => func(this.query(state)))
}
withLocation(location) {
return this.fmap(value => {
value = typeof value === 'object' && !Array.isArray(value) ? value : { value };
const locations = (value.locations || []).concat([location]);
return {...value, locations }
})
}
withValue(func) {
// Returns a new pattern, with the function applied to the value of
// each event. It has the alias 'fmap'.
@@ -478,7 +486,14 @@ class Pattern {
_patternify(func) {
const pat = this
const patterned = function (...args) {
// the problem here: args could a pattern that has been turned into an object to add location
// to avoid object checking for every pattern method, we can remove it here...
// in the future, patternified args should be marked as well + some better object handling
args = args.map((arg) =>
arg.constructor?.name === 'Pattern' ? arg.fmap((value) => value.value || value) : arg
);
const pat_arg = sequence(...args)
// arg.locations has to go somewhere..
return pat_arg.fmap(arg => func.call(pat,arg)).outerJoin()
}
return patterned
@@ -629,6 +644,9 @@ class Pattern {
edit(...funcs) {
return stack(...funcs.map(func => func(this)));
}
pipe(func) {
return func(this);
}
_bypass(on) {
on = Boolean(parseInt(on));
@@ -680,6 +698,32 @@ function steady(value) {
return new Pattern(span => Hap(undefined, span, value))
}
export const signal = func => {
const query = span => [new Hap(undefined, span, func(span.midpoint()))]
return new Pattern(query)
}
const _toBipolar = pat => pat.fmap(x => (x * 2) - 1)
const _fromBipolar = pat => pat.fmap(x => (x + 1) / 2)
export const sine2 = signal(t => Math.sin(Math.PI * 2 * t))
export const sine = _fromBipolar(sine2)
export const cosine2 = sine2._early(0.25)
export const cosine = sine._early(0.25)
export const saw = signal(t => t % 1)
export const saw2 = _toBipolar(saw)
export const isaw = signal(t => 1 - (t % 1))
export const isaw2 = _toBipolar(isaw)
export const tri2 = fastcat(isaw2, saw2)
export const tri = fastcat(isaw, saw)
export const square = signal(t => Math.floor((t*2) % 2))
export const square2 = _toBipolar(square)
function reify(thing) {
// Tunrs something into a pattern, unless it's already a pattern
if (thing?.constructor?.name == "Pattern") {
@@ -882,10 +926,34 @@ Pattern.prototype.bootstrap = () => {
return bootstrapped;
}
// this is wrapped around mini patterns to offset krill parser location into the global js code space
function withLocationOffset(pat, offset) {
return pat.fmap((value) => {
value = typeof value === 'object' && !Array.isArray(value) ? value : { value };
let locations = (value.locations || []);
locations = locations.map(({ start, end }) => {
const colOffset = start.line === 1 ? offset.start.column : 0;
return {
start: {
...start,
line: start.line - 1 + (offset.start.line - 1) + 1,
column: start.column - 1 + colOffset,
},
end: {
...end,
line: end.line - 1 + (offset.start.line - 1) + 1,
column: end.column - 1 + colOffset,
},
}});
return {...value, locations }
});
}
export {Fraction, TimeSpan, Hap, Pattern,
pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr, reify, silence,
fast, slow, early, late, rev,
add, sub, mul, div, union, every, when, off, jux, append, superimpose,
struct, mask, invert, inv
struct, mask, invert, inv,
withLocationOffset
}
+23 -1
View File
@@ -2,7 +2,7 @@ import Fraction from 'fraction.js'
import { strict as assert } from 'assert';
import {TimeSpan, Hap, Pattern, pure, stack, fastcat, slowcat, cat, sequence, polyrhythm, silence, fast, timeCat,add,sub,mul,div, State} from "../strudel.mjs";
import {TimeSpan, Hap, State, Pattern, pure, stack, fastcat, slowcat, cat, sequence, polyrhythm, silence, fast, timeCat,add,sub,mul,div,saw,saw2,isaw,isaw2,sine,sine2,square,square2,tri,tri2} from "../strudel.mjs";
//import { Time } from 'tone';
import pkg from 'tone';
const { Time } = pkg;
@@ -308,4 +308,26 @@ describe('Pattern', function() {
)
})
})
describe('signal()', function() {
it('Can make saw/saw2', function() {
assert.deepStrictEqual(
saw.struct(true,true,true,true).firstCycle,
sequence(1/8,3/8,5/8,7/8).firstCycle
)
assert.deepStrictEqual(
saw2.struct(true,true,true,true).firstCycle,
sequence(-3/4,-1/4,1/4,3/4).firstCycle
)
})
it('Can make isaw/isaw2', function() {
assert.deepStrictEqual(
isaw.struct(true,true,true,true).firstCycle,
sequence(7/8,5/8,3/8,1/8).firstCycle
)
assert.deepStrictEqual(
isaw2.struct(true,true,true,true).firstCycle,
sequence(3/4,1/4,-1/4,-3/4).firstCycle
)
})
})
})