docs docs docs
This commit is contained in:
parent
a05757f8d6
commit
5d649a516f
7 changed files with 516 additions and 34 deletions
|
|
@ -1,9 +1,10 @@
|
||||||
import React, { useLayoutEffect, useRef } from 'react';
|
import React, { useCallback, useLayoutEffect, useRef } from 'react';
|
||||||
import * as Tone from 'tone';
|
import * as Tone from 'tone';
|
||||||
import CodeMirror from './CodeMirror';
|
import CodeMirror from './CodeMirror';
|
||||||
import cx from './cx';
|
import cx from './cx';
|
||||||
import { evaluate } from './evaluate';
|
import { evaluate } from './evaluate';
|
||||||
import logo from './logo.svg';
|
import logo from './logo.svg';
|
||||||
|
import { useWebMidi } from './midi';
|
||||||
import * as tunes from './tunes';
|
import * as tunes from './tunes';
|
||||||
import useRepl from './useRepl';
|
import useRepl from './useRepl';
|
||||||
|
|
||||||
|
|
@ -34,7 +35,7 @@ function getRandomTune() {
|
||||||
const randomTune = getRandomTune();
|
const randomTune = getRandomTune();
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { setCode, setPattern, error, code, cycle, dirty, log, togglePlay } = useRepl({
|
const { setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog } = useRepl({
|
||||||
tune: decoded || randomTune,
|
tune: decoded || randomTune,
|
||||||
defaultSynth,
|
defaultSynth,
|
||||||
});
|
});
|
||||||
|
|
@ -44,6 +45,37 @@ function App() {
|
||||||
logBox.current.scrollTop = logBox.current?.scrollHeight;
|
logBox.current.scrollTop = logBox.current?.scrollHeight;
|
||||||
}, [log]);
|
}, [log]);
|
||||||
|
|
||||||
|
// set active pattern on ctrl+enter
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
// TODO: make sure this is only fired when editor has focus
|
||||||
|
const handleKeyPress = (e: any) => {
|
||||||
|
if (e.ctrlKey || e.altKey) {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'Enter':
|
||||||
|
activateCode();
|
||||||
|
!cycle.started && cycle.start();
|
||||||
|
break;
|
||||||
|
case 'Period':
|
||||||
|
cycle.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('keypress', handleKeyPress);
|
||||||
|
return () => document.removeEventListener('keypress', handleKeyPress);
|
||||||
|
}, [pattern, code]);
|
||||||
|
|
||||||
|
useWebMidi({
|
||||||
|
ready: useCallback(({ outputs }) => {
|
||||||
|
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `"${o.name}"`).join(' | ')}) to the pattern. `);
|
||||||
|
}, []),
|
||||||
|
connected: useCallback(({ outputs }) => {
|
||||||
|
pushLog(`Midi device connected! Available: ${outputs.map((o) => `"${o.name}"`).join(', ')}`);
|
||||||
|
}, []),
|
||||||
|
disconnected: useCallback(({ outputs }) => {
|
||||||
|
pushLog(`Midi device disconnected! Available: ${outputs.map((o) => `"${o.name}"`).join(', ')}`);
|
||||||
|
}, []),
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#2A3236] flex flex-col">
|
<div className="min-h-screen bg-[#2A3236] flex flex-col">
|
||||||
<header className="flex-none w-full h-16 px-2 flex border-b border-gray-200 bg-white justify-between">
|
<header className="flex-none w-full h-16 px-2 flex border-b border-gray-200 bg-white justify-between">
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,60 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import * as Tone from 'tone';
|
import * as Tone from 'tone';
|
||||||
import useRepl from '../useRepl';
|
import useRepl from '../useRepl';
|
||||||
|
import CodeMirror from '../CodeMirror';
|
||||||
|
import cx from '../cx';
|
||||||
|
|
||||||
function MiniRepl({ tune }) {
|
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({
|
||||||
const defaultSynth = useMemo(() => {
|
oscillator: { type: 'triangle' },
|
||||||
|
envelope: {
|
||||||
|
release: 0.01,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function MiniRepl({ tune, height = 100 }) {
|
||||||
|
/* const defaultSynth = useMemo(() => {
|
||||||
return new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({
|
return new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({
|
||||||
oscillator: { type: 'triangle' },
|
oscillator: { type: 'triangle' },
|
||||||
envelope: {
|
envelope: {
|
||||||
release: 0.01,
|
release: 0.01,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, []);
|
}, []); */
|
||||||
const { code, setCode, setPattern, error, cycle, dirty, log, togglePlay } = useRepl({
|
const { code, setCode, activateCode, activeCode, setPattern, error, cycle, dirty, log, togglePlay } = useRepl({
|
||||||
tune,
|
tune,
|
||||||
defaultSynth,
|
defaultSynth,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex space-y-0 overflow-auto" style={{ height }}>
|
||||||
<textarea value={code} onChange={(e) => setCode(e.target.value)} />
|
<div className="w-16 flex flex-col">
|
||||||
<button onClick={() => togglePlay()}>{cycle.started ? 'pause' : 'play'}</button>
|
<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>
|
||||||
|
<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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,25 @@ import logo from '../logo.svg';
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<div className="min-h-screen flex flex-col">
|
<div className="min-h-screen">
|
||||||
<header className="flex-none w-full h-16 px-2 flex items-center border-b border-gray-200 bg-white justify-between">
|
<header className="flex-none flex justify-center w-full h-16 px-2 items-center border-b border-gray-200 bg-white">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="p-4 w-full max-w-3xl flex justify-between">
|
||||||
<img src={logo} className="Tidal-logo w-16 h-16" alt="logo" />
|
<div className="flex items-center space-x-2">
|
||||||
<h1 className="text-2xl">Strudel Tutorial</h1>
|
<img src={logo} className="Tidal-logo w-16 h-16" alt="logo" />
|
||||||
</div>
|
<h1 className="text-2xl">Strudel Tutorial</h1>
|
||||||
{!window.location.href.includes('localhost') && (
|
|
||||||
<div className="flex space-x-4">
|
|
||||||
<a href="../">go to REPL</a>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{!window.location.href.includes('localhost') && (
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<a href="../">go to REPL</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<section className="prose p-4">
|
<div className="flex justify-center">
|
||||||
<Tutorial />
|
<main className="p-4 max-w-3xl prose">
|
||||||
</section>
|
<Tutorial />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,13 @@
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
.react-codemirror2,
|
||||||
|
.CodeMirror {
|
||||||
|
width: 100% !important;
|
||||||
|
height: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,402 @@
|
||||||
import MiniRepl from './MiniRepl';
|
import MiniRepl from './MiniRepl';
|
||||||
|
|
||||||
# About Strudel
|
# What is Strudel?
|
||||||
|
|
||||||
hello!!!!
|
With Strudel, you can expressively write dynamic music pieces.
|
||||||
|
It aims to be [Tidal Cycles](https://tidalcycles.org/) for JavaScript (started by the same author).
|
||||||
|
|
||||||
|
You don't need to know JavaScript or Tidal Cycles to make music with Strudel.
|
||||||
|
|
||||||
|
This interactive tutorial will guide you through the basics of Strudel.
|
||||||
|
|
||||||
|
The best place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/).
|
||||||
|
|
||||||
|
## Show me a Demo
|
||||||
|
|
||||||
|
To get a taste of what Strudel can do, check out this track:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
tune={`() => {
|
||||||
|
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'.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)
|
||||||
|
);
|
||||||
|
|
||||||
|
const thru = (x) => x.transpose('<0 1>/8'.m).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)
|
||||||
|
.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))
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
- This project is still in its experimental state. In the future, parts of it might change significantly.
|
||||||
|
- This tutorial is far from complete.
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
# Mini Notation
|
# Mini Notation
|
||||||
|
|
||||||
blablalba
|
Similar to Tidal Cycles, Strudel has an embedded mini language that is designed to write rhythmic patterns in a short manner.
|
||||||
|
Before diving deeper into the details, here is a flavor of how the mini language looks like:
|
||||||
|
|
||||||
<MiniRepl tune="C3.m" />
|
<MiniRepl
|
||||||
|
tune={`\`[
|
||||||
|
[
|
||||||
|
[e5 [b4 c5] d5 [c5 b4]]
|
||||||
|
[a4 [a4 c5] e5 [d5 c5]]
|
||||||
|
[b4 [~ c5] d5 e5]
|
||||||
|
[c5 a4 a4 ~]
|
||||||
|
[[~ d5] [~ f5] a5 [g5 f5]]
|
||||||
|
[e5 [~ c5] e5 [d5 c5]]
|
||||||
|
[b4 [b4 c5] d5 e5]
|
||||||
|
[c5 a4 a4 ~]
|
||||||
|
],[
|
||||||
|
[[e2 e3]*4]
|
||||||
|
[[a2 a3]*4]
|
||||||
|
[[g#2 g#3]*2 [e2 e3]*2]
|
||||||
|
[a2 a3 a2 a3 a2 a3 b1 c2]
|
||||||
|
[[d2 d3]*4]
|
||||||
|
[[c2 c3]*4]
|
||||||
|
[[b1 b2]*2 [e2 e3]*2]
|
||||||
|
[[a1 a2]*4]
|
||||||
|
]
|
||||||
|
]/16\``}
|
||||||
|
height={600}
|
||||||
|
/>
|
||||||
|
|
||||||
|
The snippet above is enclosed in backticks (`), which allows you to write multi-line strings.
|
||||||
|
You can also use double quotes (") for single line mini notation.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
<MiniRepl tune="'C3 G3'.m" />
|
Notes are notated with the note letter, followed by the octave number. You can notate flats with `b` and sharps with `#`.
|
||||||
|
|
||||||
|
<MiniRepl tune={`"e5"`} />
|
||||||
|
|
||||||
|
Here, the same note is played over and over again, once a second. This one second is the default length of one so called "cycle".
|
||||||
|
|
||||||
|
By the way, you can edit the contents of the player, and press "update" to hear your change!
|
||||||
|
You can also press "play" on the next player without needing to stop the last one.
|
||||||
|
|
||||||
|
## Sequences
|
||||||
|
|
||||||
|
We can play more notes by seperating them with spaces:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"e5 b4 d5 c5"`} />
|
||||||
|
|
||||||
|
Here, those four notes are squashed into one cycle, so each note is a quarter second long.
|
||||||
|
|
||||||
|
## Division
|
||||||
|
|
||||||
|
We can slow the sequence down by enclosing it in brackets and dividing it by a number:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"[e5 b4 d5 c5]/2"`} />
|
||||||
|
|
||||||
|
The division by two means that the sequence will be played over the course of two cycles.
|
||||||
|
You can also use decimal numbers for any tempo you like.
|
||||||
|
|
||||||
|
## Angle Brackets
|
||||||
|
|
||||||
|
Using angle brackets, we can define the sequence length based on the number of children:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"<e5 b4 d5 c5>"`} />
|
||||||
|
|
||||||
|
The above snippet is the same as:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"[e5 b4 d5 c5]/4"`} />
|
||||||
|
|
||||||
|
The advantage of the angle brackets, is that we can add more children without needing to change the number at the end.
|
||||||
|
|
||||||
|
## Multiplication
|
||||||
|
|
||||||
|
Contrary to division, a sequence can be sped up by multiplying it by a number:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"[e5 b4 d5 c5]*2"`} />
|
||||||
|
|
||||||
|
The multiplication by 2 here means that the sequence will play twice a cycle.
|
||||||
|
|
||||||
|
## Bracket Nesting
|
||||||
|
|
||||||
|
To create more interesting rhythms, you can nest sequences with brackets, like this:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"e5 [b4 c5] d5 [c5 b4]"`} />
|
||||||
|
|
||||||
|
## Rests
|
||||||
|
|
||||||
|
The "~" represents a rest:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"[b4 [~ c5] d5 e5]"`} />
|
||||||
|
|
||||||
|
## Parallel
|
||||||
|
|
||||||
|
Using commas, we can play chords:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"g3,b3,e4"`} />
|
||||||
|
|
||||||
|
To play multiple chords in a sequence, we have to wrap them in brackets:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>"`} />
|
||||||
|
|
||||||
|
## Elongation
|
||||||
|
|
||||||
|
With the "@" symbol, we can specify temporal "weight" of a sequence child:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>"`} />
|
||||||
|
|
||||||
|
Here, the first chord has a weight of 2, making it twice the length of the other chords. The default weight is 1.
|
||||||
|
|
||||||
|
## Replication
|
||||||
|
|
||||||
|
Using "!" we can repeat without speeding up:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>"`} />
|
||||||
|
|
||||||
|
In essence, the `x!n` is like a shortcut for `[x*n]@n`.
|
||||||
|
|
||||||
|
## Mini Notation TODO
|
||||||
|
|
||||||
|
Compared to [tidal mini notation](https://tidalcycles.org/docs/patternlib/tutorials/mini_notation/), the following mini notation features are missing from Strudel:
|
||||||
|
|
||||||
|
- Tie symbols "\_"
|
||||||
|
- Euclidean algorithm "c3(3,2,1)"
|
||||||
|
- feet marking "."
|
||||||
|
- random choice "|"
|
||||||
|
- Random removal "?"
|
||||||
|
- Polymetric sequences "{ ... }"
|
||||||
|
- Fixed steps using "%"
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
# Core API
|
||||||
|
|
||||||
|
While the mini notation is powerful on its own, there is much more to discover.
|
||||||
|
Internally, the mini notation will expand to use the actual functional JavaScript API.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Notes are automatically available as variables:
|
||||||
|
|
||||||
|
<MiniRepl tune={`e4`} />
|
||||||
|
|
||||||
|
An important difference to the mini notation:
|
||||||
|
For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name.
|
||||||
|
|
||||||
|
The above is the same as:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"e4"`} />
|
||||||
|
|
||||||
|
Using strings, you can also use "#".
|
||||||
|
|
||||||
|
## Functions that create Patterns
|
||||||
|
|
||||||
|
The following functions will return a pattern. We will see later what that means.
|
||||||
|
|
||||||
|
## pure(value)
|
||||||
|
|
||||||
|
To create a pattern from a value, you can wrap the value in pure:
|
||||||
|
|
||||||
|
<MiniRepl tune={`pure(e4)`} />
|
||||||
|
|
||||||
|
Most of the time, you won't need that function as input values of pattern creating functions are purified by default.
|
||||||
|
|
||||||
|
### cat(...values)
|
||||||
|
|
||||||
|
The given items are con**cat**enated spread evenly over one cycle:
|
||||||
|
|
||||||
|
<MiniRepl tune={`cat(e5, b4, d5, c5)`} />
|
||||||
|
|
||||||
|
The function **fastcat** does the same as **cat**.
|
||||||
|
|
||||||
|
### sequence(...values)
|
||||||
|
|
||||||
|
Like **cat**, but allows nesting with arrays:
|
||||||
|
|
||||||
|
<MiniRepl tune={`sequence(e5, [b4, c5], d5, [c5, b4])`} />
|
||||||
|
|
||||||
|
### stack(...values)
|
||||||
|
|
||||||
|
The given items are played at the same time at the same length:
|
||||||
|
|
||||||
|
<MiniRepl tune={`stack(g3,b3,e4)`} />
|
||||||
|
|
||||||
|
### slowcat(...values)
|
||||||
|
|
||||||
|
Like cat, but each item has the length of one cycle:
|
||||||
|
|
||||||
|
<MiniRepl tune={`slowcat(e5, b4, d5, c5)`} />
|
||||||
|
|
||||||
|
<!-- ## slowcatPrime ? -->
|
||||||
|
|
||||||
|
### Nesting functions
|
||||||
|
|
||||||
|
You can nest functions inside one another:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
tune={`slowcat(
|
||||||
|
stack(g3,b3,e4),
|
||||||
|
stack(a3,c3,e4),
|
||||||
|
stack(b3,d3,fs4),
|
||||||
|
stack(b3,e4,g4)
|
||||||
|
)`}
|
||||||
|
height={200}
|
||||||
|
/>
|
||||||
|
|
||||||
|
The above is equivalent to
|
||||||
|
|
||||||
|
<MiniRepl tune={`"<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>"`} />
|
||||||
|
|
||||||
|
### timeCat(...[weight,value])
|
||||||
|
|
||||||
|
Like with "@" in mini notation, we can specify weights to the items in a sequence:
|
||||||
|
|
||||||
|
<MiniRepl tune={`timeCat([3,e3],[1, g3])`} />
|
||||||
|
|
||||||
|
<!-- ## polymeter
|
||||||
|
|
||||||
|
how to use?
|
||||||
|
|
||||||
|
<MiniRepl tune={`polymeter(3, e3, g3, b3)`} /> -->
|
||||||
|
|
||||||
|
### polyrhythm(...[...values])
|
||||||
|
|
||||||
|
Plays the given items at the same time, within the same length:
|
||||||
|
|
||||||
|
<MiniRepl tune={`polyrhythm([e3, g3], [e4, g4, b4])`} />
|
||||||
|
|
||||||
|
We can write the same with **stack** and **cat**:
|
||||||
|
|
||||||
|
<MiniRepl tune={`stack(cat(e3, g3), cat(e4, g4, b4))`} />
|
||||||
|
|
||||||
|
You can also use the shorthand **pr** instead of **polyrhythm**.
|
||||||
|
|
||||||
|
## Pattern modifier functions
|
||||||
|
|
||||||
|
The following functions modify a pattern.
|
||||||
|
|
||||||
|
### slow(factor)
|
||||||
|
|
||||||
|
Like "/" in mini notation, **slow** will slow down a pattern over the given number of cycles:
|
||||||
|
|
||||||
|
<MiniRepl tune={`cat(e5, b4, d5, c5).slow(2)`} />
|
||||||
|
|
||||||
|
The same in mini notation:
|
||||||
|
|
||||||
|
<MiniRepl tune={`"[e5 b4 d5 c5]/2"`} />
|
||||||
|
|
||||||
|
### fast(factor)
|
||||||
|
|
||||||
|
Like "\*" in mini notation, **fast** will play a pattern times the given number in one cycle:
|
||||||
|
|
||||||
|
<MiniRepl tune={`cat(e5, b4, d5, c5).fast(2)`} />
|
||||||
|
|
||||||
|
### early(cycles)
|
||||||
|
|
||||||
|
With early, you can nudge a pattern to start earlier in time:
|
||||||
|
|
||||||
|
<MiniRepl tune={`cat(e5, pure(b4).late(0.5))`} />
|
||||||
|
|
||||||
|
Note that we have to wrap b4 in **pure** to be able to call a the pattern modifier **early**.
|
||||||
|
|
||||||
|
There is the shorthand **p** for this:
|
||||||
|
|
||||||
|
<MiniRepl tune={`cat(e5, b4.p.late(0.5))`} />
|
||||||
|
|
||||||
|
### late(cycles)
|
||||||
|
|
||||||
|
Like early, but in the other direction:
|
||||||
|
|
||||||
|
<MiniRepl tune={`cat(e5, b4.p.late(0.5))`} />
|
||||||
|
|
||||||
|
TODO: shouldn't it sound different?
|
||||||
|
|
||||||
|
### rev()
|
||||||
|
|
||||||
|
Will reverse the pattern:
|
||||||
|
|
||||||
|
<MiniRepl tune={`cat(c3,d3,e3,f3).rev()`} />
|
||||||
|
|
||||||
|
### every(n, func)
|
||||||
|
|
||||||
|
Will apply the given function every n cycles:
|
||||||
|
|
||||||
|
<MiniRepl tune={`cat(e5, b4.p.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)))`} />
|
||||||
|
|
||||||
|
TODO: should the function really run the first cycle?
|
||||||
|
|
||||||
|
### Functions not documented yet
|
||||||
|
|
||||||
|
- add
|
||||||
|
- sub
|
||||||
|
- sub
|
||||||
|
- mul
|
||||||
|
- div
|
||||||
|
- union
|
||||||
|
- every
|
||||||
|
- when
|
||||||
|
- off
|
||||||
|
- jux
|
||||||
|
- append
|
||||||
|
- superimpose
|
||||||
|
- internal Pattern functions?
|
||||||
|
- groove, TODO move to core from https://github.com/tidalcycles/strudel/blob/main/repl/src/groove.ts
|
||||||
|
|
||||||
|
## Tone API
|
||||||
|
|
||||||
|
TODO, see https://github.com/tidalcycles/strudel/blob/main/repl/src/tone.ts
|
||||||
|
|
||||||
|
## Tonal API
|
||||||
|
|
||||||
|
TODO, see
|
||||||
|
|
||||||
|
- https://github.com/tidalcycles/strudel/blob/main/repl/src/tonal.ts
|
||||||
|
- https://github.com/tidalcycles/strudel/blob/main/repl/src/voicings.ts
|
||||||
|
|
||||||
|
## MIDI API
|
||||||
|
|
||||||
|
TODO, see https://github.com/tidalcycles/strudel/blob/main/repl/src/midi.ts
|
||||||
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Contributions of any sort are very welcome! You can contribute by editing [this file](https://github.com/tidalcycles/strudel/blob/main/repl/src/tutorial/tutorial.mdx).
|
||||||
|
All you need is a github account.
|
||||||
|
|
||||||
|
If you want to run the tutorial locally, you can clone the and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd repl && npm i && npm run tutorial
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to contribute in another way, either
|
||||||
|
|
||||||
|
- [fork strudel repo on GitHub](https://github.com/tidalcycles/strudel)
|
||||||
|
- [Join the Discord Channel](https://discord.gg/remJ6gQA)
|
||||||
|
- [play with the Strudel REPL](https://strudel.tidalcycles.org/)
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,8 @@ function useRepl({ tune, defaultSynth }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// set active pattern on ctrl+enter
|
// set active pattern on ctrl+enter
|
||||||
useLayoutEffect(() => {
|
/* useLayoutEffect(() => {
|
||||||
|
// TODO: make sure this is only fired when editor has focus
|
||||||
const handleKeyPress = (e: any) => {
|
const handleKeyPress = (e: any) => {
|
||||||
if (e.ctrlKey || e.altKey) {
|
if (e.ctrlKey || e.altKey) {
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
|
|
@ -109,9 +110,9 @@ function useRepl({ tune, defaultSynth }) {
|
||||||
};
|
};
|
||||||
document.addEventListener('keypress', handleKeyPress);
|
document.addEventListener('keypress', handleKeyPress);
|
||||||
return () => document.removeEventListener('keypress', handleKeyPress);
|
return () => document.removeEventListener('keypress', handleKeyPress);
|
||||||
}, [pattern, code]);
|
}, [pattern, code]); */
|
||||||
|
|
||||||
useWebMidi({
|
/* useWebMidi({
|
||||||
ready: useCallback(({ outputs }) => {
|
ready: useCallback(({ outputs }) => {
|
||||||
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `"${o.name}"`).join(' | ')}) to the pattern. `);
|
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `"${o.name}"`).join(' | ')}) to the pattern. `);
|
||||||
}, []),
|
}, []),
|
||||||
|
|
@ -121,7 +122,7 @@ function useRepl({ tune, defaultSynth }) {
|
||||||
disconnected: useCallback(({ outputs }) => {
|
disconnected: useCallback(({ outputs }) => {
|
||||||
pushLog(`Midi device disconnected! Available: ${outputs.map((o) => `"${o.name}"`).join(', ')}`);
|
pushLog(`Midi device disconnected! Available: ${outputs.map((o) => `"${o.name}"`).join(', ')}`);
|
||||||
}, []),
|
}, []),
|
||||||
});
|
}); */
|
||||||
|
|
||||||
const togglePlay = () => {
|
const togglePlay = () => {
|
||||||
if (!cycle.started) {
|
if (!cycle.started) {
|
||||||
|
|
@ -131,7 +132,20 @@ function useRepl({ tune, defaultSynth }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { code, setCode, pattern, error, cycle, setPattern, dirty, log, togglePlay };
|
return {
|
||||||
|
code,
|
||||||
|
setCode,
|
||||||
|
pattern,
|
||||||
|
error,
|
||||||
|
cycle,
|
||||||
|
setPattern,
|
||||||
|
dirty,
|
||||||
|
log,
|
||||||
|
togglePlay,
|
||||||
|
activateCode,
|
||||||
|
activeCode,
|
||||||
|
pushLog,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useRepl;
|
export default useRepl;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ['./public/**/*.html', './src/**/*.{js,jsx,ts,tsx}'],
|
content: ['./public/**/*.html', './src/**/*.{js,jsx,ts,tsx,mdx}'],
|
||||||
// specify other options here
|
// specify other options here
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue