Merge branch 'main' into stateful-events

This commit is contained in:
Alex McLean 2022-02-23 20:20:35 +00:00 committed by GitHub
commit 0418517c3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 2275 additions and 916 deletions

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)

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
};

13
docs/dist/App.js vendored
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", {

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);
};

13
docs/dist/evaluate.js vendored
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
docs/dist/groove.js vendored
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});

12
docs/dist/parse.js vendored
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":

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,
},
},
],
},
},
],
};
}

11
docs/dist/tonal.js vendored
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};
});

397
docs/dist/tunes.js vendored
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)
}
`;

16
docs/dist/useRepl.js vendored
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;

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);
});
};

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 one or more lines are too long

View File

@ -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

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>

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)

View File

@ -24,6 +24,7 @@
this.type_ = "element";
this.source_ = source;
this.options_ = options;
this.location_ = location();
}
var CommandStub = function(name, options)

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)}
/>

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);
};

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 };
};

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 });

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':

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,
},
},
],
},
},
],
};
}

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 };
});

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)
}
`;

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>
);
}

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()`}
/>-->

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>;

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,
};
}

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);
});
};

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
}

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
)
})
})
})