Compare commits

..

3 Commits

Author SHA1 Message Date
Felix Roos 9b765085e0 example: using onPaint with @strudel/web 2023-07-07 08:59:36 +02:00
Felix Roos 28b978f95f can now await initStrudel to get scheduler 2023-07-07 08:57:28 +02:00
Felix Roos cdf5456b75 fix: reset pianoroll linewidth to 1 2023-07-07 08:56:47 +02:00
37 changed files with 3429 additions and 5046 deletions
+1 -1
View File
@@ -67,6 +67,6 @@
"lerna": "^6.6.1",
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.8.1",
"vitest": "^0.33.0"
"vitest": "^0.28.0"
}
}
+1 -10
View File
@@ -509,7 +509,7 @@ const generic_params = [
* @superDirtOnly
*/
['octave'],
['offset'], // TODO: what is this? not found in tidal doc
// ['ophatdecay'],
// TODO: example
/**
@@ -573,15 +573,6 @@ const generic_params = [
// TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano
// ['velocity'],
['voice'], // TODO: synth param
// voicings // https://github.com/tidalcycles/strudel/issues/506
['chord'], // chord to voice, like C Eb Fm7 G7. the symbols can be defined via addVoicings
['dictionary', 'dict'], // which dictionary to use for the voicings
['anchor'], // the top note to align the voicing to, defaults to c5
['offset'], // how the voicing is offset from the anchored position
['octaves'], // how many octaves are voicing steps spread apart, defaults to 1
[['mode', 'anchor']], // below = anchor note will be removed from the voicing, useful for melody harmonization
/**
* Sets the level of reverb.
*
+1 -1
View File
@@ -36,6 +36,6 @@
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
"devDependencies": {
"vite": "^4.3.3",
"vitest": "^0.33.0"
"vitest": "^0.28.0"
}
}
+9 -27
View File
@@ -783,14 +783,13 @@ export class Pattern {
hap.setContext({
...hap.context,
onTrigger: (...args) => {
// run previously set trigger, if it exists
hap.context.onTrigger?.(...args);
if (!dominant && hap.context.onTrigger) {
hap.context.onTrigger(...args);
}
onTrigger(...args);
},
// if dominantTrigger is set to true, the default output (webaudio) will be disabled
// when using multiple triggers, you cannot flip this flag to false again!
// example: x.csound('CooLSynth').log() as well as x.log().csound('CooLSynth') should work the same
dominantTrigger: hap.context.dominantTrigger || dominant,
// we need this to know later if the default trigger should still fire
dominantTrigger: dominant,
}),
);
}
@@ -2241,18 +2240,14 @@ const _loopAt = function (factor, pat, cps = 1) {
.slow(factor);
};
/**
/*
* Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers.
* Instead of a number, it also accepts a list of numbers from 0 to 1 to slice at specific points.
* @name slice
* @memberof Pattern
* @returns Pattern
* @example
* await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(1.5)
* @example
* await samples('github:tidalcycles/Dirt-Samples/master')
* s("breaks125/2").fit().slice([0,.25,.5,.75], "0 1 1 <2 3>")
*/
export const slice = register(
@@ -2263,9 +2258,9 @@ export const slice = register(
opat.outerBind((o) => {
// If it's not an object, assume it's a string and make it a 's' control parameter
o = o instanceof Object ? o : { s: o };
const begin = Array.isArray(n) ? n[i] : i / n;
const end = Array.isArray(n) ? n[i + 1] : (i + 1) / n;
return pure({ begin, end, _slices: n, ...o });
// Remember we must stay pure and avoid editing the object directly
const toAdd = { begin: i / n, end: (i + 1) / n, _slices: n };
return pure({ ...toAdd, ...o });
}),
),
);
@@ -2307,19 +2302,6 @@ export const { loopAt, loopat } = register(['loopAt', 'loopat'], function (facto
return _loopAt(factor, pat, 1);
});
// this function will be redefined in repl.mjs to use the correct cps value.
// It is still here to work in cases where repl.mjs is not used
export const fit = register('fit', (pat) =>
pat.withHap((hap) =>
hap.withValue((v) => ({
...v,
speed: 1 / hap.whole.duration,
unit: 'c',
})),
),
);
/**
* Makes the sample fit the given number of cycles and cps value, by
* changing the speed. Please note that at some point cps will be
+3 -1
View File
@@ -198,6 +198,8 @@ export function pianoroll({
let from = -cycles * playhead;
let to = cycles * (1 - playhead);
ctx.lineWidth = 1;
if (timeframeProp) {
console.warn('timeframe is deprecated! use from/to instead');
from = 0;
@@ -246,7 +248,7 @@ export function pianoroll({
haps
// .filter(inFrame)
.forEach((event) => {
const isActive = event.whole.begin <= time && event.endClipped > time;
const isActive = event.whole.begin <= time && event.whole.end > time;
const color = event.value?.color || event.context?.color;
ctx.fillStyle = color || inactive;
ctx.strokeStyle = color || active;
+7
View File
@@ -191,6 +191,13 @@ export const chooseInWith = (pat, xs) => {
*/
export const choose = (...xs) => chooseWith(rand, xs);
/**
* Chooses from the given list of values (or patterns of values), according
* to the pattern that the method is called on. The pattern should be in
* the range 0 .. 1.
* @param {...any} xs
* @returns {Pattern}
*/
Pattern.prototype.choose = function (...xs) {
return chooseWith(this, xs);
};
+5 -8
View File
@@ -11,24 +11,21 @@ export const tokenizeNote = (note) => {
if (typeof note !== 'string') {
return [];
}
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)([0-9]*)$/)?.slice(1) || [];
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)([0-9])?$/)?.slice(1) || [];
if (!pc) {
return [];
}
return [pc, acc, oct ? Number(oct) : undefined];
};
const chromas = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };
const accs = { '#': 1, b: -1, s: 1, f: -1 };
// turns the given note into its midi number representation
export const noteToMidi = (note, defaultOctave = 3) => {
const [pc, acc, oct = defaultOctave] = tokenizeNote(note);
export const noteToMidi = (note) => {
const [pc, acc, oct = 3] = tokenizeNote(note);
if (!pc) {
throw new Error('not a note: "' + note + '"');
}
const chroma = chromas[pc.toLowerCase()];
const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0;
const chroma = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }[pc.toLowerCase()];
const offset = acc?.split('').reduce((o, char) => o + { '#': 1, b: -1, s: 1, f: -1 }[char], 0) || 0;
return (Number(oct) + 1) * 12 + chroma + offset;
};
export const midiToFreq = (n) => {
+2 -2
View File
@@ -113,8 +113,8 @@ Pattern.prototype.midi = function (output) {
const velocity = hap.context?.velocity ?? 0.9; // TODO: refactor velocity
const duration = hap.duration.valueOf() * 1000 - 5;
if (note != null) {
const midiNumber = typeof note === 'number' ? note : noteToMidi(note);
if (note) {
const midiNumber = noteToMidi(note);
device.playNote(midiNumber, midichan, {
time,
duration,
+1 -1
View File
@@ -37,6 +37,6 @@
"devDependencies": {
"peggy": "^3.0.2",
"vite": "^4.3.3",
"vitest": "^0.33.0"
"vitest": "^0.28.0"
}
}
-2
View File
@@ -3,5 +3,3 @@ import './voicings.mjs';
export * from './tonal.mjs';
export * from './voicings.mjs';
import './ireal.mjs';
-523
View File
@@ -1,523 +0,0 @@
// explore them here: https://codesandbox.io/s/voicing-explorer-ireal-47tkx5?file=/src/ireal.js:0-16036
// scraped via: https://codesandbox.io/s/ireal-midi-scraper-2-gjz2mr?file=/src/index.js
export const simple = {
2: ['1P 5P 8P 9M', '1P 5P 8P 9M 12P', '5P 8P 9M 12P'],
5: ['1P 5P 8P 12P', '5P 8P 12P 15P'],
6: ['1P 5P 6M 8P 10M', '1P 5P 8P 10M 13M', '3M 5P 8P 10M 13M', '5P 8P 10M 12P 13M'],
7: [
'1P 5P 7m 8P 10M',
'1P 7m 8P 10M 12P',
'3M 7m 8P 10M 12P',
'3M 7m 8P 10M 14m',
'3M 7m 10M 12P 15P',
'7m 10M 12P 14m 15P',
'7m 10M 12P 15P 17M',
],
9: [
'1P 5P 7m 9M 10M',
'1P 7m 9M 10M 12P',
'3M 7m 8P 9M 12P',
'7m 9M 10M 14m 15P',
'3M 7m 8P 12P 16M',
'7m 10M 12P 15P 16M',
],
11: ['1P 5P 7m 9M 11P', '5P 7m 8P 9M 11P', '7m 8P 9M 11P 12P', '7m 8P 11P 12P 16M'],
13: ['1P 6M 7m 9M 10M', '1P 7m 9M 10M 13M', '3M 7m 8P 9M 13M', '7m 8P 9M 10M 13M', '7m 9M 10M 13M 15P'],
69: ['1P 5P 6M 9M 10M', '1P 5P 9M 10M 13M', '3M 5P 8P 9M 13M', '5P 8P 9M 10M 13M'],
add9: ['1P 5P 8P 9M 10M', '1P 5P 9M 10M 12P', '3M 8P 9M 10M 12P', '3M 8P 9M 12P 15P', '5P 8P 9M 12P 17M'],
'+': [
'1P 3M 6m 8P 10M',
'1P 6m 8P 10M 13m',
'3M 6m 8P 10M 13m',
'3M 8P 10M 13m 15P',
'6m 8P 10M 13m 15P',
'6m 10M 13m 15P 17M',
],
o: ['1P 5d 8P 10m 12d', '3m 8P 10m 12d 15P', '5d 8P 10m 12d 15P'],
h: [
'3m 5d 7m 8P 10m',
'1P 5d 7m 10m 12d',
'3m 7m 8P 10m 12d',
'3m 7m 8P 12d 14m',
'5d 7m 8P 10m 14m',
'5d 8P 10m 12d 14m',
'7m 10m 12d 14m 15P',
'5d 8P 10m 14m 17m',
],
sus: ['1P 4P 5P 8P', '1P 4P 5P 8P 11P', '5P 8P 11P 12P', '5P 8P 11P 12P 15P'],
'^': ['1P 5P 8P 10M', '1P 5P 8P 10M 12P', '3M 5P 8P 10M 12P', '3M 8P 10M 12P 15P', '5P 8P 10M 12P 15P'],
'-': ['1P 3m 5P 8P 10m', '1P 5P 8P 10m 12P', '3m 5P 8P 10m 12P', '5P 8P 10m 12P 15P'],
'^7': ['1P 5P 7M 10M 12P', '1P 10M 12P 14M', '3M 8P 10M 12P 14M', '5P 8P 10M 12P 14M', '5P 8P 10M 14M 17M'],
'-7': [
'1P 3m 5P 7m 10m',
'1P 5P 7m 10m 12P',
'3m 7m 8P 10m 12P',
'3m 7m 8P 10m 14m',
'5P 7m 8P 10m 14m',
'7m 10m 12P 14m 15P',
'5P 8P 10m 14m 17m',
'7m 10m 12P 15P 17m',
],
'7sus': ['1P 5P 7m 8P 11P', '5P 8P 11P 12P 14m', '7m 8P 11P 12P 14m', '7m 11P 12P 14m 18P'],
h7: [
'3m 5d 7m 8P 10m',
'1P 5d 7m 10m 12d',
'1P 7m 10m 12d',
'3m 7m 8P 10m 12d',
'3m 7m 8P 12d 14m',
'5d 7m 8P 10m 14m',
'5d 8P 10m 12d 14m',
'7m 10m 12d 14m 15P',
'5d 8P 10m 14m 17m',
],
o7: [
'1P 6M 8P 10m 12d',
'1P 6M 10m 12d 13M',
'3m 8P 10m 12d 13M',
'3m 8P 12d 13M 15P',
'5d 10m 12d 13M 15P',
'5d 10m 13M 15P 17m',
'6M 12d 13M 15P 17m',
'6M 12d 15P 17m 19d',
],
'^9': [
'1P 5P 7M 9M 10M',
'1P 7M 9M 10M 12P',
'3M 7M 8P 9M 12P',
'3M 7M 8P 12P 16M',
'5P 8P 10M 14M 16M',
'7M 8P 10M 12P 16M',
],
'^13': ['1P 6M 7M 9M 10M', '1P 7M 9M 10M 13M', '3M 7M 8P 9M 13M', '3M 7M 8P 13M 16M', '7M 8P 10M 13M 16M'],
'^7#11': ['1P 5P 7M 10M 12d', '3M 7M 8P 10M 12d', '1P 7M 10M 12d 14M', '3M 7M 8P 12d 14M', '5P 8P 10M 12d 14M'],
'^9#11': ['1P 3M 5d 7M 9M', '1P 7M 9M 10M 12d', '3M 7M 8P 9M 12d', '3M 8P 9M 12d 14M'],
'^7#5': ['1P 6m 7M 10M 13m', '3M 7M 8P 10M 13m', '6m 7M 8P 10M 13m'],
'-6': [
'1P 3m 5P 6M 8P',
'1P 5P 6M 8P 10m',
'3m 5P 6M 8P 10m',
'1P 5P 8P 10m 13M',
'3m 5P 8P 10m 13M',
'5P 8P 10m 12P 13M',
'5P 8P 10m 13M 15P',
],
'-69': [
'1P 3m 5P 6M 9M',
'3m 5P 6M 8P 9M',
'3m 6M 9M 10m 12P',
'1P 5P 9M 10m 13M',
'3m 5P 8P 9M 13M',
'5P 8P 9M 10m 13M',
'5P 8P 10m 13M 16M',
],
'-^7': ['1P 3m 5P 7M 10m', '1P 5P 7M 10m 12P', '3m 7M 8P 10m 12P', '5P 7M 8P 10m 14M', '5P 8P 10m 14M 17m'],
'-^9': ['1P 3m 5P 7M 9M', '1P 7M 9M 10m 12P', '3m 7M 8P 9M 12P', '5P 8P 9M 10m 14M'],
'-9': [
'1P 3m 5P 7m 9M',
'3m 5P 7m 8P 9M',
'3m 7m 8P 9M 12P',
'5P 8P 9M 10m 14m',
'3m 7m 9M 12P 15P',
'7m 10m 12P 15P 16M',
],
'-add9': ['1P 2M 3m 5P 8P', '1P 3m 5P 9M', '3m 5P 8P 9M 12P', '5P 8P 9M 10m 12P'],
'-11': [
'1P 3m 7m 9M 11P',
'3m 7m 8P 9M 11P',
'1P 4P 7m 10m 12P',
'5P 8P 11P 14m',
'3m 7m 9M 11P 15P',
'5P 8P 11P 14m 16M',
'7m 10m 12P 15P 18P',
],
'-7b5': [
'3m 5d 7m 8P 10m',
'1P 7m 10m 12d',
'1P 5d 7m 10m 12d',
'3m 7m 8P 10m 12d',
'3m 7m 8P 12d 14m',
'5d 7m 8P 10m 14m',
'5d 8P 10m 12d 14m',
'7m 10m 12d 14m 15P',
'5d 8P 10m 14m 17m',
],
h9: ['1P 7m 9M 10m 12d', '3m 7m 8P 9M 12d', '5d 8P 9M 10m 14m', '7m 10m 12d 15P 16M'],
'-b6': ['1P 5P 6m 8P 10m', '1P 5P 8P 10m 13m', '3m 5P 8P 10m 13m', '5P 8P 10m 13m', '5P 8P 10m 13m 15P'],
'-#5': ['1P 6m 8P 10m 13m', '3m 6m 8P 10m 13m', '6m 8P 10m 13m 15P'],
'7b9': ['1P 3M 7m 9m 10M', '3M 7m 8P 9m 10M', '3M 7m 8P 9m 14m', '7m 9m 10M 14m 15P'],
'7#9': ['1P 3M 7m 10m', '3M 7m 8P 10m 14m', '7m 10m 10M 14m 15P'],
'7#11': ['1P 3M 7m 10M 12d', '3M 7m 8P 10M 12d', '7m 10M 12d 14m 15P'],
'7b5': ['1P 3M 7m 10M 12d', '3M 7m 8P 10M 12d', '7m 10M 12d 14m 15P'],
'7#5': ['1P 3M 7m 10M 13m', '3M 7m 8P 10M 13m', '3M 7m 8P 13m 14m', '7m 10M 13m 14m 15P'],
'9#11': ['1P 7m 9M 10M 12d', '3M 7m 8P 9M 12d', '7m 10M 12d 15P 16M'],
'9b5': ['1P 7m 9M 10M 12d', '3M 7m 8P 9M 12d', '7m 10M 12d 15P 16M'],
'9#5': ['1P 7m 9M 10M 13m', '3M 7m 9M 10M 13m', '3M 7m 9M 13m 14m', '7m 10M 13m 14m 16M', '7m 10M 13m 16M 17M'],
'7b13': ['1P 3M 7m 10M 13m', '3M 7m 8P 10M 13m', '3M 7m 8P 13m 14m', '7m 10M 13m 14m 15P'],
'7#9#5': ['1P 3M 7m 10m 13m', '3M 7m 10m 13m 15P', '7m 10M 13m 15P 17m'],
'7#9b5': ['1P 3M 7m 10m 12d', '3M 7m 10m 12d 15P', '7m 10M 12d 15P 17m'],
'7#9#11': ['1P 3M 7m 10m 12d', '3M 7m 10m 12d 15P', '7m 10M 12d 15P 17m'],
'7b9#11': ['1P 7m 9m 10M 12d', '3M 7m 8P 9m 12d', '7m 8P 10M 12d 16m'],
'7b9b5': ['1P 7m 9m 10M 12d', '3M 7m 8P 9m 12d', '7m 8P 10M 12d 16m'],
'7b9#5': ['1P 7m 9m 10M 13m', '3M 7m 8P 9m 13m', '7m 9m 10M 13m 15P'],
'7b9#9': ['1P 3M 7m 9m 10m', '3M 7m 8P 9m 10m', '7m 8P 10M 16m 17m'],
'7b9b13': ['1P 7m 9m 10M 13m', '3M 7m 8P 9m 13m', '7m 9m 10M 13m 15P'],
'7alt': [
'3M 7m 8P 9m 12d',
'1P 7m 10m 10M 13m',
'3M 7m 8P 10m 13m',
'3M 7m 9m 12d 15P',
'3M 7m 10m 13m 15P',
'7m 10M 12d 15P 17m',
'7m 10M 13m 15P 17m',
],
'13#11': ['1P 6M 7m 10M 12d', '3M 7m 9M 12d 13M', '7m 10M 12d 13M 16M'],
'13b9': ['1P 3M 6M 7m 9m', '1P 6M 7m 9m 10M', '3M 7m 9m 10M 13M', '3M 7m 10M 13M 16m', '7m 10M 13M 16m 17M'],
'13#9': ['1P 3M 6M 7m 10m', '3M 7m 8P 10m 13M', '7m 10M 13M 14m 17m'],
'7b9sus': ['1P 5P 7m 9m 11P', '5P 7m 8P 9m 11P', '7m 8P 11P 14m 16m'],
'7susadd3': ['1P 4P 5P 7m 10M', '5P 8P 10M 11P 14m', '7m 11P 12P 15P 17M'],
'9sus': ['1P 5P 7m 9M 11P', '5P 7m 8P 9M 11P', '7m 8P 9M 11P 12P', '7m 8P 11P 12P 16M'],
'13sus': ['1P 4P 6M 7m 9M', '1P 7m 9M 11P 13M', '5P 7m 9M 11P 13M', '7m 9M 11P 13M 15P'],
'7b13sus': ['1P 5P 7m 11P 13m', '5P 7m 8P 11P 13m', '7m 11P 13m 14m 15P'],
};
export const complex = {
2: ['1P 5P 6M 8P 9M', '1P 5P 8P 9M 12P', '5P 8P 9M 12P 13M', '5P 8P 9M 12P 15P'],
5: ['1P 5P 8P 12P', '1P 5P 8P 9M 12P', '5P 8P 12P 15P', '5P 8P 12P 15P 16M'],
6: ['1P 5P 6M 9M 10M', '1P 5P 9M 10M 13M', '3M 5P 9M 10M 13M', '5P 8P 9M 10M 13M', '3M 6M 9M 12P 15P'],
7: [
'1P 5P 7m 8P 10M',
'1P 7m 8P 10M 12P',
'3M 7m 8P 10M 12P',
'3M 7m 8P 10M 14m',
'3M 7m 10M 12P 15P',
'7m 10M 12P 14m 15P',
'7m 10M 12P 15P 17M',
'7m 10M 14m 17M 19P',
],
9: [
'1P 6M 7m 9M 10M',
'3M 7m 9M 10M 12P',
'1P 7m 9M 10M 13M',
'3M 7m 9M 10M 13M',
'3M 7m 9M 12P 15P',
'7m 10M 12P 13M 16M',
'7m 10M 13M 16M 17M',
'7m 10M 13M 16M 19P',
],
11: [
'1P 4P 6M 7m 9M',
'1P 5P 7m 9M 11P',
'4P 6M 7m 9M 11P',
'5P 8P 9M 11P 14m',
'7m 9M 11P 13M 15P',
'7m 11P 12P 14m 18P',
],
13: [
'3M 7m 9M 10M 13M',
'3M 7m 9M 13M 15P',
'3M 7m 10M 13M 16M',
'7m 10M 12P 13M 16M',
'7m 10M 13M 16M 17M',
'7m 10M 13M 16M 19P',
],
69: ['1P 5P 6M 9M 10M', '1P 5P 9M 10M 13M', '3M 5P 9M 10M 13M', '5P 8P 9M 10M 13M', '3M 6M 9M 12P 15P'],
add9: [
'1P 5P 8P 9M 10M',
'1P 5P 9M 10M 12P',
'3M 8P 9M 10M 12P',
'3M 8P 9M 12P 15P',
'5P 8P 9M 10M 15P',
'5P 8P 9M 12P 17M',
],
'+': [
'1P 6m 8P 9M 10M',
'1P 6m 8P 10M 13m',
'3M 8P 9M 10M 13m',
'3M 8P 10M 13m 15P',
'6m 10M 13m 15P 16M',
'6m 10M 13m 15P 17M',
],
o: [
'1P 6M 8P 10m 12d',
'1P 6M 10m 12d 13M',
'3m 8P 10m 12d 13M',
'3m 8P 12d 13M 15P',
'5d 10m 12d 13M 15P',
'5d 10m 13M 15P 17m',
'6M 12d 13M 15P 17m',
'6M 12d 15P 17m 19d',
],
h: [
'1P 5d 7m 10m 11P',
'3m 5d 7m 8P 11P',
'5d 7m 8P 10m 11P',
'1P 7m 10m 12d',
'3m 7m 8P 12d 14m',
'5d 8P 10m 11P 14m',
'7m 10m 11P 12d 14m',
'7m 10m 12d 14m 15P',
'5d 8P 10m 14m 17m',
],
sus: [
'1P 4P 5P 8P 9M',
'1P 4P 5P 8P 11P',
'1P 5P 8P 9M 11P',
'5P 8P 9M 11P 12P',
'5P 8P 11P 12P 13M',
'5P 8P 11P 13M 15P',
],
'^': [
'1P 3M 5P 6M 9M',
'1P 5P 8P 10M 12P',
'3M 5P 9M 10M 12P',
'1P 5P 8P 10M 13M',
'3M 8P 10M 13M 15P',
'5P 9M 10M 12P 15P',
],
'-': [
'1P 3m 5P 8P 10m',
'1P 3m 5P 9M 11P',
'3m 5P 8P 9M 11P',
'5P 8P 9M 10m 11P',
'1P 5P 9M 10m 12P',
'3m 5P 8P 10m 12P',
'5P 8P 10m 12P 15P',
],
'^7': [
'1P 6M 7M 9M 10M',
'3M 7M 9M 10M 12P',
'1P 7M 9M 10M 13M',
'3M 7M 9M 10M 13M',
'3M 7M 9M 12P 13M',
'3M 7M 9M 13M 14M',
'3M 7M 10M 13M 16M',
'7M 10M 13M 14M 16M',
'7M 10M 13M 16M 17M',
'7M 10M 13M 16M 19P',
],
'-7': [
'1P 3m 5P 7m 9M',
'1P 3m 5P 7m 10m',
'1P 5P 7m 10m 11P',
'3m 7m 8P 10m 11P',
'1P 5P 7m 10m 12P',
'3m 7m 9M 10m 12P',
'3m 7m 8P 10m 14m',
'5P 7m 9M 10m 14m',
'7m 10m 11P 14m 15P',
'7m 10m 12P 15P 16M',
'5P 8P 11P 14m 17m',
'7m 10m 12P 15P 17m',
],
'7sus': [
'1P 4P 6M 7m 9M',
'1P 5P 7m 9M 11P',
'4P 6M 7m 9M 11P',
'5P 8P 9M 11P 14m',
'7m 9M 11P 13M 15P',
'7m 11P 12P 14m 18P',
],
h7: [
'1P 5d 7m 10m 11P',
'3m 5d 7m 8P 11P',
'5d 7m 8P 10m 11P',
'1P 7m 10m 12d',
'3m 7m 8P 10m 12d',
'3m 7m 8P 12d 14m',
'5d 8P 10m 11P 14m',
'7m 10m 11P 12d 14m',
'7m 10m 12d 14m 15P',
'5d 8P 10m 14m 17m',
],
o7: [
'1P 6M 8P 10m 12d',
'1P 6M 10m 12d 13M',
'3m 8P 10m 12d 13M',
'3m 8P 12d 13M 15P',
'5d 10m 12d 13M 15P',
'5d 10m 13M 15P 17m',
'6M 12d 13M 15P 17m',
'6M 12d 15P 17m 19d',
],
'^9': [
'1P 6M 7M 9M 10M',
'1P 7M 9M 10M 13M',
'3M 7M 9M 10M 13M',
'3M 7M 9M 12P 13M',
'3M 7M 8P 9M 13M',
'3M 7M 9M 13M 14M',
'3M 7M 10M 13M 16M',
'7M 10M 13M 14M 16M',
'7M 10M 13M 16M 17M',
'7M 10M 13M 16M 19P',
],
'^13': [
'1P 6M 7M 9M 10M',
'1P 7M 9M 10M 13M',
'3M 7M 9M 12P 13M',
'3M 7M 9M 10M 13M',
'3M 7M 8P 9M 13M',
'3M 7M 9M 13M 14M',
'3M 7M 10M 13M 16M',
'7M 10M 13M 14M 16M',
'7M 10M 13M 16M 17M',
'7M 10M 13M 16M 19P',
],
'^7#11': [
'1P 3M 5d 7M 9M',
'1P 7M 9M 10M 12d',
'3M 7M 9M 10M 12d',
'3M 7M 9M 12d 13M',
'3M 7M 10M 12d 14M',
'7M 10M 12d 13M 14M',
'7M 10M 12d 13M 16M',
'7M 10M 12d 14M 17M',
],
'^9#11': [
'1P 3M 5d 7M 9M',
'1P 7M 9M 10M 12d',
'3M 7M 9M 10M 12d',
'3M 7M 9M 12d 13M',
'3M 7M 9M 12d 14M',
'7M 10M 12d 14M 16M',
'7M 10M 12d 13M 16M',
],
'^7#5': ['1P 6m 7M 10M 13m', '3M 7M 9M 10M 13m', '3M 7M 10M 13m 14M', '7M 10M 13m 14M 16M', '7M 10M 13m 14M 17M'],
'-6': [
'1P 3m 5P 6M 9M',
'3m 5P 6M 8P 9M',
'1P 5P 6M 10m 11P',
'3m 5P 6M 8P 11P',
'1P 5P 9M 10m 13M',
'3m 5P 8P 9M 13M',
'5P 8P 10m 11P 13M',
'5P 8P 10m 13M 16M',
],
'-69': [
'1P 3m 5P 6M 9M',
'3m 5P 6M 8P 9M',
'3m 6M 9M 10m 12P',
'1P 5P 9M 10m 13M',
'3m 5P 8P 9M 13M',
'5P 8P 9M 10m 13M',
'5P 8P 10m 13M 16M',
],
'-^7': [
'1P 3m 5P 7M 9M',
'1P 5P 7M 10m 11P',
'3m 7M 9M 10m 11P',
'3m 7M 9M 10m 12P',
'3m 7M 9M 12P 14M',
'7M 10m 11P 12P 14M',
'7M 10m 12P 14M 16M',
],
'-^9': [
'1P 3m 5P 7M 9M',
'1P 5P 7M 10m 11P',
'3m 7M 9M 10m 11P',
'3m 7M 9M 10m 12P',
'3m 7M 9M 12P 14M',
'7M 10m 11P 12P 14M',
'7M 10m 12P 14M 16M',
],
'-9': [
'1P 3m 5P 7m 9M',
'1P 3m 7m 9M 11P',
'3m 7m 9M 10m 11P',
'3m 7m 9M 10m 12P',
'3m 7m 9M 10m 14m',
'3m 7m 9M 12P 15P',
'7m 10m 11P 14m 16M',
'7m 10m 12P 16M 18P',
],
'-add9': ['1P 2M 3m 5P 8P', '1P 3m 5P 9M', '3m 5P 8P 9M 12P', '5P 8P 9M 10m 12P'],
'-11': [
'3m 5P 7m 9M 11P',
'7m 9M 10m 11P',
'1P 4P 7m 10m 12P',
'3m 7m 9M 11P 12P',
'7m 9M 10m 11P 12P',
'3m 7m 9M 11P 14m',
'4P 10m 12P 14m',
'5P 8P 11P 14m',
'5P 8P 11P 14m 16M',
'7m 10m 12P 16M 18P',
'7m 10m 11P 16M 21m',
],
'-7b5': [
'1P 5d 7m 10m 11P',
'3m 5d 7m 8P 11P',
'5d 7m 8P 10m 11P',
'1P 7m 10m 12d',
'3m 7m 8P 10m 12d',
'3m 7m 8P 12d 14m',
'5d 8P 10m 11P 14m',
'7m 10m 11P 12d 14m',
'7m 10m 12d 14m 15P',
'5d 8P 10m 14m 17m',
],
h9: [
'3m 5d 7m 9M 11P',
'1P 7m 9M 10m 12d',
'3m 7m 9M 12d 14m',
'5d 8P 9M 10m 14m',
'7m 10m 11P 12d 14m',
'7m 10m 12d 14m 16M',
],
'-b6': ['1P 3m 5P 6m 8P', '3m 5P 8P 11P 13m', '5P 8P 10m 11P 13m'],
'-#5': ['1P 6m 8P 10m 13m', '3m 6m 8P 11P 13m', '6m 8P 10m 13m 15P'],
'7b9': ['1P 3M 7m 9m 10M', '3M 7m 8P 9m 10M', '3M 7m 8P 9m 14m', '7m 9m 10M 14m 15P'],
'7#9': ['1P 3M 7m 10m', '3M 7m 10m 10M 12P', '3M 7m 10m 12P 14m', '7m 10M 12P 14m 17m'],
'7#11': ['1P 3M 7m 9M 12d', '3M 7m 9M 12d 13M', '7m 10M 12d 13M 16M'],
'7b5': ['1P 3M 7m 9M 12d', '3M 7m 9M 12d 13M', '7m 10M 12d 13M 16M'],
'7#5': ['1P 3M 7m 10M 13m', '3M 7m 8P 10M 13m', '3M 7m 8P 13m 14m', '7m 10M 13m 14m 15P', '7m 10M 13m 14m 17M'],
'9#11': ['1P 7m 9M 10M 12d', '3M 7m 8P 9M 12d', '7m 10M 12d 15P 16M'],
'9b5': ['1P 7m 9M 10M 12d', '3M 7m 8P 9M 12d', '7m 10M 12d 15P 16M'],
'9#5': ['1P 7m 9M 10M 13m', '3M 7m 9M 10M 13m', '3M 7m 9M 13m 14m', '7m 10M 13m 14m 16M', '7m 10M 13m 16M 17M'],
'7b13': ['1P 3M 7m 10M 13m', '3M 7m 8P 10M 13m', '3M 7m 8P 13m 14m', '7m 10M 13m 14m 15P', '7m 10M 13m 14m 17M'],
'7#9#5': ['3M 7m 10m 10M 13m', '3M 7m 10m 13m 14m', '7m 10M 13m 14m 17m'],
'7#9b5': ['3M 7m 10m 10M 12d', '3M 7m 10m 12d 14m', '7m 10M 12d 14m 17m'],
'7#9#11': ['3M 7m 10m 10M 12d', '3M 7m 10m 12d 14m', '7m 10M 12d 14m 17m'],
'7b9#11': ['3M 7m 9m 10M 12d', '3M 7m 9m 12d 14m', '7m 8P 10M 12d 16m', '7m 10M 12d 14m 16m'],
'7b9b5': ['3M 7m 9m 10M 12d', '3M 7m 9m 12d 14m', '7m 8P 10M 12d 16m', '7m 10M 12d 14m 16m'],
'7b9#5': ['1P 7m 9m 10M 13m', '3M 7m 9m 10M 13m', '3M 7m 10M 13m 16m', '7m 10M 13m 14m 16m', '7m 10M 13m 16m 17M'],
'7b9#9': ['1P 3M 7m 9m 10m', '3M 7m 10m 13m 16m', '7m 10M 13m 16m 17m'],
'7b9b13': ['1P 7m 9m 10M 13m', '3M 7m 9m 10M 13m', '3M 7m 10M 13m 16m', '7m 10M 13m 14m 16m', '7m 10M 13m 16m 17M'],
'7alt': [
'3M 7m 8P 10m 13m',
'3M 7m 9m 12d 13m',
'3M 7m 9m 10m 13m',
'3M 7m 10m 13m 14m',
'3M 7m 9m 12d 14m',
'3M 7m 10m 13m 15P',
'3M 7m 10m 13m 16m',
'7m 10M 12d 14m 16m',
'7m 10M 12d 13m 16m',
'7m 10M 13m 15P 17m',
'7m 10M 13m 16m 17m',
'7m 10M 13m 16m 19d',
],
'13#11': ['3M 7m 9M 12d 13M', '7m 10M 12d 13M 16M'],
'13b9': ['3M 7m 9m 10M 13M', '3M 7m 10M 13M 16m', '7m 10M 13M 16m 17M'],
'13#9': ['3M 7m 10m 10M 13M', '7m 10M 13M 14m 17m'],
'7b9sus': ['1P 5P 7m 9m 11P', '5P 7m 8P 9m 11P', '7m 8P 11P 14m 16m'],
'7susadd3': ['1P 4P 5P 7m 10M', '5P 8P 10M 11P 14m', '7m 11P 12P 15P 17M'],
'9sus': [
'1P 4P 6M 7m 9M',
'1P 5P 7m 9M 11P',
'4P 6M 7m 9M 11P',
'5P 8P 9M 11P 14m',
'7m 9M 11P 13M 15P',
'7m 11P 12P 14m 18P',
],
'13sus': [
'1P 4P 6M 7m 9M',
'1P 7m 9M 11P 13M',
'4P 7m 9M 11P 13M',
'7m 9M 11P 13M 15P',
'7m 11P 13M 14m 16M',
'7m 11P 13M 16M 18P',
],
'7b13sus': ['1P 5P 7m 11P 13m', '5P 7m 8P 11P 13m', '7m 11P 13m 14m 15P'],
};
+1 -1
View File
@@ -38,6 +38,6 @@
},
"devDependencies": {
"vite": "^4.3.3",
"vitest": "^0.33.0"
"vitest": "^0.28.0"
}
}
-152
View File
@@ -1,152 +0,0 @@
/*
tonleiter.test.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/tonal/test/tonleiter.test.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { describe, test, expect } from 'vitest';
import {
Step,
Note,
transpose,
pc2chroma,
rotateChroma,
chroma2pc,
tokenizeChord,
note2pc,
note2oct,
midi2note,
renderVoicing,
scaleStep,
} from '../tonleiter.mjs';
describe('tonleiter', () => {
test('Step ', () => {
expect(Step.tokenize('#11')).toEqual(['#', 11]);
expect(Step.tokenize('b13')).toEqual(['b', 13]);
expect(Step.tokenize('bb6')).toEqual(['bb', 6]);
expect(Step.tokenize('b3')).toEqual(['b', 3]);
expect(Step.tokenize('3')).toEqual(['', 3]);
expect(Step.tokenize('10')).toEqual(['', 10]);
// expect(Step.tokenize('asdasd')).toThrow();
expect(Step.accidentals('b3')).toEqual(-1);
expect(Step.accidentals('#11')).toEqual(1);
});
test('Note', () => {
expect(Note.tokenize('C##')).toEqual(['C', '##']);
expect(Note.tokenize('Bb')).toEqual(['B', 'b']);
expect(Note.accidentals('C#')).toEqual(1);
expect(Note.accidentals('C##')).toEqual(2);
expect(Note.accidentals('Eb')).toEqual(-1);
expect(Note.accidentals('Bbb')).toEqual(-2);
});
test('transpose', () => {
expect(transpose('F#', '3')).toEqual('A#');
expect(transpose('C', '3')).toEqual('E');
expect(transpose('D', '3')).toEqual('F#');
expect(transpose('E', '3')).toEqual('G#');
expect(transpose('Eb', '3')).toEqual('G');
expect(transpose('Ebb', '3')).toEqual('Gb');
});
test('pc2chroma', () => {
expect(pc2chroma('C')).toBe(0);
expect(pc2chroma('C#')).toBe(1);
expect(pc2chroma('C##')).toBe(2);
expect(pc2chroma('D')).toBe(2);
expect(pc2chroma('Db')).toBe(1);
expect(pc2chroma('Dbb')).toBe(0);
expect(pc2chroma('bb')).toBe(10);
expect(pc2chroma('f')).toBe(5);
expect(pc2chroma('c')).toBe(0);
});
test('rotateChroma', () => {
expect(rotateChroma(0, 1)).toBe(1);
expect(rotateChroma(0, -1)).toBe(11);
expect(rotateChroma(11, 1)).toBe(0);
expect(rotateChroma(11, 13)).toBe(0);
});
test('chroma2pc', () => {
expect(chroma2pc(0)).toBe('C');
expect(chroma2pc(1)).toBe('Db');
expect(chroma2pc(1, true)).toBe('C#');
expect(chroma2pc(2)).toBe('D');
expect(chroma2pc(3)).toBe('Eb');
});
test('tokenizeChord', () => {
expect(tokenizeChord('Cm7')).toEqual(['C', 'm7', undefined]);
expect(tokenizeChord('C#m7')).toEqual(['C#', 'm7', undefined]);
expect(tokenizeChord('Bb^7')).toEqual(['Bb', '^7', undefined]);
expect(tokenizeChord('Bb^7/F')).toEqual(['Bb', '^7', 'F']);
});
test('note2pc', () => {
expect(note2pc('C5')).toBe('C');
expect(note2pc('C52')).toBe('C');
expect(note2pc('Bb3')).toBe('Bb');
expect(note2pc('F')).toBe('F');
});
test('note2oct', () => {
expect(note2oct('C5')).toBe(5);
expect(note2oct('Bb3')).toBe(3);
expect(note2oct('C7')).toBe(7);
expect(note2oct('C10')).toBe(10);
});
test('midi2note', () => {
expect(midi2note(60)).toBe('C4');
expect(midi2note(61)).toBe('Db4');
expect(midi2note(61, true)).toBe('C#4');
});
test('scaleStep', () => {
expect(scaleStep([60, 63, 67], 0)).toBe(60);
expect(scaleStep([60, 63, 67], 1)).toBe(63);
expect(scaleStep([60, 63, 67], 2)).toBe(67);
expect(scaleStep([60, 63, 67], 3)).toBe(72);
expect(scaleStep([60, 63, 67], 4)).toBe(75);
expect(scaleStep([60, 63, 67], -1)).toBe(55);
expect(scaleStep([60, 63, 67], -2)).toBe(51);
expect(scaleStep([60, 63, 67], -3)).toBe(48);
expect(scaleStep([60, 63, 67], -4)).toBe(43);
});
test('renderVoicing', () => {
const dictionary = {
m7: [
'3 7 10 14', // b3 5 b7 9
'10 14 15 19', // b7 9 b3 5
],
};
expect(renderVoicing({ chord: 'Em7', anchor: 'Bb4', dictionary, mode: 'below' })).toEqual([
'G3',
'B3',
'D4',
'Gb4',
]);
expect(renderVoicing({ chord: 'Cm7', anchor: 'D5', dictionary, mode: 'below' })).toEqual([
'Eb4',
'G4',
'Bb4',
'D5',
]);
expect(renderVoicing({ chord: 'Cm7', anchor: 'G5', dictionary, mode: 'below' })).toEqual([
'Bb4',
'D5',
'Eb5',
'G5',
]);
expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, mode: 'below' })).toEqual([
'Bb4',
'D5',
'Eb5',
'G5',
]);
expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, mode: 'below', n: 0 })).toEqual([70]); // Bb4
expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, mode: 'below', n: 1 })).toEqual([74]); // D5
expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, mode: 'below', n: 4 })).toEqual([82]); // Bb5
expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, mode: 'below', offset: 1 })).toEqual([
'Eb5',
'G5',
'Bb5',
'D6',
]);
// expect(voiceBelow('G4', 'Cm7', voicingDictionary)).toEqual(['Bb3', 'D4', 'Eb4', 'G4']);
// TODO: test with offset
});
});
+6 -6
View File
@@ -127,18 +127,18 @@ export const scaleTranspose = register('scaleTranspose', function (offset /* : n
*
* The root note defaults to octave 3, if no octave number is given.
*
* @memberof Pattern
* @name scale
* @param {string} scale Name of scale
* @returns Pattern
* @example
* n("0 2 4 6 4 2").scale("C:major")
* "0 2 4 6 4 2".scale("C2:major").note()
* @example
* n("[0,7] 4 [2,7] 4")
* .scale("C:<major minor>/2")
* .s("piano")
* "0 2 4 6 4 2"
* .scale("C2:<major minor>")
* .note()
* @example
* n(rand.range(0,12).segment(8).round())
* .scale("C:ritusen")
* "0 1 2 3 4 5 6 7".rev().scale("C2:<major minor>").note()
* .s("folkharp")
*/
-190
View File
@@ -1,190 +0,0 @@
import { isNote, isNoteWithOctave, _mod, noteToMidi, tokenizeNote } from '@strudel.cycles/core';
import { Interval } from '@tonaljs/tonal';
// https://codesandbox.io/s/stateless-voicings-g2tmz0?file=/src/lib.js:0-2515
const flats = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
const pcs = ['c', 'db', 'd', 'eb', 'e', 'f', 'gb', 'g', 'ab', 'a', 'bb', 'b'];
const sharps = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
const accs = { b: -1, '#': 1 };
export const pc2chroma = (pc) => {
const [letter, ...rest] = pc.split('');
return pcs.indexOf(letter.toLowerCase()) + rest.reduce((sum, sign) => sum + accs[sign], 0);
};
export const rotateChroma = (chroma, steps) => (chroma + (steps % 12) + 12) % 12;
export const chroma2pc = (chroma, sharp = false) => {
return (sharp ? sharps : flats)[chroma];
};
export function tokenizeChord(chord) {
const match = (chord || '').match(/^([A-G][b#]*)([^/]*)[/]?([A-G][b#]*)?$/);
if (!match) {
// console.warn('could not tokenize chord', chord);
return [];
}
return match.slice(1);
}
export const note2pc = (note) => note.match(/^[A-G][#b]?/i)[0];
export const note2oct = (note) => tokenizeNote(note)[2];
export const note2chroma = (note) => {
return pc2chroma(note2pc(note));
};
// TODO: test
export const midi2chroma = (midi) => midi % 12;
// TODO: test and use in voicing function
export const pitch2chroma = (x, defaultOctave) => {
if (isNoteWithOctave(x)) {
return note2chroma(x);
}
if (isNote(x)) {
//pc
return pc2chroma(x, defaultOctave);
}
if (typeof x === 'number') {
// expect midi
return midi2chroma(x);
}
};
export const step2semitones = (x) => {
let num = Number(x);
if (!isNaN(num)) {
return num;
}
return Interval.semitones(x);
};
export const x2midi = (x) => {
if (typeof x === 'number') {
return x;
}
if (typeof x === 'string') {
return noteToMidi(x);
}
};
// duplicate: util.mjs (does not support sharp flag)
export const midi2note = (midi, sharp = false) => {
const oct = Math.floor(midi / 12) - 1;
const pc = (sharp ? sharps : flats)[midi % 12];
return pc + oct;
};
export function scaleStep(notes, offset, octaves = 1) {
notes = notes.map((note) => (typeof note === 'string' ? noteToMidi(note) : note));
const octOffset = Math.floor(offset / notes.length) * octaves * 12;
offset = _mod(offset, notes.length);
return notes[offset] + octOffset;
}
// different ways to resolve the note to compare the anchor to (see renderVoicing)
let modeTarget = {
below: (v) => v.slice(-1)[0],
duck: (v) => v.slice(-1)[0],
above: (v) => v[0],
};
export function renderVoicing({ chord, dictionary, offset = 0, n, mode = 'below', anchor = 'c5', octaves = 1 }) {
const [root, symbol] = tokenizeChord(chord);
const rootChroma = pc2chroma(root);
anchor = anchor?.note || anchor;
const anchorChroma = pitch2chroma(anchor);
const voicings = dictionary[symbol].map((voicing) =>
(typeof voicing === 'string' ? voicing.split(' ') : voicing).map(step2semitones),
);
let minDistance, bestIndex;
// calculate distances up from voicing top notes
let chromaDiffs = voicings.map((v, i) => {
const targetStep = modeTarget[mode](v);
const diff = _mod(anchorChroma - targetStep - rootChroma, 12);
if (minDistance === undefined || diff < minDistance) {
minDistance = diff;
bestIndex = i;
}
return diff;
});
const octDiff = Math.ceil(offset / voicings.length) * 12;
const indexWithOffset = _mod(bestIndex + offset, voicings.length);
const voicing = voicings[indexWithOffset];
const targetStep = modeTarget[mode](voicing);
const anchorMidi = noteToMidi(anchor, 4) - chromaDiffs[indexWithOffset] + octDiff;
const voicingMidi = voicing.map((v) => anchorMidi - targetStep + v);
let notes = voicingMidi.map((n) => midi2note(n));
if (mode === 'duck') {
notes = notes.filter((_, i) => voicingMidi[i] !== noteToMidi(anchor));
}
if (n !== undefined) {
return [scaleStep(notes, n, octaves)];
}
return notes;
}
// https://github.com/tidalcycles/strudel/blob/14184993d0ee7d69c47df57ac864a1a0f99a893f/packages/tonal/tonleiter.mjs
const steps = [1, 0, 2, 0, 3, 4, 0, 5, 0, 6, 0, 7];
const notes = ['C', '', 'D', '', 'E', 'F', '', 'G', '', 'A', '', 'B'];
const noteLetters = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
export const accidentalOffset = (accidentals) => {
return accidentals.split('#').length - accidentals.split('b').length;
};
const accidentalString = (offset) => {
if (offset < 0) {
return 'b'.repeat(-offset);
}
if (offset > 0) {
return '#'.repeat(offset);
}
return '';
};
export const Step = {
tokenize(step) {
const matches = step.match(/^([#b]*)([1-9][0-9]*)$/);
if (!matches) {
throw new Error(`Step.tokenize: not a valid step: ${step}`);
}
const [accidentals, stepNumber] = matches.slice(1);
return [accidentals, parseInt(stepNumber)];
},
accidentals(step) {
return accidentalOffset(Step.tokenize(step)[0]);
},
};
export const Note = {
// TODO: support octave numbers
tokenize(note) {
return [note[0], note.slice(1)];
},
accidentals(note) {
return accidentalOffset(this.tokenize(note)[1]);
},
};
// TODO: support octave numbers
export function transpose(note, step) {
// example: E, 3
const stepNumber = Step.tokenize(step)[1]; // 3
const noteLetter = Note.tokenize(note)[0]; // E
const noteIndex = noteLetters.indexOf(noteLetter); // 2 "E is C+2"
const targetNote = noteLetters[(noteIndex + stepNumber - 1) % 8]; // G "G is a third above E"
const rootIndex = notes.indexOf(noteLetter); // 4 "E is 4 semitones above C"
const targetIndex = notes.indexOf(targetNote); // 7 "G is 7 semitones above C"
const indexOffset = targetIndex - rootIndex; // 3 (E to G is normally a 3 semitones)
const stepIndex = steps.indexOf(stepNumber); // 4 ("3" is normally 4 semitones)
const offsetAccidentals = accidentalString(Step.accidentals(step) + Note.accidentals(note) + stepIndex - indexOffset); // "we need to add a # to to the G to make it a major third from E"
return [targetNote, offsetAccidentals].join('');
}
//Note("Bb3").transpose("c3")
+5 -105
View File
@@ -5,9 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { stack, register } from '@strudel.cycles/core';
import { renderVoicing } from './tonleiter.mjs';
import _voicings from 'chord-voicings';
import { complex, simple } from './ireal.mjs';
const { dictionaryVoicing, minTopNoteDiff } = _voicings.default || _voicings; // parcel module resolution fuckup
const lefthand = {
@@ -51,33 +49,10 @@ const triads = {
aug: ['1P 3m 5A', '3m 5A 8P', '5A 8P 10m'],
};
const defaultDictionary = {
// triads
'': ['1P 3M 5P', '3M 5P 8P', '5P 8P 10M'],
M: ['1P 3M 5P', '3M 5P 8P', '5P 8P 10M'],
m: ['1P 3m 5P', '3m 5P 8P', '5P 8P 10m'],
o: ['1P 3m 5d', '3m 5d 8P', '5d 8P 10m'],
aug: ['1P 3m 5A', '3m 5A 8P', '5A 8P 10m'],
// sevenths chords
m7: ['3m 5P 7m 9M', '7m 9M 10m 12P'],
7: ['3M 6M 7m 9M', '7m 9M 10M 13M'],
'^7': ['3M 5P 7M 9M', '7M 9M 10M 12P'],
69: ['3M 5P 6A 9M'],
m7b5: ['3m 5d 7m 8P', '7m 8P 10m 12d'],
'7b9': ['3M 6m 7m 9m', '7m 9m 10M 13m'],
'7b13': ['3M 6m 7m 9m', '7m 9m 10M 13m'],
o7: ['1P 3m 5d 6M', '5d 6M 8P 10m'],
'7#11': ['7m 9M 11A 13A'],
'7#9': ['3M 7m 9A'],
mM7: ['3m 5P 7M 9M', '7M 9M 10m 12P'],
m6: ['3m 5P 6M 9M', '6M 9M 10m 12P'],
};
export const voicingRegistry = {
lefthand: { dictionary: lefthand, range: ['F3', 'A4'], mode: 'below', anchor: 'a4' },
triads: { dictionary: triads, mode: 'below', anchor: 'a4' },
guidetones: { dictionary: guidetones, mode: 'above', anchor: 'a4' },
default: { dictionary: defaultDictionary, mode: 'below', anchor: 'a4' },
lefthand: { dictionary: lefthand, range: ['F3', 'A4'] },
triads: { dictionary: triads },
guidetones: { dictionary: guidetones },
};
export const setVoicingRange = (name, range) => addVoicings(name, voicingRegistry[name].dictionary, range);
@@ -106,11 +81,6 @@ export const addVoicings = (name, dictionary, range = ['F3', 'A4']) => {
Object.assign(voicingRegistry, { [name]: { dictionary, range } });
};
// new call signature
export const registerVoicings = (name, dictionary, options = {}) => {
Object.assign(voicingRegistry, { [name]: { dictionary, ...options } });
};
const getVoicing = (chord, dictionaryName, lastVoicing) => {
const { dictionary, range } = voicingRegistry[dictionaryName];
return dictionaryVoicing({
@@ -123,7 +93,6 @@ const getVoicing = (chord, dictionaryName, lastVoicing) => {
};
/**
* DEPRECATED: still works, but it is recommended you use .voicing instead (without s).
* Turns chord symbols into voicings, using the smoothest voice leading possible.
* Uses [chord-voicings package](https://github.com/felixroos/chord-voicings#chord-voicings).
*
@@ -160,76 +129,7 @@ export const voicings = register('voicings', function (dictionary, pat) {
*/
export const rootNotes = register('rootNotes', function (octave, pat) {
return pat.fmap((value) => {
const chord = value.chord || value;
const root = chord.match(/^([a-gA-G][b#]?).*$/)[1];
const note = root + octave;
return value.chord ? { note } : note;
const root = value.match(/^([a-gA-G][b#]?).*$/)[1];
return root + octave;
});
});
/**
* Turns chord symbols into voicings. You can use the following control params:
*
* - `chord`: Note, followed by chord symbol, e.g. C Am G7 Bb^7
* - `dict`: voicing dictionary to use, falls back to default dictionary
* - `anchor`: the note that is used to align the chord
* - `mode`: how the voicing is aligned to the anchor
* - `below`: top note <= anchor
* - `duck`: top note <= anchor, anchor excluded
* - `above`: bottom note >= anchor
* - `offset`: whole number that shifts the voicing up or down to the next voicing
* - `n`: if set, the voicing is played like a scale. Overshooting numbers will be octaved
*
* All of the above controls are optional, except `chord`.
* If you pass a pattern of strings to voicing, they will be interpreted as chords.
*
* @name voicing
* @param {string} dictionary which voicing dictionary to use.
* @returns Pattern
* @example
* voicing("<C Am F G>")
* @example
* n("0 1 2 3 4 5 6 7").chord("<C Am F G>").voicing()
*/
export const voicing = register('voicing', function (pat) {
return pat
.fmap((value) => {
// destructure voicing controls out
value = typeof value === 'string' ? { chord: value } : value;
let { dictionary = 'default', chord, anchor, offset, mode, n, octaves, ...rest } = value;
dictionary =
typeof dictionary === 'string' ? voicingRegistry[dictionary] : { dictionary, mode: 'below', anchor: 'c5' };
let notes = renderVoicing({ ...dictionary, chord, anchor, offset, mode, n, octaves });
return stack(...notes)
.note()
.set(rest); // rest does not include voicing controls anymore!
})
.outerJoin();
});
export function voicingAlias(symbol, alias, setOrSets) {
setOrSets = !Array.isArray(setOrSets) ? [setOrSets] : setOrSets;
setOrSets.forEach((set) => {
set[alias] = set[symbol];
});
}
// no symbol = major chord
voicingAlias('^', '', [simple, complex]);
Object.keys(simple).forEach((symbol) => {
// add aliases for "-" === "m"
if (symbol.includes('-')) {
let alias = symbol.replace('-', 'm');
voicingAlias(symbol, alias, [complex, simple]);
}
// add aliases for "^" === "M"
if (symbol.includes('^')) {
let alias = symbol.replace('^', 'M');
voicingAlias(symbol, alias, [complex, simple]);
}
});
registerVoicings('ireal', simple);
registerVoicings('ireal-ext', complex);
+1 -1
View File
@@ -38,6 +38,6 @@
},
"devDependencies": {
"vite": "^4.3.3",
"vitest": "^0.33.0"
"vitest": "^0.28.0"
}
}
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="https://strudel.tidalcycles.org/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@strudel/web REPL Example</title>
</head>
<body>
<div id="app"></div>
<canvas style="background: #000" width="320" height="320" id="canvas"></canvas><br />
<button id="a">A</button>
<button id="b">B</button>
<button id="c">C</button>
<button id="stop">stop</button>
<script type="module">
import { initStrudel } from '@strudel/web';
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let drawer;
let init = initStrudel({
prebake: () => samples('github:tidalcycles/Dirt-Samples/master'),
onToggle: async (started) => {
const { scheduler } = await init;
if (started) {
drawer.start(scheduler);
} else {
drawer.stop();
}
},
}).then(({ scheduler }) => {
drawer = new Drawer(
(haps, time, { drawTime }) => {
scheduler.pattern.context?.onPaint?.(ctx, time, haps, drawTime);
},
[-2, 2],
);
return { scheduler };
});
const click = (id, action) => document.getElementById(id).addEventListener('click', action);
click('a', () => evaluate(`s('bd,jvbass(3,8)').jux(rev).spiral({size:20})`));
click('b', () => s('bd*2,hh(3,4),jvbass(5,8,1)').jux(rev).spiral({ size: 20, steady: 0.9 }).play());
click('c', () => s('bd*2,hh(3,4),jvbass:[0 4](5,8,1)').jux(rev).stack(s('~ sd')).punchcard().play());
click('stop', () => hush());
</script>
</body>
</html>
+3 -1
View File
@@ -29,7 +29,7 @@ export async function defaultPrebake() {
let initDone;
let scheduler;
export function initStrudel(options = {}) {
export async function initStrudel(options = {}) {
initAudioOnFirstClick();
miniAllStrings();
const { prebake, ...schedulerOptions } = options;
@@ -39,6 +39,8 @@ export function initStrudel(options = {}) {
await prebake?.();
})();
scheduler = webaudioScheduler(schedulerOptions);
await initDone;
return { scheduler };
}
window.initStrudel = initStrudel;
+1 -1
View File
@@ -34,6 +34,6 @@
},
"devDependencies": {
"vite": "^4.3.3",
"vitest": "^0.33.0"
"vitest": "^0.28.0"
}
}
+147 -135
View File
@@ -66,8 +66,8 @@ importers:
specifier: ^5.8.1
version: 5.9.0
vitest:
specifier: ^0.33.0
version: 0.33.0(@vitest/ui@0.28.0)
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
packages/codemirror:
dependencies:
@@ -107,8 +107,8 @@ importers:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.33.0
version: 0.33.0(@vitest/ui@0.28.0)
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
packages/core/examples/vite-vanilla-repl:
dependencies:
@@ -207,8 +207,8 @@ importers:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.33.0
version: 0.33.0(@vitest/ui@0.28.0)
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
packages/osc:
dependencies:
@@ -405,8 +405,8 @@ importers:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.33.0
version: 0.33.0(@vitest/ui@0.28.0)
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
packages/transpiler:
dependencies:
@@ -430,8 +430,8 @@ importers:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.33.0
version: 0.33.0(@vitest/ui@0.28.0)
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
packages/web:
dependencies:
@@ -488,8 +488,8 @@ importers:
specifier: ^4.3.3
version: 4.3.3(@types/node@18.16.3)
vitest:
specifier: ^0.33.0
version: 0.33.0(@vitest/ui@0.28.0)
specifier: ^0.28.0
version: 0.28.0(@vitest/ui@0.28.0)
website:
dependencies:
@@ -2698,13 +2698,6 @@ packages:
'@sinclair/typebox': 0.25.24
dev: true
/@jest/schemas@29.6.0:
resolution: {integrity: sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@sinclair/typebox': 0.27.8
dev: true
/@jridgewell/gen-mapping@0.1.1:
resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
engines: {node: '>=6.0.0'}
@@ -2738,10 +2731,6 @@ packages:
/@jridgewell/sourcemap-codec@1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
/@jridgewell/sourcemap-codec@1.4.15:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
dev: true
/@jridgewell/trace-mapping@0.3.17:
resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
dependencies:
@@ -3636,10 +3625,6 @@ packages:
resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==}
dev: true
/@sinclair/typebox@0.27.8:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
dev: true
/@supabase/functions-js@2.1.1:
resolution: {integrity: sha512-bIR1Puae6W+1/MzPfYBWOG/SCWGo4B5CB7c0ZZksvliNEAzhxNBJ0UFKYINcGdGtxG8ZC+1xr3utWpNZNwnoRw==}
dependencies:
@@ -4046,11 +4031,11 @@ packages:
/@types/chai-subset@1.3.3:
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
dependencies:
'@types/chai': 4.3.5
'@types/chai': 4.3.4
dev: true
/@types/chai@4.3.5:
resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==}
/@types/chai@4.3.4:
resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==}
dev: true
/@types/debug@4.1.7:
@@ -4126,6 +4111,10 @@ packages:
dependencies:
'@types/unist': 2.0.6
/@types/node@18.11.18:
resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
dev: true
/@types/node@18.16.3:
resolution: {integrity: sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==}
@@ -4572,34 +4561,26 @@ packages:
- supports-color
dev: true
/@vitest/expect@0.33.0:
resolution: {integrity: sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==}
/@vitest/expect@0.28.0:
resolution: {integrity: sha512-GSk1k9/W8JcVHFhCqVX/MdV9SbVbOV4p7U293f7f9xpNK17c1oA47OErsdL0a5lwJoS82RT4ZieTuE1Q9egeaQ==}
dependencies:
'@vitest/spy': 0.33.0
'@vitest/utils': 0.33.0
'@vitest/spy': 0.28.0
'@vitest/utils': 0.28.0
chai: 4.3.7
dev: true
/@vitest/runner@0.33.0:
resolution: {integrity: sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==}
/@vitest/runner@0.28.0:
resolution: {integrity: sha512-SXQO9aubp7Hg4DV4D5DP70wJ/4o0krH1gAPrSt+rhEZQbQvMaBJAHWOxEibwzLkklgoHreaMEvETFILkGQWXww==}
dependencies:
'@vitest/utils': 0.33.0
'@vitest/utils': 0.28.0
p-limit: 4.0.0
pathe: 1.1.1
pathe: 1.1.0
dev: true
/@vitest/snapshot@0.33.0:
resolution: {integrity: sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==}
/@vitest/spy@0.28.0:
resolution: {integrity: sha512-gYBDQIP0QDvxrscl2Id0BTbzLUbuAzFiFur3eHxH9Yt5cM6YCH/kxBrSHhmXTbu92UenLx53Gwq17u5N0zGNDQ==}
dependencies:
magic-string: 0.30.1
pathe: 1.1.1
pretty-format: 29.6.1
dev: true
/@vitest/spy@0.33.0:
resolution: {integrity: sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==}
dependencies:
tinyspy: 2.1.1
tinyspy: 1.0.2
dev: true
/@vitest/ui@0.28.0:
@@ -4612,12 +4593,14 @@ packages:
sirv: 2.0.2
dev: true
/@vitest/utils@0.33.0:
resolution: {integrity: sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==}
/@vitest/utils@0.28.0:
resolution: {integrity: sha512-Dt+jDZbwriZWzJ5Hi9nAUnz9IPgNb+ACE96tWiXPp/u9NmCYWIWcuNoUOYS8HQyGFz31GiNYGvaZ4ZEDjAgi1g==}
dependencies:
diff-sequences: 29.4.3
cli-truncate: 3.1.0
diff: 5.1.0
loupe: 2.3.6
pretty-format: 29.6.1
picocolors: 1.0.0
pretty-format: 27.5.1
dev: true
/@vscode/emmet-helper@2.8.6:
@@ -4686,12 +4669,6 @@ packages:
engines: {node: '>=0.4.0'}
dev: true
/acorn@8.10.0:
resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/acorn@8.8.2:
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
engines: {node: '>=0.4.0'}
@@ -5537,6 +5514,14 @@ packages:
resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==}
engines: {node: '>=6'}
/cli-truncate@3.1.0:
resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
slice-ansi: 5.0.0
string-width: 5.1.2
dev: true
/cli-width@3.0.0:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
engines: {node: '>= 10'}
@@ -6193,11 +6178,6 @@ packages:
/didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
/diff-sequences@29.4.3:
resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dev: true
/diff@5.1.0:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
engines: {node: '>=0.3.1'}
@@ -8043,6 +8023,11 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
/is-fullwidth-code-point@4.0.0:
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
engines: {node: '>=12'}
dev: true
/is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -8813,13 +8798,6 @@ packages:
dependencies:
'@jridgewell/sourcemap-codec': 1.4.14
/magic-string@0.30.1:
resolution: {integrity: sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/make-dir@2.1.0:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
engines: {node: '>=6'}
@@ -9673,13 +9651,13 @@ packages:
engines: {node: '>=10'}
hasBin: true
/mlly@1.4.0:
resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==}
/mlly@1.2.0:
resolution: {integrity: sha512-+c7A3CV0KGdKcylsI6khWyts/CYrGTrRVo4R/I7u/cUsy0Conxa6LUhiEzVKIw14lc2L5aiO4+SeVe4TeGRKww==}
dependencies:
acorn: 8.10.0
pathe: 1.1.1
pkg-types: 1.0.3
ufo: 1.1.2
acorn: 8.8.2
pathe: 1.1.0
pkg-types: 1.0.2
ufo: 1.1.1
dev: true
/modify-values@1.0.1:
@@ -10658,10 +10636,6 @@ packages:
resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==}
dev: true
/pathe@1.1.1:
resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
dev: true
/pathval@1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
dev: true
@@ -10735,12 +10709,12 @@ packages:
- supports-color
dev: true
/pkg-types@1.0.3:
resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
/pkg-types@1.0.2:
resolution: {integrity: sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==}
dependencies:
jsonc-parser: 3.2.0
mlly: 1.4.0
pathe: 1.1.1
mlly: 1.2.0
pathe: 1.1.0
dev: true
/pkg@5.8.1:
@@ -10936,6 +10910,15 @@ packages:
engines: {node: ^14.13.1 || >=16.0.0}
dev: true
/pretty-format@27.5.1:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
dependencies:
ansi-regex: 5.0.1
ansi-styles: 5.2.0
react-is: 17.0.2
dev: true
/pretty-format@29.4.3:
resolution: {integrity: sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -10945,15 +10928,6 @@ packages:
react-is: 18.2.0
dev: true
/pretty-format@29.6.1:
resolution: {integrity: sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/schemas': 29.6.0
ansi-styles: 5.2.0
react-is: 18.2.0
dev: true
/prismjs@1.29.0:
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
engines: {node: '>=6'}
@@ -11101,6 +11075,10 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
dev: true
/react-is@18.2.0:
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
dev: true
@@ -11864,6 +11842,14 @@ packages:
resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==}
engines: {node: '>=12'}
/slice-ansi@5.0.0:
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
engines: {node: '>=12'}
dependencies:
ansi-styles: 6.2.1
is-fullwidth-code-point: 4.0.0
dev: true
/smart-buffer@4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
@@ -12014,8 +12000,8 @@ packages:
tslib: 2.5.0
dev: false
/std-env@3.3.3:
resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==}
/std-env@3.3.2:
resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==}
dev: true
/stdopt@2.2.0:
@@ -12397,7 +12383,7 @@ packages:
hasBin: true
dependencies:
'@jridgewell/source-map': 0.3.2
acorn: 8.10.0
acorn: 8.8.2
commander: 2.20.3
source-map-support: 0.5.21
dev: true
@@ -12476,13 +12462,13 @@ packages:
resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==}
dev: true
/tinypool@0.6.0:
resolution: {integrity: sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==}
/tinypool@0.3.1:
resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==}
engines: {node: '>=14.0.0'}
dev: true
/tinyspy@2.1.1:
resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==}
/tinyspy@1.0.2:
resolution: {integrity: sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==}
engines: {node: '>=14.0.0'}
dev: true
@@ -12719,8 +12705,8 @@ packages:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
dev: true
/ufo@1.1.2:
resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==}
/ufo@1.1.1:
resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==}
dev: true
/uglify-js@3.17.4:
@@ -13053,17 +13039,19 @@ packages:
replace-ext: 1.0.1
dev: false
/vite-node@0.33.0(@types/node@18.16.3):
resolution: {integrity: sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==}
engines: {node: '>=v14.18.0'}
/vite-node@0.28.0(@types/node@18.11.18):
resolution: {integrity: sha512-4w+hFGfAfsfCchVpZFkIEEEGxF+OA1lVIPc7Dijf/k/nJRUGU80RWnDubA0jmc7CApg/UbnSkIsYGh8v4eJ1HA==}
engines: {node: '>=v14.16.0'}
hasBin: true
dependencies:
cac: 6.7.14
debug: 4.3.4
mlly: 1.4.0
pathe: 1.1.1
mlly: 1.2.0
pathe: 1.1.0
picocolors: 1.0.0
vite: 4.3.3(@types/node@18.16.3)
source-map: 0.6.1
source-map-support: 0.5.21
vite: 4.3.3(@types/node@18.11.18)
transitivePeerDependencies:
- '@types/node'
- less
@@ -13093,6 +13081,39 @@ packages:
- supports-color
dev: true
/vite@4.3.3(@types/node@18.11.18):
resolution: {integrity: sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
'@types/node': '>= 14'
less: '*'
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
dependencies:
'@types/node': 18.11.18
esbuild: 0.17.18
postcss: 8.4.23
rollup: 3.21.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/vite@4.3.3(@types/node@18.16.3):
resolution: {integrity: sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -13135,9 +13156,9 @@ packages:
dependencies:
vite: 4.3.3(@types/node@18.16.3)
/vitest@0.33.0(@vitest/ui@0.28.0):
resolution: {integrity: sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==}
engines: {node: '>=v14.18.0'}
/vitest@0.28.0(@vitest/ui@0.28.0):
resolution: {integrity: sha512-qgPw3vHEOc36wJBknmmeWIpuaLnXtWZinkeT8aOTyIuBYipgpabVzWjWgCilKDa8F+ZrY2cHeH2xGUCYOc5/XA==}
engines: {node: '>=v14.16.0'}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
@@ -13145,9 +13166,6 @@ packages:
'@vitest/ui': '*'
happy-dom: '*'
jsdom: '*'
playwright: '*'
safaridriver: '*'
webdriverio: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
@@ -13159,37 +13177,31 @@ packages:
optional: true
jsdom:
optional: true
playwright:
optional: true
safaridriver:
optional: true
webdriverio:
optional: true
dependencies:
'@types/chai': 4.3.5
'@types/chai': 4.3.4
'@types/chai-subset': 1.3.3
'@types/node': 18.16.3
'@vitest/expect': 0.33.0
'@vitest/runner': 0.33.0
'@vitest/snapshot': 0.33.0
'@vitest/spy': 0.33.0
'@types/node': 18.11.18
'@vitest/expect': 0.28.0
'@vitest/runner': 0.28.0
'@vitest/spy': 0.28.0
'@vitest/ui': 0.28.0
'@vitest/utils': 0.33.0
acorn: 8.10.0
'@vitest/utils': 0.28.0
acorn: 8.8.2
acorn-walk: 8.2.0
cac: 6.7.14
chai: 4.3.7
debug: 4.3.4
local-pkg: 0.4.3
magic-string: 0.30.1
pathe: 1.1.1
pathe: 1.1.0
picocolors: 1.0.0
std-env: 3.3.3
source-map: 0.6.1
std-env: 3.3.2
strip-literal: 1.0.1
tinybench: 2.5.0
tinypool: 0.6.0
vite: 4.3.3(@types/node@18.16.3)
vite-node: 0.33.0(@types/node@18.16.3)
tinypool: 0.3.1
tinyspy: 1.0.2
vite: 4.3.3(@types/node@18.11.18)
vite-node: 0.28.0(@types/node@18.11.18)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
+106 -189
View File
@@ -1,4 +1,4 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
// Vitest Snapshot v1
exports[`runs examples > example "_euclidRot" example index 0 1`] = `
[
@@ -3545,96 +3545,96 @@ exports[`runs examples > example "saw" example index 1 1`] = `
exports[`runs examples > example "scale" example index 0 1`] = `
[
"[ 0/1 → 1/6 | n:0 note:C3 ]",
"[ 1/6 → 1/3 | n:2 note:E3 ]",
"[ 1/3 → 1/2 | n:4 note:G3 ]",
"[ 1/2 → 2/3 | n:6 note:B3 ]",
"[ 2/3 → 5/6 | n:4 note:G3 ]",
"[ 5/6 → 1/1 | n:2 note:E3 ]",
"[ 1/1 → 7/6 | n:0 note:C3 ]",
"[ 7/6 → 4/3 | n:2 note:E3 ]",
"[ 4/3 → 3/2 | n:4 note:G3 ]",
"[ 3/2 → 5/3 | n:6 note:B3 ]",
"[ 5/3 → 11/6 | n:4 note:G3 ]",
"[ 11/6 → 2/1 | n:2 note:E3 ]",
"[ 2/1 → 13/6 | n:0 note:C3 ]",
"[ 13/6 → 7/3 | n:2 note:E3 ]",
"[ 7/3 → 5/2 | n:4 note:G3 ]",
"[ 5/2 → 8/3 | n:6 note:B3 ]",
"[ 8/3 → 17/6 | n:4 note:G3 ]",
"[ 17/6 → 3/1 | n:2 note:E3 ]",
"[ 3/1 → 19/6 | n:0 note:C3 ]",
"[ 19/6 → 10/3 | n:2 note:E3 ]",
"[ 10/3 → 7/2 | n:4 note:G3 ]",
"[ 7/2 → 11/3 | n:6 note:B3 ]",
"[ 11/3 → 23/6 | n:4 note:G3 ]",
"[ 23/6 → 4/1 | n:2 note:E3 ]",
"[ 0/1 → 1/6 | note:C2 ]",
"[ 1/6 → 1/3 | note:E2 ]",
"[ 1/3 → 1/2 | note:G2 ]",
"[ 1/2 → 2/3 | note:B2 ]",
"[ 2/3 → 5/6 | note:G2 ]",
"[ 5/6 → 1/1 | note:E2 ]",
"[ 1/1 → 7/6 | note:C2 ]",
"[ 7/6 → 4/3 | note:E2 ]",
"[ 4/3 → 3/2 | note:G2 ]",
"[ 3/2 → 5/3 | note:B2 ]",
"[ 5/3 → 11/6 | note:G2 ]",
"[ 11/6 → 2/1 | note:E2 ]",
"[ 2/1 → 13/6 | note:C2 ]",
"[ 13/6 → 7/3 | note:E2 ]",
"[ 7/3 → 5/2 | note:G2 ]",
"[ 5/2 → 8/3 | note:B2 ]",
"[ 8/3 → 17/6 | note:G2 ]",
"[ 17/6 → 3/1 | note:E2 ]",
"[ 3/1 → 19/6 | note:C2 ]",
"[ 19/6 → 10/3 | note:E2 ]",
"[ 10/3 → 7/2 | note:G2 ]",
"[ 7/2 → 11/3 | note:B2 ]",
"[ 11/3 → 23/6 | note:G2 ]",
"[ 23/6 → 4/1 | note:E2 ]",
]
`;
exports[`runs examples > example "scale" example index 1 1`] = `
[
"[ 0/1 → 1/4 | n:0 note:C3 s:piano ]",
"[ 0/1 → 1/4 | n:7 note:C4 s:piano ]",
"[ 1/4 → 1/2 | n:4 note:G3 s:piano ]",
"[ 1/2 → 3/4 | n:2 note:E3 s:piano ]",
"[ 1/2 → 3/4 | n:7 note:C4 s:piano ]",
"[ 3/4 → 1/1 | n:4 note:G3 s:piano ]",
"[ 1/1 → 5/4 | n:0 note:C3 s:piano ]",
"[ 1/1 → 5/4 | n:7 note:C4 s:piano ]",
"[ 5/4 → 3/2 | n:4 note:G3 s:piano ]",
"[ 3/2 → 7/4 | n:2 note:E3 s:piano ]",
"[ 3/2 → 7/4 | n:7 note:C4 s:piano ]",
"[ 7/4 → 2/1 | n:4 note:G3 s:piano ]",
"[ 2/1 → 9/4 | n:0 note:C3 s:piano ]",
"[ 2/1 → 9/4 | n:7 note:C4 s:piano ]",
"[ 9/4 → 5/2 | n:4 note:G3 s:piano ]",
"[ 5/2 → 11/4 | n:2 note:Eb3 s:piano ]",
"[ 5/2 → 11/4 | n:7 note:C4 s:piano ]",
"[ 11/4 → 3/1 | n:4 note:G3 s:piano ]",
"[ 3/1 → 13/4 | n:0 note:C3 s:piano ]",
"[ 3/1 → 13/4 | n:7 note:C4 s:piano ]",
"[ 13/4 → 7/2 | n:4 note:G3 s:piano ]",
"[ 7/2 → 15/4 | n:2 note:Eb3 s:piano ]",
"[ 7/2 → 15/4 | n:7 note:C4 s:piano ]",
"[ 15/4 → 4/1 | n:4 note:G3 s:piano ]",
"[ 0/1 → 1/6 | note:C2 ]",
"[ 1/6 → 1/3 | note:E2 ]",
"[ 1/3 → 1/2 | note:G2 ]",
"[ 1/2 → 2/3 | note:B2 ]",
"[ 2/3 → 5/6 | note:G2 ]",
"[ 5/6 → 1/1 | note:E2 ]",
"[ 1/1 → 7/6 | note:C2 ]",
"[ 7/6 → 4/3 | note:Eb2 ]",
"[ 4/3 → 3/2 | note:G2 ]",
"[ 3/2 → 5/3 | note:Bb2 ]",
"[ 5/3 → 11/6 | note:G2 ]",
"[ 11/6 → 2/1 | note:Eb2 ]",
"[ 2/1 → 13/6 | note:C2 ]",
"[ 13/6 → 7/3 | note:E2 ]",
"[ 7/3 → 5/2 | note:G2 ]",
"[ 5/2 → 8/3 | note:B2 ]",
"[ 8/3 → 17/6 | note:G2 ]",
"[ 17/6 → 3/1 | note:E2 ]",
"[ 3/1 → 19/6 | note:C2 ]",
"[ 19/6 → 10/3 | note:Eb2 ]",
"[ 10/3 → 7/2 | note:G2 ]",
"[ 7/2 → 11/3 | note:Bb2 ]",
"[ 11/3 → 23/6 | note:G2 ]",
"[ 23/6 → 4/1 | note:Eb2 ]",
]
`;
exports[`runs examples > example "scale" example index 2 1`] = `
[
"[ 0/1 → 1/8 | n:10 note:C5 s:folkharp ]",
"[ 1/8 → 1/4 | n:2 note:F3 s:folkharp ]",
"[ 1/4 → 3/8 | n:7 note:F4 s:folkharp ]",
"[ 3/8 → 1/2 | n:4 note:A3 s:folkharp ]",
"[ 1/2 → 5/8 | n:2 note:F3 s:folkharp ]",
"[ 5/8 → 3/4 | n:5 note:C4 s:folkharp ]",
"[ 3/4 → 7/8 | n:9 note:A4 s:folkharp ]",
"[ 7/8 → 1/1 | n:8 note:G4 s:folkharp ]",
"[ 1/1 → 9/8 | n:7 note:F4 s:folkharp ]",
"[ 9/8 → 5/4 | n:1 note:D3 s:folkharp ]",
"[ 5/4 → 11/8 | n:1 note:D3 s:folkharp ]",
"[ 11/8 → 3/2 | n:6 note:D4 s:folkharp ]",
"[ 3/2 → 13/8 | n:2 note:F3 s:folkharp ]",
"[ 13/8 → 7/4 | n:4 note:A3 s:folkharp ]",
"[ 7/4 → 15/8 | n:6 note:D4 s:folkharp ]",
"[ 15/8 → 2/1 | n:10 note:C5 s:folkharp ]",
"[ 2/1 → 17/8 | n:4 note:A3 s:folkharp ]",
"[ 17/8 → 9/4 | n:0 note:C3 s:folkharp ]",
"[ 9/4 → 19/8 | n:8 note:G4 s:folkharp ]",
"[ 19/8 → 5/2 | n:2 note:F3 s:folkharp ]",
"[ 5/2 → 21/8 | n:7 note:F4 s:folkharp ]",
"[ 21/8 → 11/4 | n:6 note:D4 s:folkharp ]",
"[ 11/4 → 23/8 | n:11 note:D5 s:folkharp ]",
"[ 23/8 → 3/1 | n:3 note:G3 s:folkharp ]",
"[ 3/1 → 25/8 | n:0 note:C3 s:folkharp ]",
"[ 25/8 → 13/4 | n:11 note:D5 s:folkharp ]",
"[ 13/4 → 27/8 | n:4 note:A3 s:folkharp ]",
"[ 27/8 → 7/2 | n:9 note:A4 s:folkharp ]",
"[ 7/2 → 29/8 | n:10 note:C5 s:folkharp ]",
"[ 29/8 → 15/4 | n:12 note:F5 s:folkharp ]",
"[ 15/4 → 31/8 | n:1 note:D3 s:folkharp ]",
"[ 31/8 → 4/1 | n:4 note:A3 s:folkharp ]",
"[ 0/1 → 1/8 | note:C3 s:folkharp ]",
"[ 1/8 → 1/4 | note:B2 s:folkharp ]",
"[ 1/4 → 3/8 | note:A2 s:folkharp ]",
"[ 3/8 → 1/2 | note:G2 s:folkharp ]",
"[ 1/2 → 5/8 | note:F2 s:folkharp ]",
"[ 5/8 → 3/4 | note:E2 s:folkharp ]",
"[ 3/4 → 7/8 | note:D2 s:folkharp ]",
"[ 7/8 → 1/1 | note:C2 s:folkharp ]",
"[ 1/1 → 9/8 | note:C3 s:folkharp ]",
"[ 9/8 → 5/4 | note:Bb2 s:folkharp ]",
"[ 5/4 → 11/8 | note:Ab2 s:folkharp ]",
"[ 11/8 → 3/2 | note:G2 s:folkharp ]",
"[ 3/2 → 13/8 | note:F2 s:folkharp ]",
"[ 13/8 → 7/4 | note:Eb2 s:folkharp ]",
"[ 7/4 → 15/8 | note:D2 s:folkharp ]",
"[ 15/8 → 2/1 | note:C2 s:folkharp ]",
"[ 2/1 → 17/8 | note:C3 s:folkharp ]",
"[ 17/8 → 9/4 | note:B2 s:folkharp ]",
"[ 9/4 → 19/8 | note:A2 s:folkharp ]",
"[ 19/8 → 5/2 | note:G2 s:folkharp ]",
"[ 5/2 → 21/8 | note:F2 s:folkharp ]",
"[ 21/8 → 11/4 | note:E2 s:folkharp ]",
"[ 11/4 → 23/8 | note:D2 s:folkharp ]",
"[ 23/8 → 3/1 | note:C2 s:folkharp ]",
"[ 3/1 → 25/8 | note:C3 s:folkharp ]",
"[ 25/8 → 13/4 | note:Bb2 s:folkharp ]",
"[ 13/4 → 27/8 | note:Ab2 s:folkharp ]",
"[ 27/8 → 7/2 | note:G2 s:folkharp ]",
"[ 7/2 → 29/8 | note:F2 s:folkharp ]",
"[ 29/8 → 15/4 | note:Eb2 s:folkharp ]",
"[ 15/4 → 31/8 | note:D2 s:folkharp ]",
"[ 31/8 → 4/1 | note:C2 s:folkharp ]",
]
`;
@@ -3874,60 +3874,6 @@ exports[`runs examples > example "sine" example index 0 1`] = `
]
`;
exports[`runs examples > example "slice" example index 0 1`] = `
[
"[ 0/1 → 3/16 | begin:0.875 end:1 _slices:8 s:breaks165 ]",
"[ 3/16 → 3/8 | begin:0.75 end:0.875 _slices:8 s:breaks165 ]",
"[ 3/8 → 9/16 | begin:0.625 end:0.75 _slices:8 s:breaks165 ]",
"[ 9/16 → 21/32 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
"[ 21/32 → 3/4 | begin:0.5 end:0.625 _slices:8 s:breaks165 ]",
"[ 3/4 → 15/16 | begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
"[ (15/16 → 1/1) ⇝ 9/8 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
"[ 15/16 ⇜ (1/1 → 9/8) | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
"[ 9/8 → 21/16 | begin:0.125 end:0.25 _slices:8 s:breaks165 ]",
"[ 21/16 → 3/2 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
"[ 3/2 → 27/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
"[ 27/16 → 15/8 | begin:0.125 end:0.25 _slices:8 s:breaks165 ]",
"[ 15/8 → 63/32 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
"[ (63/32 → 2/1) ⇝ 33/16 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
"[ 63/32 ⇜ (2/1 → 33/16) | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
"[ 33/16 → 9/4 | begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
"[ 9/4 → 75/32 | begin:0.5 end:0.625 _slices:8 s:breaks165 ]",
"[ 75/32 → 39/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
"[ 39/16 → 21/8 | begin:0.625 end:0.75 _slices:8 s:breaks165 ]",
"[ 21/8 → 45/16 | begin:0.75 end:0.875 _slices:8 s:breaks165 ]",
"[ 45/16 → 3/1 | begin:0.875 end:1 _slices:8 s:breaks165 ]",
"[ 3/1 → 51/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
"[ 51/16 → 27/8 | begin:0.125 end:0.25 _slices:8 s:breaks165 ]",
"[ 27/8 → 57/16 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
"[ 57/16 → 15/4 | begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
"[ 15/4 → 123/32 | begin:0.5 end:0.625 _slices:8 s:breaks165 ]",
"[ 123/32 → 63/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
"[ (63/16 → 4/1) ⇝ 33/8 | begin:0.625 end:0.75 _slices:8 s:breaks165 ]",
]
`;
exports[`runs examples > example "slice" example index 1 1`] = `
[
"[ 0/1 → 1/4 | begin:0 end:0.25 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 1/4 → 1/2 | begin:0.25 end:0.5 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 1/2 → 3/4 | begin:0.25 end:0.5 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 3/4 → 1/1 | begin:0.5 end:0.75 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 1/1 → 5/4 | begin:0 end:0.25 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 5/4 → 3/2 | begin:0.25 end:0.5 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 3/2 → 7/4 | begin:0.25 end:0.5 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 7/4 → 2/1 | begin:0.75 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 2/1 → 9/4 | begin:0 end:0.25 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 9/4 → 5/2 | begin:0.25 end:0.5 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 5/2 → 11/4 | begin:0.25 end:0.5 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 11/4 → 3/1 | begin:0.5 end:0.75 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 3/1 → 13/4 | begin:0 end:0.25 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 13/4 → 7/2 | begin:0.25 end:0.5 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 7/2 → 15/4 | begin:0.25 end:0.5 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
"[ 15/4 → 4/1 | begin:0.75 _slices:[0 0.25 0.5 0.75] s:breaks125 speed:0.5 unit:c ]",
]
`;
exports[`runs examples > example "slow" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:bd ]",
@@ -4431,60 +4377,6 @@ exports[`runs examples > example "velocity" example index 0 1`] = `
]
`;
exports[`runs examples > example "voicing" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:E4 ]",
"[ 0/1 → 1/1 | note:G4 ]",
"[ 0/1 → 1/1 | note:C5 ]",
"[ 1/1 → 2/1 | note:E4 ]",
"[ 1/1 → 2/1 | note:A4 ]",
"[ 1/1 → 2/1 | note:C5 ]",
"[ 2/1 → 3/1 | note:F4 ]",
"[ 2/1 → 3/1 | note:A4 ]",
"[ 2/1 → 3/1 | note:C5 ]",
"[ 3/1 → 4/1 | note:D4 ]",
"[ 3/1 → 4/1 | note:G4 ]",
"[ 3/1 → 4/1 | note:B4 ]",
]
`;
exports[`runs examples > example "voicing" example index 1 1`] = `
[
"[ 0/1 → 1/8 | note:64 ]",
"[ 1/8 → 1/4 | note:67 ]",
"[ 1/4 → 3/8 | note:72 ]",
"[ 3/8 → 1/2 | note:76 ]",
"[ 1/2 → 5/8 | note:79 ]",
"[ 5/8 → 3/4 | note:84 ]",
"[ 3/4 → 7/8 | note:88 ]",
"[ 7/8 → 1/1 | note:91 ]",
"[ 1/1 → 9/8 | note:64 ]",
"[ 9/8 → 5/4 | note:69 ]",
"[ 5/4 → 11/8 | note:72 ]",
"[ 11/8 → 3/2 | note:76 ]",
"[ 3/2 → 13/8 | note:81 ]",
"[ 13/8 → 7/4 | note:84 ]",
"[ 7/4 → 15/8 | note:88 ]",
"[ 15/8 → 2/1 | note:93 ]",
"[ 2/1 → 17/8 | note:65 ]",
"[ 17/8 → 9/4 | note:69 ]",
"[ 9/4 → 19/8 | note:72 ]",
"[ 19/8 → 5/2 | note:77 ]",
"[ 5/2 → 21/8 | note:81 ]",
"[ 21/8 → 11/4 | note:84 ]",
"[ 11/4 → 23/8 | note:89 ]",
"[ 23/8 → 3/1 | note:93 ]",
"[ 3/1 → 25/8 | note:62 ]",
"[ 25/8 → 13/4 | note:67 ]",
"[ 13/4 → 27/8 | note:71 ]",
"[ 27/8 → 7/2 | note:74 ]",
"[ 7/2 → 29/8 | note:79 ]",
"[ 29/8 → 15/4 | note:83 ]",
"[ 15/4 → 31/8 | note:86 ]",
"[ 31/8 → 4/1 | note:91 ]",
]
`;
exports[`runs examples > example "voicings" example index 0 1`] = `
[
"[ 0/1 → 1/1 | note:B3 ]",
@@ -4523,6 +4415,31 @@ exports[`runs examples > example "vowel" example index 0 1`] = `
]
`;
exports[`runs examples > example "webdirt" example index 0 1`] = `
[
"[ 0/1 → 1/8 | s:bd n:0 ]",
"[ 1/8 → 1/4 | s:bd n:0 ]",
"[ 1/4 → 1/2 | s:hh n:0 ]",
"[ 1/2 → 3/4 | s:sd n:0 ]",
"[ 3/4 → 1/1 | s:hh n:0 ]",
"[ 1/1 → 9/8 | s:bd n:1 ]",
"[ 9/8 → 5/4 | s:bd n:1 ]",
"[ 5/4 → 3/2 | s:hh n:1 ]",
"[ 3/2 → 7/4 | s:sd n:1 ]",
"[ 7/4 → 2/1 | s:hh n:1 ]",
"[ 2/1 → 17/8 | s:bd n:0 ]",
"[ 17/8 → 9/4 | s:bd n:0 ]",
"[ 9/4 → 5/2 | s:hh n:0 ]",
"[ 5/2 → 11/4 | s:sd n:0 ]",
"[ 11/4 → 3/1 | s:hh n:0 ]",
"[ 3/1 → 25/8 | s:bd n:1 ]",
"[ 25/8 → 13/4 | s:bd n:1 ]",
"[ 13/4 → 7/2 | s:hh n:1 ]",
"[ 7/2 → 15/4 | s:sd n:1 ]",
"[ 15/4 → 4/1 | s:hh n:1 ]",
]
`;
exports[`runs examples > example "when" example index 0 1`] = `
[
"[ 0/1 → 1/3 | note:c3 ]",
File diff suppressed because it is too large Load Diff
-1
View File
@@ -60,7 +60,6 @@ const toneHelpersMocked = {
vol: mockNode,
out: id,
osc: id,
samples: id,
adsr: id,
getDestination: id,
players: mockNode,
@@ -43,18 +43,14 @@ const sidebar = SIDEBAR[langCode];
</li>
))
}
<li>
<a class={`py-0.5 mb-4 w-full hover:bg-lineHighlight block`} href={`${Astro.site?.pathname}ref`}> Reference</a>
</li>
</ul>
</nav>
<script is:inline>
window.addEventListener('DOMContentLoaded', () => {
var target = document.querySelector('[aria-current="page"]');
const nav = document.querySelector('.nav-groups');
if (nav && target && target.offsetTop > window.innerHeight - 100) {
nav.scrollTop = target.offsetTop;
}
});
window.addEventListener('DOMContentLoaded', () => {
var target = document.querySelector('[aria-current="page"]');
if (target && target.offsetTop > window.innerHeight - 100) {
document.querySelector('.nav-groups').scrollTop = target.offsetTop;
}
});
</script>
@@ -1,66 +0,0 @@
---
import { docs } from '../../../../doc.json';
type Props = {
currentPage: string;
};
const { currentPage } = Astro.props as Props;
const { BASE_URL } = import.meta.env;
let currentPageMatch = currentPage.slice(BASE_URL.length, currentPage.endsWith('/') ? -1 : undefined);
---
<nav aria-labelledby="grid-left" class="max-h-full overflow-auto pb-20 text-foreground">
<ul>
<li>
<a
class={`py-0.5 mb-4 w-full hover:bg-lineHighlight block`}
href={`${Astro.site?.pathname}workshop/getting-started`}>Back to Docs</a
>
</li>
<li>
<div class="nav-group pb-4">
<a class="hover:bg-lineHighlight w-full block" href={Astro.site?.pathname + 'ref'}><h2>Reference</h2></a>
<ul>
{
docs
.filter(
({ tags }) =>
!tags?.find((t) => t.title === 'noautocomplete') && !tags?.find((t) => t.title === 'superdirtonly'),
)
.filter(({ name, description }) => name && !name.startsWith('_') && !!description)
// @ts-ignore
.sort((a, b) => a.name.localeCompare(b.name))
.map(({ name }) => {
const link = 'ref/' + name;
const url = Astro.site?.pathname + link;
return (
<li class="">
<a
class={`pl-4 py-0.5 w-full hover:bg-lineHighlight block${
currentPageMatch === link ? ' bg-lineHighlight' : ''
}`}
href={url}
aria-current={currentPageMatch === link ? 'page' : false}
>
{name}
</a>
</li>
);
})
}
</ul>
</div>
</li>
</ul>
</nav>
<script is:inline>
window.addEventListener('DOMContentLoaded', () => {
var target = document.querySelector('[aria-current="page"]');
const nav = document.querySelector('.nav-groups');
if (nav && target && target.offsetTop > window.innerHeight - 100) {
nav.scrollTop = target.offsetTop;
}
});
</script>
@@ -8,10 +8,9 @@ type Props = {
frontmatter: Frontmatter;
headings: MarkdownHeading[];
githubEditUrl: string;
hideMoreMenu?: boolean;
};
const { frontmatter, headings, githubEditUrl, hideMoreMenu } = Astro.props as Props;
const { frontmatter, headings, githubEditUrl } = Astro.props as Props;
const title = frontmatter.title;
const currentPage = Astro.url.pathname;
---
@@ -23,13 +22,9 @@ const currentPage = Astro.url.pathname;
<slot />
</div>
</section>
{
!hideMoreMenu && (
<nav class="block sm:hidden">
<MoreMenu editHref={githubEditUrl} />
</nav>
)
}
<nav class="block sm:hidden">
<MoreMenu editHref={githubEditUrl} />
</nav>
</article>
<style>
.content {
-268
View File
@@ -1,268 +0,0 @@
import useEvent from '@strudel.cycles/react/src/hooks/useEvent.mjs';
import useFrame from '@strudel.cycles/react/src/hooks/useFrame.mjs';
import { getAudioContext } from '@strudel.cycles/webaudio';
import { midi2note } from '@strudel.cycles/core';
import { useState, useRef, useEffect } from 'react';
import Claviature from '@components/Claviature';
let Button = (props) => <button {...props} className="bg-lineHighlight p-2 rounded-md color-foreground" />;
function plotValues(ctx, values, min, max, color) {
let { width, height } = ctx.canvas;
ctx.strokeStyle = color;
const thickness = 8;
ctx.lineWidth = thickness;
ctx.beginPath();
let x = (f) => ((f - min) / (max - min)) * width;
let y = (i) => (1 - i / values.length) * height;
values.forEach((f, i, a) => {
ctx.lineTo(x(f), y(i));
});
ctx.stroke();
}
function getColor(cssVariable) {
if (typeof document === 'undefined') {
return 'white';
}
const dummyElement = document.createElement('div');
dummyElement.style.color = cssVariable;
// Append the dummy element to the document body
document.body.appendChild(dummyElement);
// Get the computed style of the dummy element
const styles = getComputedStyle(dummyElement);
// Get the value of the CSS variable
const color = styles.getPropertyValue(cssVariable);
document.body.removeChild(dummyElement);
return color;
}
let pitchColor = '#eab308';
let frequencyColor = '#3b82f6';
export function PitchSlider({
buttons = [],
animatable = false,
plot = false,
showPitchSlider = false,
showFrequencySlider = false,
pitchStep = '0.001',
min = 55,
max = 7040,
initial = 220,
baseFrequency = min,
zeroOffset = 0,
claviature,
}) {
const oscRef = useRef();
const activeRef = useRef();
const freqRef = useRef(initial);
const historyRef = useRef([freqRef.current]);
const frameRef = useRef();
const canvasRef = useRef();
const [hz, setHz] = useState(freqRef.current);
useEffect(() => {
freqRef.current = hz;
}, [hz]);
useEvent('mouseup', () => {
oscRef.current?.stop();
activeRef.current = false;
});
let freqSlider2freq = (progress) => min + progress * (max - min);
let pitchSlider2freq = (progress) => min * 2 ** (progress * Math.log2(max / min));
let freq2freqSlider = (freq) => (freq - min) / (max - min);
let freq2pitchSlider = (freq) => {
const [minOct, maxOct] = [Math.log2(min), Math.log2(max)];
return (Math.log2(freq) - minOct) / (maxOct - minOct);
};
const freqSlider = freq2freqSlider(hz);
const pitchSlider = freq2pitchSlider(hz);
let startOsc = (hz) => {
if (oscRef.current) {
oscRef.current.stop();
}
oscRef.current = getAudioContext().createOscillator();
oscRef.current.frequency.value = hz;
oscRef.current.connect(getAudioContext().destination);
oscRef.current.start();
activeRef.current = true;
setHz(hz);
};
let startSweep = (exp = false) => {
let f = min;
startOsc(f);
const frame = () => {
if (f < max) {
if (!exp) {
f += 10;
} else {
f *= 1.01;
}
oscRef.current.frequency.value = f;
frameRef.current = requestAnimationFrame(frame);
} else {
oscRef.current.stop();
cancelAnimationFrame(frameRef.current);
}
setHz(f);
};
requestAnimationFrame(frame);
};
useFrame(() => {
historyRef.current.push(freqRef.current);
historyRef.current = historyRef.current.slice(-1000);
if (canvasRef.current) {
let ctx = canvasRef.current.getContext('2d');
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if (showFrequencySlider) {
plotValues(ctx, historyRef.current, min, max, frequencyColor);
}
if (showPitchSlider) {
const [minOct, maxOct] = [Math.log2(min), Math.log2(max)];
let perceptual = historyRef.current.map((v) => Math.log2(v));
plotValues(ctx, perceptual, minOct, maxOct, pitchColor);
}
}
}, plot);
let handleChangeFrequency = (f) => {
setHz(f);
if (oscRef.current) {
oscRef.current.frequency.value = f;
}
};
let handleMouseDown = () => {
cancelAnimationFrame(frameRef.current);
startOsc(hz);
};
let exponent, activeNote, activeNoteLabel;
if (showPitchSlider) {
const expOffset = baseFrequency ? Math.log2(baseFrequency / min) : 0;
exponent = freq2pitchSlider(hz) * Math.log2(max / min) - expOffset;
let semitones = parseFloat((exponent * 12).toFixed(2));
if (zeroOffset) {
semitones = semitones + zeroOffset;
const isWhole = Math.round(semitones) === semitones;
activeNote = midi2note(Math.round(semitones));
activeNoteLabel = (!isWhole ? '~' : '') + activeNote;
semitones = !isWhole ? semitones.toFixed(2) : semitones;
exponent = (
<>
(<span className="text-yellow-500">{semitones}</span> - {zeroOffset})/12
</>
);
} else if (semitones % 12 === 0) {
exponent = <span className="text-yellow-500">{semitones / 12}</span>;
} else if (semitones % 1 === 0) {
exponent = (
<>
<span className="text-yellow-500">{semitones}</span>/12
</>
);
} else {
exponent = <span className="text-yellow-500">{exponent.toFixed(2)}</span>;
}
}
return (
<>
<span className="font-mono">
{showFrequencySlider && <span className="text-blue-500">{hz.toFixed(0)}Hz</span>}
{showFrequencySlider && showPitchSlider && <> = </>}
{showPitchSlider && (
<>
{baseFrequency || min}Hz * 2<sup>{exponent}</sup>
</>
)}
</span>
{claviature && (
<>
{' '}
= <span className="text-yellow-500">{activeNoteLabel}</span>
</>
)}
<div>
{showFrequencySlider && (
<div className="flex space-x-1 items-center">
<input
type="range"
value={freqSlider}
min={0}
max={1}
step={0.001}
onMouseDown={handleMouseDown}
className={`block w-full max-w-[600px] accent-blue-500 `}
onChange={(e) => {
const f = freqSlider2freq(parseFloat(e.target.value));
handleChangeFrequency(f);
}}
/>
</div>
)}
{showPitchSlider && (
<div>
<input
type="range"
value={pitchSlider}
min={0}
max={1}
//step=".001"
step={pitchStep}
onMouseDown={handleMouseDown}
className={`block w-full max-w-[600px] accent-yellow-500`}
onChange={(e) => {
const f = pitchSlider2freq(parseFloat(e.target.value));
handleChangeFrequency(f);
}}
/>
</div>
)}
</div>
<div className="px-2">
{plot && <canvas ref={canvasRef} className="w-full max-w-[584px] h-[300px]" height="600" width={800} />}
</div>
<div className="space-x-2">
{animatable && (
<Button onClick={() => startSweep()}>
<span style={{ color: '#3b82f6' }}>Frequency Sweep</span>
</Button>
)}
{animatable && (
<Button onClick={() => startSweep(true)}>
<span style={{ color: '#eab308' }}>Pitch Sweep</span>
</Button>
)}
{buttons.map((f, i) => (
<Button key={(f, i)} onMouseDown={() => startOsc(f)}>
{f}Hz
</Button>
))}
</div>
{claviature && (
<Claviature
onMouseDown={(note) => {
const f = 440 * 2 ** ((note - 69) / 12);
handleChangeFrequency(f);
cancelAnimationFrame(frameRef.current);
startOsc(f);
}}
options={{
range: ['A1', 'A5'],
scaleY: 0.75,
scaleX: 0.86,
colorize: activeNote ? [{ keys: [activeNote], color: '#eab308' }] : [],
labels: activeNote ? { [activeNote]: activeNote } : {},
}}
/>
)}
</>
);
}
+1 -2
View File
@@ -15,7 +15,7 @@ export const OPEN_GRAPH = {
export type Frontmatter = {
title: string;
description: string;
layout?: string;
layout: string;
image?: { src: string; alt: string };
dir?: 'ltr' | 'rtl';
ogLocale?: string;
@@ -89,7 +89,6 @@ export const SIDEBAR: Sidebar = {
{ text: 'Accumulation', link: 'learn/accumulation' },
{ text: 'Tonal Functions', link: 'learn/tonal' },
],
Understand: [{ text: 'Pitch', link: 'understand/pitch' }],
Development: [
{ text: 'REPL', link: 'technical-manual/repl' },
{ text: 'Sounds', link: 'technical-manual/sounds' },
-50
View File
@@ -1,50 +0,0 @@
---
import HeadCommon from '../components/HeadCommon.astro';
import HeadSEO from '../components/HeadSEO.astro';
import Header from '../components/Header/Header.astro';
import PageContent from '../components/PageContent/PageContent.astro';
import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
import * as CONFIG from '../config';
import type { MarkdownHeading } from 'astro';
import '../docs/docs.css';
import ReferenceSidebar from '../components/LeftSidebar/ReferenceSidebar.astro';
type Props = {
frontmatter: CONFIG.Frontmatter;
headings: MarkdownHeading[];
};
const { frontmatter, headings } = Astro.props as Props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const currentPage = Astro.url.pathname;
const currentFile = `src/pages${currentPage.replace(/\/$/, '')}.mdx`;
const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
---
<html dir={frontmatter.dir ?? 'ltr'} lang={frontmatter.lang ?? 'en'} class="initial dark">
<head>
<HeadCommon />
<HeadSEO frontmatter={frontmatter} canonicalUrl={canonicalURL} />
<title>
{frontmatter.title ? `${frontmatter.title} 🌀 ${CONFIG.SITE.title}` : CONFIG.SITE.title}
</title>
</head>
<body class="h-app-height text-gray-50 bg-background">
<div class="w-full h-full space-y-4 flex flex-col">
<header class="max-w-full fixed top-0 w-full z-[100]">
<Header currentPage={currentPage} />
</header>
<main class="relative pt-16">
<div class="h-full top-0 overflow-auto min-w-[300px] flex xl:justify-center pr-4 pl-4 md:pl-[300px] xl:pl-0">
<aside title="Site Navigation" class="w-[300px] px-6 left-0 hidden md:block fixed h-full">
<ReferenceSidebar currentPage={currentPage} />
</aside>
<PageContent hideMoreMenu={true} frontmatter={frontmatter} headings={headings} githubEditUrl={githubEditUrl}>
<slot />
</PageContent>
</div>
</main>
</div>
</body>
</html>
-4
View File
@@ -319,10 +319,6 @@ Sampler effects are functions that can be used to change the behaviour of sample
<JsDoc client:idle name="Pattern.chop" h={0} />
### slice
<JsDoc client:idle name="Pattern.slice" h={0} />
### speed
<JsDoc client:idle name="speed" h={0} />
+12 -5
View File
@@ -10,13 +10,20 @@ import { JsDoc } from '../../docs/JsDoc';
These functions use [tonaljs](https://github.com/tonaljs/tonal) to provide helpers for musical operations.
### voicing()
<JsDoc client:idle name="voicing" h={0} />
### scale(name)
<JsDoc client:idle name="scale" h={0} />
Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like scaleTranspose.
<MiniRepl
client:idle
tune={`"0 2 4 6 4 2"
.scale("C2:major C2:minor").slow(2))
.note().s("piano")`}
/>
Note that the scale root is octaved here. You can also omit the octave, then index zero will default to octave 3.
All the available scale names can be found [here](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts).
### transpose(semitones)
-40
View File
@@ -1,40 +0,0 @@
---
import ReferenceLayout from '../../layouts/ReferenceLayout.astro';
import { docs } from '../../../../doc.json';
import JsDoc from '../../docs/JsDoc.astro';
export const getSynonyms = (doc) => {
const synonyms = doc.tags?.find((t) => t.title === 'synonyms');
if (synonyms) {
return synonyms.text?.split(', ');
}
return [];
};
export function getStaticPaths() {
let paths: any = [];
docs
.filter((doc) => !!doc.name)
.forEach((doc) => {
paths.push({ params: { ref: doc.name } });
const synonyms = getSynonyms(doc);
if (synonyms) {
synonyms.forEach((synonym) => {
paths.push({ params: { ref: synonym } });
});
}
});
return paths;
}
const { ref } = Astro.params;
const entry = docs.find((d) => d.name === ref || getSynonyms(d).includes(ref));
if (!entry) {
return;
}
---
<ReferenceLayout frontmatter={{ title: entry.name || 'Reference', description: '' }} headings={[]}>
<h1>{entry!.name}</h1>
<JsDoc name={entry.longname} />
</ReferenceLayout>
-8
View File
@@ -1,8 +0,0 @@
---
import ReferenceLayout from '../../layouts/ReferenceLayout.astro';
---
<ReferenceLayout frontmatter={{ title: 'Reference', description: 'All functions' }} headings={[]}>
<h1>Reference</h1>
<p>Welcome to the Reference! Pick an Item on the left</p>
</ReferenceLayout>
-169
View File
@@ -1,169 +0,0 @@
---
title: Understanding Pitch
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { PitchSlider } from '../../components/PitchSlider';
import Box from '@components/Box.astro';
# Understanding Pitch
Let's learn how pitch works! The slider below controls the <span style="color:#3b82f6;">frequency</span> of an oscillator, producing a pitch:
{/* <PitchSlider client:load showFrequencySlider plot /> */}
<PitchSlider client:load showFrequencySlider min={20} max={20000} />
- Drag the slider to hear a pitch
- Move the slider to change the pitch
- Observe how the Hz number changes
- <span className="text-red-300">Caution</span>: The higher frequencies could be disturbing for children or animals!
The Hz number is the frequency of the pitch you're hearing.
The higher the frequency, the higher the pitch and vice versa.
A pitch occurs whenever something is vibrating / oscillating at a frequency, in this case it's your speaker.
The unit **Hz** describes how many times that oscillation happens per second.
Our eyes are too slow to actually see the oscillation on the speaker, but we can <a href="https://www.youtube.com/watch?v=CDMBWw7OuJQ" target="_blank">see it in slow motion</a>.
<Box>
The hearing range of a newborn is said to be between 20Hz and 20000Hz.
The upper limit decreases with age. What's your upper limit?
</Box>
In Strudel, we can play frequencies directly with the `freq` control:
<MiniRepl client:visible tune={`freq("200 [300,500] 400 [500,<600 670 712 670>]")`} />
## Frequency vs Pitch Perception
Maybe you have already noticed that the <span style="color:#3b82f6;">frequency slider</span> is "lopsided",
meaning the pitch changes more in the left region and less in the right region.<br/>
To make that more obvious, let's add a <span style="color:#eab308">pitch slider</span>
that controls the frequency on a different scale:
<PitchSlider animatable plot showFrequencySlider showPitchSlider client:load />
Try out the buttons above to sweep through the frequency range in 2 different ways:
- Frequency Sweep: <span style="color:#3b82f6;">frequency rises linear</span> , <span style="color:#eab308">pitch rises logarithmic</span>
- Pitch Sweep: <span style="color:#3b82f6;">frequency rises exponential</span> , <span style="color:#eab308">pitch rises linear</span>
<Box>
Don't be scared of these mathematical terms:
- "logarithmic" is just a fancy way of saying "it starts fast and slows down"
- "exponential" is just a fancy way of saying "it starts slow and gets faster"
</Box>
Most of the time, we might want to control pitch in a way that matches our perception,
which is what the <span style="color:#eab308">pitch slider</span> does.
## From Hz to Semitones
Because Hz does not match our perception, let's try to find a unit for pitch that matches.
To approach that unit of pitch, let's look at how frequency behaves when it is doubled:
<PitchSlider client:load showPitchSlider showFrequencySlider pitchStep={1 / 7} />
- Use the now stepped pitch slider above
- Can you hear how these pitches seem related to each other?
<Box>
In musical terms, a pitch with double the frequency of another is an `octave` higher.
</Box>
Because octaves are pretty far apart, octaves are typically divided into 12 smaller parts:
<PitchSlider client:load showPitchSlider showFrequencySlider pitchStep={1 / 12} min={440} max={880} initial={440} />
This step is also called a semitone, which is the most common division of pitched music.
For example, the keys on a piano keyboard are also divided into semitones.
In Strudel, we could do that with `freq` like this:
<MiniRepl
client:visible
tune={`freq(
"0 4 7 12"
.fmap(n => 440 * 2**(n/12))
)`}
/>
Of course, this can be written shorter with note, as we will see below.
## From Semitones to MIDI numbers
Now we know what the distance of a semitone is.
Above, we used an arbitrary base frequency of 440Hz, which means the exponent 0 is equal to 440Hz.
Typically, 440Hz is standardized to the number 69, which leads to this calculation:
<PitchSlider
client:load
showPitchSlider
showFrequencySlider
baseFrequency={440}
zeroOffset={69}
pitchStep={1 / 12 / 7}
min={440 / 8}
max={7040}
initial={440}
/>
The yellow number is now a MIDI number, covering more than the whole human hearing range with numbers from 0 to 127.
In Strudel, we can use MIDI numbers inside `note`:
<MiniRepl client:visible tune={`note("69 73 76 81")`} />
## From MIDI numbers to notes
In western music theory, notes are used instead of numbers.
For each midi number, there is at least one note label:
<PitchSlider
client:load
showPitchSlider
showFrequencySlider
baseFrequency={440}
zeroOffset={69}
pitchStep={1 / 48}
min={440 / 8}
max={880}
initial={440}
claviature
/>
A full note label consists of a letter (A-G), 0 or more accidentals (b | #) and an octave number.
This system is also known as [Scientific Pitch Notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation).
In Strudel, these note labels can also be used inside `note` as an alternative to midi numbers:
<MiniRepl client:visible tune={`note("A4 C#5 E5 A5").piano()`} />
## Open Questions
Now that we have learned about different representations of pitch, there are still open questions:
- Why 12 notes? What about different divisions of the octave?
- Why are notes labeled as they are? Why only 7 letters?
- Are there other labeling systems?
- What about Just Intonation Systems?
- What about Timbre?
All those questions are important to ask and will be answered in another article.
## Definition
At first, I wanted to start this article with a definition, but then thought it might be a good idea to focus on intuitive exploration.
Maybe you now understand this definition much better:
<Box>
From [wikipedia](<https://en.wikipedia.org/wiki/Pitch_(music)>): "Pitch is a perceptual property of sounds that allows their ordering on a frequency-related scale, or more commonly, pitch is the quality that makes it possible to judge sounds as "higher" and "lower" in the sense associated with musical melodies."
</Box>
+7 -12
View File
@@ -151,18 +151,13 @@ export function Repl({ embedded = false }) {
// init code
useEffect(() => {
initCode().then((decoded) => {
let msg;
if (decoded) {
setCode(decoded);
msg = `I have loaded the code from the URL.`;
} else if (latestCode) {
setCode(latestCode);
msg = `Your last session has been loaded!`;
} /* if(randomTune) */ else {
setCode(randomTune);
msg = `A random code snippet named "${name}" has been loaded!`;
}
logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight');
logger(
`Welcome to Strudel! ${
decoded ? `I have loaded the code from the URL.` : `A random code snippet named "${name}" has been loaded!`
} Press play or hit ctrl+enter to run it!`,
'highlight',
);
setCode(decoded || latestCode || randomTune);
setPending(false);
});
}, []);
+40 -60
View File
@@ -68,35 +68,32 @@ stack(
`;
export const giantSteps = `// John Coltrane - Giant Steps
let melody = note(
"[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]",
)
setVoicingRange('lefthand', ['E3', 'G4']);
stack(
// melody
melody.color('#F8E71C'),
seq(
"[F#5 D5] [B4 G4] Bb4 [B4 A4]",
"[D5 Bb4] [G4 Eb4] F#4 [G4 F4]",
"Bb4 [B4 A4] D5 [D#5 C#5]",
"F#5 [G5 F5] Bb5 [F#5 F#5]",
).color('#F8E71C'),
// chords
chord(
seq(
"[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]",
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
).dict('lefthand')
.anchor(melody).mode('duck')
.voicing().color('#7ED321'),
).voicings('lefthand').color('#7ED321'),
// bass
note(
seq(
"[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]"
).color('#00B8D4')
).slow(20)
.pianoroll({fold:1})`;
).slow(20).note()
//.pianoroll({fold:1})`;
export const zeldasRescue = `// Koji Kondo - Princess Zelda's Rescue
stack(
@@ -137,31 +134,28 @@ const drums = stack(
s("[~ hh]*2").delay(.3).delayfeedback(.5).delaytime(.125).gain(.4)
);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
"<eb4 d4 c4 b3>/2"
.scale("<C:minor!3 C:melodic:minor>/2")
.struct("[~ x]*2")
.scale(timeCat([3,'C minor'],[1,'C melodic minor'])
.slow(8)).struct("[~ x]*2")
.layer(
x=>x.scaleTranspose(0).early(0),
x=>x.scaleTranspose(2).early(1/8),
x=>x.scaleTranspose(7).early(1/4),
x=>x.scaleTranspose(8).early(3/8)
).note().apply(keys).mask("<~ x>/16")
).apply(thru).note().apply(keys).mask("<~ x>/16")
.color('darkseagreen'),
note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2")
note("<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".apply(thru))
.struct("[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2".fast(2))
.s('sawtooth').attack(0.001).decay(0.2).sustain(1).cutoff(500)
.color('brown'),
chord("<Cm7 Bb7 Fm7 G7b13>/2")
.struct("~ [x@0.2 ~]".fast(2))
.dict('lefthand').voicing()
.every(2, early(1/8))
.apply(keys).sustain(0)
"<Cm7 Bb7 Fm7 G7b13>/2".struct("~ [x@0.2 ~]".fast(2))
.voicings('lefthand')
.apply(thru).every(2, early(1/8)).note().apply(keys).sustain(0)
.delay(.4).delaytime(.12)
.mask("<x@7 ~>/8".early(1/4))
).add(note("<-1 0>/8"))
)
stack(
drums.fast(2).color('tomato'),
synths
@@ -269,20 +263,15 @@ export const festivalOfFingers = `// "Festival of fingers"
const chords = "<Cm7 Fm7 G7 F#7>";
stack(
chord(chords).dict('lefthand').voicing().struct("x(3,8,-1)")
.velocity(.5).off(1/7,x=>x.add(note(12)).velocity(.2)),
chords.rootNotes(2).struct("x(4,8,-2)").note(),
chords.voicings('lefthand').struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)),
chords.rootNotes(2).struct("x(4,8,-2)"),
chords.rootNotes(4)
.scale(cat('C minor','F dorian','G dorian','F# mixolydian'))
.struct("x(3,8,-2)".fast(2))
.scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/4"))
.note()
).slow(2)
.velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8))
.piano()`;
.note().piano()`;
// iter, echo, echoWith
export const undergroundPlumber = `// "Underground plumber"
@@ -538,10 +527,10 @@ stack(
.gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
//.hush()
,chord("<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>")
.dict('lefthand').voicing() // chords
.add(note("0,.04")) // add second, slightly detuned voice
.add(note(perlin.range(0,.5))) // random pitch variation
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
.add(perlin.range(0,.5)) // random pitch variation
.note() // wrap in "note"
.s('sawtooth') // waveform
.gain(.16) // turn down
.cutoff(500) // fixed cutoff
@@ -570,15 +559,14 @@ samples({
perc: ['perc/002_perc2.wav'],
}, 'github:tidalcycles/Dirt-Samples/master/');
chord("<C^7 Am7 Dm7 G7>*2").dict('lefthand').anchor("G4").voicing()
.stack(n("0@6 [<1 2> <2 0> 1]@2").scale('C5 major'))
.slow(4)
"C^7 Am7 Dm7 G7".slow(2).voicings('lefthand')
.stack("0@6 [<1 2> <2 0> 1]@2".scale('C5 major'))
.note().slow(4)
.s("gm_epiano1:1")
.color('steelblue')
.stack(
n("<-7 ~@2 [~@2 -7] -9 ~@2 [~@2 -9] -10!2 ~ [~@2 -10] -5 ~ [-3 -2 -10]@2>*2")
.scale('C3 major')
.s('sawtooth').color('brown')
"<-7 ~@2 [~@2 -7] -9 ~@2 [~@2 -9] -10!2 ~ [~@2 -10] -5 ~ [-3 -2 -10]@2>*2".scale('C3 major')
.note().s('sawtooth').color('brown')
)
.attack(0.05).decay(.1).sustain(.7)
.cutoff(perlin.range(800,2000))
@@ -676,10 +664,8 @@ stack(
s("mt lt ht").struct("x(3,8)").fast(2).gain(.5).room(.5).sometimes(x=>x.speed(".5")),
s("misc:2").speed(1).delay(.5).delaytime(1/3).gain(.4),
// chords
chord("[~ Gm7] ~ [~ Dm7] ~")
.dict('lefthand').voicing()
.add(note("0,.1"))
.s('sawtooth').gain(.8)
note("[~ Gm7] ~ [~ Dm7] ~".voicings('lefthand').superimpose(x=>x.add(.1)))
.s('sawtooth').gain(.5)
.cutoff(perlin.range(400,3000).slow(8))
.decay(perlin.range(0.05,.2)).sustain(0)
.delay(.9).room(1),
@@ -709,11 +695,7 @@ setVoicingRange('lefthand', ['c3','a4'])
stack(
s('bass').loopAt(8).clip(1),
s("bd*2, ~ sd,hh*4"),
chord("Abm7")
.mode("below:G4")
.dict('lefthand')
.voicing()
.struct("x(3,8,1)".slow(2)),
note("Abm7".voicings('lefthand').struct("x(3,8,1)".slow(2))),
"0 1 2 3".scale('ab4 minor pentatonic')
.superimpose(x=>x.add(.1))
.sometimes(x=>x.add(12))
@@ -877,7 +859,7 @@ export const loungeSponge = `// "Lounge sponge"
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
stack(
chord("<C^7 A7 Dm7 Fm7>/2").dict('lefthand').voicing()
note("<C^7 A7 Dm7 Fm7>/2".voicings('lefthand'))
.cutoff(sine.range(500,2000).round().slow(16))
.euclidLegato(3,8).csound('FM1')
,
@@ -896,11 +878,9 @@ export const arpoon = `// "Arpoon"
await samples('github:tidalcycles/Dirt-Samples/master')
n("[0,3] 2 [1,3] 2".fast(3).lastOf(4, fast(2))).clip(2)
.offset("<<1 2> 2 1 1>")
.chord("<<Am7 C^7> C7 F^7 [Fm7 E7b9]>")
.dict('lefthand').voicing()
.add(perlin.range(0,0.2).add("<-12 0>/8").note())
note("<<Am7 C^7> C7 F^7 [Fm7 E7b9]>".voicings('lefthand'))
.arp("[0,3] 2 [1,3] 2".fast(3).lastOf(4, fast(2))).clip(2)
.add(perlin.range(0,0.2).add("<0 12>/8").note())
.cutoff(perlin.range(500,4000)).resonance(12)
.gain("<.5 .8>*16")
.decay(.16).sustain(0.5)