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