diff --git a/packages/mini/krill-parser.js b/packages/mini/krill-parser.js
index a91e46b8..66dff038 100644
--- a/packages/mini/krill-parser.js
+++ b/packages/mini/krill-parser.js
@@ -219,7 +219,7 @@ function peg$parse(input, options) {
var peg$r0 = /^[1-9]/;
var peg$r1 = /^[eE]/;
var peg$r2 = /^[0-9]/;
- var peg$r3 = /^[ \n\r\t]/;
+ var peg$r3 = /^[ \n\r\t\xA0]/;
var peg$r4 = /^[0-9~]/;
var peg$r5 = /^[^\n]/;
var peg$r6 = /^[a-z\xB5\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137-\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148-\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C-\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA-\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9-\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC-\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF-\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F-\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0-\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB-\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE-\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6-\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FC7\u1FD0-\u1FD3\u1FD6-\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6-\u1FF7\u210A\u210E-\u210F\u2113\u212F\u2134\u2139\u213C-\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5E\u2C61\u2C65-\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73-\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3-\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7FA\uAB30-\uAB5A\uAB60-\uAB65\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A]/;
@@ -238,7 +238,7 @@ function peg$parse(input, options) {
var peg$e6 = peg$literalExpectation("0", false);
var peg$e7 = peg$classExpectation([["0", "9"]], false, false);
var peg$e8 = peg$otherExpectation("whitespace");
- var peg$e9 = peg$classExpectation([" ", "\n", "\r", "\t"], false, false);
+ var peg$e9 = peg$classExpectation([" ", "\n", "\r", "\t", "\xA0"], false, false);
var peg$e10 = peg$literalExpectation(",", false);
var peg$e11 = peg$literalExpectation("|", false);
var peg$e12 = peg$literalExpectation("\"", false);
diff --git a/packages/mini/krill.pegjs b/packages/mini/krill.pegjs
index 448d291e..62b2f1a1 100644
--- a/packages/mini/krill.pegjs
+++ b/packages/mini/krill.pegjs
@@ -95,7 +95,7 @@ DIGIT = [0-9]
// ------------------ delimiters ---------------------------
-ws "whitespace" = [ \n\r\t]*
+ws "whitespace" = [ \n\r\t\u00A0]*
comma = ws "," ws
pipe = ws "|" ws
quote = '"' / "'"
diff --git a/packages/mini/mini.mjs b/packages/mini/mini.mjs
index 83aadf37..c2faf29b 100644
--- a/packages/mini/mini.mjs
+++ b/packages/mini/mini.mjs
@@ -160,11 +160,19 @@ export const getLeafLocation = (code, leaf, globalOffset = 0) => {
};
// takes quoted mini string, returns ast
-export const mini2ast = (code) => krill.parse(code);
+export const mini2ast = (code, start, userCode) => {
+ try {
+ return krill.parse(code);
+ } catch (error) {
+ const region = [error.location.start.offset + start, error.location.end.offset + start];
+ const line = userCode.slice(0, region[0]).split('\n').length;
+ throw new Error(`[mini] parse error at line ${line}: ${error.message}`);
+ }
+};
// takes quoted mini string, returns all nodes that are leaves
-export const getLeaves = (code) => {
- const ast = mini2ast(code);
+export const getLeaves = (code, start, userCode) => {
+ const ast = mini2ast(code, start, userCode);
let leaves = [];
patternifyAST(
ast,
@@ -180,8 +188,8 @@ export const getLeaves = (code) => {
};
// takes quoted mini string, returns locations [fromCol,toCol] of all leaf nodes
-export const getLeafLocations = (code, offset = 0) => {
- return getLeaves(code).map((l) => getLeafLocation(code, l, offset));
+export const getLeafLocations = (code, start = 0, userCode) => {
+ return getLeaves(code, start, userCode).map((l) => getLeafLocation(code, l, start));
};
// mini notation only (wraps in "")
diff --git a/packages/tonal/tonal.mjs b/packages/tonal/tonal.mjs
index 3b6fe41e..76bda827 100644
--- a/packages/tonal/tonal.mjs
+++ b/packages/tonal/tonal.mjs
@@ -5,14 +5,20 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { Note, Interval, Scale } from '@tonaljs/tonal';
-import { register, _mod } from '@strudel.cycles/core';
+import { register, _mod, silence, logger, pure, isNote } from '@strudel.cycles/core';
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
function scaleStep(step, scale) {
scale = scale.replaceAll(':', ' ');
step = Math.ceil(step);
- const { intervals, tonic } = Scale.get(scale);
+ let { intervals, tonic, empty } = Scale.get(scale);
+ if ((empty && isNote(scale)) || (!empty && !tonic)) {
+ throw new Error(`incomplete scale. Make sure to use ":" instead of spaces, example: .scale("C:major")`);
+ } else if (empty) {
+ throw new Error(`invalid scale "${scale}"`);
+ }
+ tonic = tonic || 'C';
const { pc, oct = 3 } = Note.get(tonic);
const octaveOffset = Math.floor(step / intervals.length);
const scaleStep = _mod(step, intervals.length);
@@ -160,16 +166,34 @@ export const scale = register('scale', function (scale, pat) {
if (Array.isArray(scale)) {
scale = scale.flat().join(' ');
}
- return pat.withHap((hap) => {
- const isObject = typeof hap.value === 'object';
- let note = isObject ? hap.value.n : hap.value;
- if (isObject) {
- delete hap.value.n; // remove n so it won't cause trouble
- }
- const asNumber = Number(note);
- if (!isNaN(asNumber)) {
- note = scaleStep(asNumber, scale);
- }
- return hap.withValue(() => (isObject ? { ...hap.value, note } : note)).setContext({ ...hap.context, scale });
- });
+ return (
+ pat
+ .fmap((value) => {
+ const isObject = typeof value === 'object';
+ let step = isObject ? value.n : value;
+ if (isObject) {
+ delete value.n; // remove n so it won't cause trouble
+ }
+ if (isNote(step)) {
+ // legacy..
+ return pure(step);
+ }
+ const asNumber = Number(step);
+ if (isNaN(asNumber)) {
+ logger(`[tonal] invalid scale step "${step}", expected number`, 'error');
+ return silence;
+ }
+ try {
+ const note = scaleStep(asNumber, scale);
+ value = pure(isObject ? { ...value, note } : note);
+ } catch (err) {
+ logger(`[tonal] ${err.message}`, 'error');
+ value = silence;
+ }
+ return value;
+ })
+ .outerJoin()
+ // legacy:
+ .withHap((hap) => hap.setContext({ ...hap.context, scale }))
+ );
});
diff --git a/packages/tonal/voicings.mjs b/packages/tonal/voicings.mjs
index a99a043b..6f4d334d 100644
--- a/packages/tonal/voicings.mjs
+++ b/packages/tonal/voicings.mjs
@@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see .
*/
-import { stack, register } from '@strudel.cycles/core';
+import { stack, register, silence, logger } from '@strudel.cycles/core';
import { renderVoicing } from './tonleiter.mjs';
import _voicings from 'chord-voicings';
import { complex, simple } from './ireal.mjs';
@@ -184,12 +184,9 @@ export const rootNotes = register('rootNotes', function (octave, pat) {
* 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("")
- * @example
- * n("0 1 2 3 4 5 6 7").chord("").voicing()
+ * n("0 1 2 3").chord("").voicing()
*/
export const voicing = register('voicing', function (pat) {
return pat
@@ -199,11 +196,15 @@ export const voicing = register('voicing', function (pat) {
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!
+ try {
+ let notes = renderVoicing({ ...dictionary, chord, anchor, offset, mode, n, octaves });
+ return stack(...notes)
+ .note()
+ .set(rest); // rest does not include voicing controls anymore!
+ } catch (err) {
+ logger(`[voicing]: unknown chord "${chord}"`);
+ return silence;
+ }
})
.outerJoin();
});
diff --git a/packages/transpiler/transpiler.mjs b/packages/transpiler/transpiler.mjs
index 4e66a57c..0231a2f9 100644
--- a/packages/transpiler/transpiler.mjs
+++ b/packages/transpiler/transpiler.mjs
@@ -15,7 +15,7 @@ export function transpiler(input, options = {}) {
let miniLocations = [];
const collectMiniLocations = (value, node) => {
- const leafLocs = getLeafLocations(`"${value}"`, node.start); // stimmt!
+ const leafLocs = getLeafLocations(`"${value}"`, node.start, input);
miniLocations = miniLocations.concat(leafLocs);
};
let widgets = [];
diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap
index 3d23c106..dd7392d1 100644
--- a/test/__snapshots__/examples.test.mjs.snap
+++ b/test/__snapshots__/examples.test.mjs.snap
@@ -5365,55 +5365,22 @@ exports[`runs examples > example "vibmod" example index 1 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 ]",
+ "[ 0/1 → 1/4 | note:64 ]",
+ "[ 1/4 → 1/2 | note:67 ]",
+ "[ 1/2 → 3/4 | note:72 ]",
+ "[ 3/4 → 1/1 | note:76 ]",
+ "[ 1/1 → 5/4 | note:64 ]",
+ "[ 5/4 → 3/2 | note:69 ]",
+ "[ 3/2 → 7/4 | note:72 ]",
+ "[ 7/4 → 2/1 | note:76 ]",
+ "[ 2/1 → 9/4 | note:65 ]",
+ "[ 9/4 → 5/2 | note:69 ]",
+ "[ 5/2 → 11/4 | note:72 ]",
+ "[ 11/4 → 3/1 | note:77 ]",
+ "[ 3/1 → 13/4 | note:62 ]",
+ "[ 13/4 → 7/2 | note:67 ]",
+ "[ 7/2 → 15/4 | note:71 ]",
+ "[ 15/4 → 4/1 | note:74 ]",
]
`;
diff --git a/website/.astro/settings.json b/website/.astro/settings.json
new file mode 100644
index 00000000..bf82959b
--- /dev/null
+++ b/website/.astro/settings.json
@@ -0,0 +1,5 @@
+{
+ "devToolbar": {
+ "enabled": false
+ }
+}
\ No newline at end of file
diff --git a/website/src/env.d.ts b/website/src/env.d.ts
index 8959d4d6..5263bd6a 100644
--- a/website/src/env.d.ts
+++ b/website/src/env.d.ts
@@ -1,3 +1,4 @@
+///
///
///
///
diff --git a/website/src/repl/Repl.css b/website/src/repl/Repl.css
index 9c4a440d..2d97d8a2 100644
--- a/website/src/repl/Repl.css
+++ b/website/src/repl/Repl.css
@@ -24,9 +24,12 @@
#code .cm-scroller {
padding-top: 10px !important;
- padding-bottom: 50vh;
+ height: 100%;
font-family: inherit;
}
+#code .cm-content {
+ padding-bottom: 50vh;
+}
#code .cm-line > * {
background: var(--lineBackground);
}
diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx
index 98840e43..d53e7e5b 100644
--- a/website/src/repl/Repl.jsx
+++ b/website/src/repl/Repl.jsx
@@ -21,7 +21,6 @@ import {
} from '../settings.mjs';
import { Header } from './Header';
import Loader from './Loader';
-import './Repl.css';
import { Panel } from './panel/Panel';
import { useStore } from '@nanostores/react';
import { prebake } from './prebake.mjs';