diff --git a/documentation/BUILTIN.md b/documentation/BUILTIN.md index 81952bd9..9039c4b1 100644 --- a/documentation/BUILTIN.md +++ b/documentation/BUILTIN.md @@ -102,7 +102,7 @@ Obtain an array of `[key, value]` pairs of the record. ### equaveOf(*val*) Return the equave of the val. -### every(*array*, *test*) +### every(*array = $$*, *test*) Tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value. It doesn't modify the array. If no array is provided it defaults to the current scale. If no test is provided it defaults to truthiness. ### expm1(*x*) @@ -216,8 +216,8 @@ The Just Intonation Point. Converts intervals to real cents. ### kCombinations(*set*, *k*) Obtain all k-sized combinations in a set -### keepUnique(*scale = $$*) -Only keep unique intervals in the current/given scale. +### keepUnique(*scale = ££*) +Obtain a copy of the popped/given scale with only unique intervals kept. ### labelAbsoluteFJS(*interval*, *flavor = ""*) Convert interval to absolute FJS and label without octaves. Color black if there are accidentals, white otherwise. @@ -312,11 +312,11 @@ Map a riff over the given/current scale replacing the contents. ### repr(*value*) Obtain a string representation of the value (with color and label). -### reverse(*scale = $$*) -Reverse the order of the current/given scale. +### reverse(*scale = ££*) +Obtain a copy of the popped/given scale in reversed order. -### reversed(*scale = $$*) -Obtain a copy of the current/given scale in reversed order. +### reverseInPlace(*scale = $$*) +Reverse the order of the current/given scale. ### rgb(*red*, *green*, *blue*) RGB color (Red range 0-255, Green range 0-255, Blue range 0-255). @@ -339,14 +339,14 @@ Calculate sin x. ### slice(*array*, *indexStart*, *indexEnd*) Obtain a slice of a string or scale between the given indices. -### some(*array*, *test*) +### some(*array = $$*, *test*) Test whether at least one element in the array passes the test implemented by the provided function. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise it returns false. It doesn't modify the array. If no array is provided it defaults to the current scale. If no test is provided it defaults to truthiness. -### sort(*scale = $$*, *compareFn*) -Sort the current/given scale in ascending order. +### sort(*scale = ££*, *compareFn*) +Obtain a sorted copy of the popped/given scale in ascending order. -### sorted(*scale = $$*, *compareFn*) -Obtain a sorted copy of the current/given scale in ascending order. +### sortInPlace(*scale = $$*, *compareFn*) +Sort the current/given scale in ascending order. ### stepSignature(*word*) Calculate the step signature of an entire scale word. @@ -384,9 +384,6 @@ Transpose a matrix. For modal transposition see rotate(). ### trunc(*interval*) Truncate value towards zero to the nearest integer. -### uniquesOf(*scale = $$*) -Obtain a copy of the current/given scale with only unique intervals kept. - ### unshift(*interval*, *scale = $$*) Prepend an interval at the beginning of the current/given scale. @@ -455,11 +452,8 @@ Calculate the geometric difference of two intervals on a circle. ### circleDistance(*a*, *b*, *equave = 2*) Calculate the geometric distance of two intervals on a circle. -### coalesce(*tolerance = 3.5*, *action = "simplest"*, *preserveBoundary = false*, *scale = $$*) -Coalesce intervals in the current/given scale separated by `tolerance` (default 3.5 cents) into one. `action` is one of 'simplest', 'wilson', 'lowest', 'highest', 'avg', 'havg' or 'geoavg' defaulting to 'simplest'. If `preserveBoundary` is `true` intervals close to unison and the equave are not eliminated. - -### coalesced(*tolerance = 3.5*, *action = "simplest"*, *preserveBoundary = false*, *scale = $$*) -Obtain a copy of the current/given scale where groups of intervals separated by `tolerance` are coalesced into one. `action` is one of 'simplest', 'wilson', 'lowest', 'highest', 'avg', 'havg' or 'geoavg'. If `preserveBoundary` is `true` intervals close to unison and the equave are not eliminated. +### coalesce(*tolerance = 3.5*, *action = "simplest"*, *preserveBoundary = false*, *scale = ££*) +Obtain a copy of the popped/given scale where groups of intervals separated by `tolerance` are coalesced into one. `action` is one of 'simplest', 'wilson', 'lowest', 'highest', 'avg', 'havg' or 'geoavg'. If `preserveBoundary` is `true` intervals close to unison and the equave are not eliminated. ### colorsOf(*scale = $$*) Obtain an array of colors of the current/given scale. @@ -494,26 +488,17 @@ Return the domain of the given interval as a callable converter. ### edColors(*divisions = 12*, *offset = 0*, *equave = 2*) Color every interval in the scale with hue repeating every step of an equal division of `equave`. `offset` rotates the hue wheel. -### elevate(*scale = $$*) -Remove denominators and make the root explicit in the current/given scale. - -### elevated(*scale = $$*) -Obtain a copy of the current/given scale with denominators removed and the root made explicit. +### elevate(*scale = ££*) +Obtain a copy of the popped/given scale with denominators removed and the root made explicit. ### enumerate(*array = $$*) Produce an array of [index, element] pairs from the given current/given array. -### equalize(*divisions*, *scale = $$*) -Quantize the current/given scale to given equal divisions of its equave. - -### equalized(*divisions*, *scale = $$*) -Obtain a copy of the current/given scale quantized to given equal divisions of its equave. +### equalize(*divisions*, *scale = ££*) +Obtain a copy of the popped/given scale quantized to given equal divisions of its equave. -### equaveReduce(*scale = $$*) -Reduce the current/given scale by its equave. - -### equaveReduced(*scale = $$*) -Obtain a copy of the current/given scale reduced by its equave. +### equaveReduce(*scale = ££*) +Obtain a copy of the popped/given scale reduced by its equave. ### eulerGenus(*guide*, *root = 1*, *equave = 2*) Span a lattice from all divisors of the guide-tone rotated to the root-tone. @@ -524,8 +509,8 @@ Calculate e raised to the power of x. ### fail(*message*) Throw the given message as an error. -### flatRepeat(*times = 2*, *scale = $$*) -Repeat the current/given intervals as-is without accumulating equaves. Clears the scale if the number of repeats is zero. +### flatRepeat(*times = 2*, *scale = ££*) +Repeat the popped/given intervals as-is without accumulating equaves. ### ftom(*freq*) Convert absolute frequency to MIDI note number / MTS value (fractional semitones with A440 = 69). @@ -536,18 +521,12 @@ Calculate the geometric mean of the factors. ### geodiff(*array*) Calculate the geometric differences between the factors. -### ground(*scale = $$*) -Use the first interval in the current/given scale as the implicit unison. - -### grounded(*scale = $$*) -Obtain a copy of the current/given scale that uses the first interval as the implicit unison. +### ground(*scale = ££*) +Obtain a copy of the popped/given scale that uses the first interval as the implicit unison. ### gs(*generators*, *size*, *period = 2*, *numPeriods = 1*) Stack a periodic array of generators up to the given size which must be a multiple of the number of periods. -### harmonicsOf(*fundamental*, *scale = $$*) -Obtain a copy of the current/given scale quantized to harmonics of the given fundamental. - ### havg(*...terms*) Calculate the harmonic mean of the terms. @@ -560,12 +539,6 @@ Calculate the square root of the sum of squares of the arguments. ### keys(*record*) Obtain an array of keys of the record. -### label(*labels*, *scale = $$*) -Apply labels (or colors) from the first array to the current/given scale. Can also apply a single color to the whole scale. - -### labeled(*labels*, *scale = $$*) -Apply labels (or colors) from the first array to a copy of the current/given scale. Can also apply a single color to the whole scale. - ### labelsOf(*scale = $$*) Obtain an array of labels of the current/given scale. @@ -578,8 +551,8 @@ Calculate the logarithm of x base 10. ### log2(*x*) Calculate the logarithm of x base 2. -### mergeOffset(*offsets*, *overflow = "drop"*, *scale = $$*) -Merge the given offset or polyoffset of the current/given scale onto itself. `overflow` is one of 'keep', 'drop' or 'wrap' and controls what to do with offset intervals outside of current bounds. +### mergeOffset(*offsets*, *overflow = "drop"*, *scale = ££*) +Obtain a copy of the popped/given scale with the given offset or polyoffset merged into it. `overflow` is one of 'keep', 'drop' or 'wrap' and controls what to do with offset intervals outside of current bounds. ### mos(*numberOfLargeSteps*, *numberOfSmallSteps*, *sizeOfLargeStep = 2*, *sizeOfSmallStep = 1*, *up = niente*, *down = niente*, *equave = 2*) Generate a Moment-Of-Symmetry scale with the given number number of large and small steps. `up` defines the brightness of the mode i.e. the number of major intervals from the root. Alternatively `down` defines the darkness of the mode i.e. the number of minor intervals from the root. The default `equave` is the octave `2/1`. @@ -596,8 +569,8 @@ Convert interval to (relative) FJS using neutral comma flavors. ### numerator(*x*) Calculate the numerator of x in reduced form. -### o(*scale = $$*) -Obtain a copy of the current/given scale in the default overtonal interpretation. +### o(*scale = ££*) +Obtain a copy of the popped/given scale in the default overtonal interpretation. ### octaplex(*b0*, *b1*, *b2*, *b3*, *equave = 2*, *withUnity = false*) Generate a 4-dimensional octaplex a.k.a. 20-cell from the given basis intervals. @@ -608,11 +581,8 @@ Generate all fractions with odd limit <= `limit` reduced to between 1 (exclusive ### oddLimitOf(*x*, *equave = 2*) Calculate the odd limit of x. Here 'odd' means not divisible by the equave. -### organize(*tolerance = niente*, *action = "simplest"*, *preserveBoundary = false*, *scale = $$*) -Reduce the current/given scale by its last interval, sort the result and filter out duplicates. If `tolerance` is given near-duplicates are coalesced instead using the given `action`. If `preserveBoundary` is `true` intervals close to unison and the equave are not eliminated. - -### organized(*tolerance = niente*, *action = "simplest"*, *preserveBoundary = false*, *scale = $$*) -Obtain a copy of the current/given scale reduced by its last interval, sorted and with duplicates filtered out. If `tolerance` is given near-duplicates are coalesced instead using the given `action`. If `preserveBoundary` is `true` intervals close to unison and the equave are not eliminated. +### organize(*tolerance = niente*, *action = "simplest"*, *preserveBoundary = false*, *scale = ££*) +Obtain a copy of the popped/given scale reduced by its last interval, sorted and with duplicates filtered out. If `tolerance` is given near-duplicates are coalesced instead using the given `action`. If `preserveBoundary` is `true` intervals close to unison and the equave are not eliminated. ### parallelotope(*basis*, *ups = niente*, *downs = niente*, *equave = 2*, *basisSizeHints = niente*, *equaveSizeHint = niente*) Span a parallelotope by extending a basis combinatorically. `ups` defaults to all ones while `downs` defaults to all zeros. The size hints are used to get the correct period reduction when generating a preimage. @@ -620,8 +590,8 @@ Span a parallelotope by extending a basis combinatorically. `ups` defaults to al ### periodiff(*array*) Calculate the geometric differences of the periodic interval pattern. -### periostack(*guideGenerator*, *array = $$*) -Stack the current/given inflections along with the guide generator into a periodic sequence of steps. +### periostack(*guideGenerator*, *array = ££*) +Stack the popped/given inflections along with the guide generator into a periodic sequence of steps. ### pow(*x*, *y*) Calculate x to the power of y. @@ -629,11 +599,8 @@ Calculate x to the power of y. ### prod(*factors = $$*) Calculate the (linear) product of the factors or the current scale i.e. the logarithmic sum. -### randomVariance(*amount*, *varyEquave = false*, *scale = $$*) -Add random variance to the current/given scale. - -### randomVaried(*amount*, *varyEquave = false*, *scale = $$*) -Obtain a copy of the current/given scale with random variance added. +### randomVariance(*amount*, *varyEquave = false*, *scale = ££*) +Obtain a copy of the popped/given scale with random variance added. ### range(*start*, *stop = niente*, *step = 1*) Obtain an array of integers from `start` to `stop - 1`. When only a single parameter is given `range(0, n)` is returned. @@ -644,17 +611,11 @@ Create a finite segment of a Rank-2 scale by stacking the given generator agains ### realizeWord(*word*, *sizes*, *equave = niente*) Realize a scale word like "LLsLLLs" as a concrete scale with the given step sizes. One step size may be omitted and inferred based on the size of the `equave` (default `2`). -### reduce(*scale = $$*) -Reduce the current/given scale by its equave. Issue a warning if the scale was already reduced. - -### reduced(*scale = $$*) -Obtain a copy of the current/given scale reduced by its equave. Issue a warning if the scale was already reduced. +### reduce(*scale = ££*) +Obtain a copy of the popped/given scale reduced by its equave. Issue a warning if the scale was already reduced. -### reflect(*scale = $$*) -Reflect the current/given scale about unison. - -### reflected(*scale = $$*) -Obtain a copy of the current/given scale reflected about unison. +### reflect(*scale = ££*) +Obtain a copy of the popped/given scale reflected about unison. ### relin(*interval*) Convert interval to relative linear representation. @@ -662,44 +623,26 @@ Convert interval to relative linear representation. ### relog(*interval*) Convert interval to relative logarithmic representation. -### repeat(*times = 2*, *scale = $$*) -Stack the current scale on top of itself. Clears the scale if the number of repeats is zero. - -### repeated(*times = 2*, *scale = $$*) -Stack the current/given scale on top of itself. - -### repeatedLinear(*times = 2*, *scale = $$*) -Repeat the current/given scale shifted linearly each time. - -### repeatLinear(*times = 2*, *scale = $$*) -Repeat the current/given scale shifted linearly each time. Clears the scale if the number of repeats is zero. +### repeat(*times = 2*, *scale = ££*) +Stack the popped/given scale on top of itself. -### replace(*interval*, *replacement*, *scale = $$*) -Replace occurences of `interval` in the current/given scale by `replacement`. +### repeatLinear(*times = 2*, *scale = ££*) +Repeat the popped/given scale shifted linearly each time. -### replaced(*interval*, *replacement*, *scale = $$*) -Obtain a copy of the current/given scale with occurences of `interval` replaced by `replacement`. +### replace(*interval*, *replacement*, *scale = ££*) +Obtain a copy of the popped/given scale with occurences of `interval` replaced by `replacement`. -### replaceStep(*step*, *replacement*, *scale = $$*) -Replace relative occurences of `step` in the current/given scale by `replacement`. +### replaceStep(*step*, *replacement*, *scale = ££*) +Obtain a copy of the popped/given scale with relative occurences of `step` replaced by `replacement`. -### retrovert(*scale = $$*) -Retrovert the current/given scale (negative harmony i.e reflect and transpose). +### retrovert(*scale = ££*) +Obtain an retroverted copy of the popped/given scale (negative harmony i.e. reflect and transpose). -### retroverted(*scale = $$*) -Obtain an retroverted copy of the current/given scale (negative harmony i.e. reflect and transpose). +### revpose(*scale = ££*) +Obtain a copy of the popped/given scale that sounds in the opposite direction. -### revpose(*scale = $$*) -Change the sounding direction. Converts a descending scale to an ascending one. - -### revposed(*scale = $$*) -Obtain a copy of the current/given scale that sounds in the opposite direction. - -### rotate(*onto = 1*, *scale = $$*) -Rotate the current/given scale onto the given degree. - -### rotated(*onto = 1*, *scale = $$*) -Obtain a copy of the current/given scale rotated onto the given degree. +### rotate(*onto = 1*, *scale = ££*) +Obtain a copy of the popped/given scale rotated onto the given degree. ### sanitize(*interval*) Get rid of interval formatting, color and label. @@ -713,35 +656,20 @@ Calculate the hyperbolic sine of x. ### sqrt(*x*) Calculate the square root of the input. -### stack(*array = $$*) -Cumulatively stack the current/given intervals on top of each other. - -### stacked(*array*) -Obtain a copy of the current/given intervals cumulatively stacked on top of each other. +### stack(*array = ££*) +Cumulatively stack the popped/given intervals on top of each other. -### stackLinear(*array = $$*) -Cumulatively sum the numbers of the current/given array. +### stackLinear(*array = ££*) +Cumulatively sum the numbers of the popped/given array. -### stepReplaced(*step*, *replacement*, *scale = $$*) -Obtain a copy of the current/given scale with relative occurences of `step` replaced by `replacement`. - -### stretch(*amount*, *scale = $$*) -Stretch the current/given scale by the given amount. A value of `1` corresponds to no change. - -### stretched(*amount*, *scale = $$*) -Obtain a copy of the current/given scale streched by the given amount. A value of `1` corresponds to no change. +### stretch(*amount*, *scale = ££*) +Obtain a copy of the popped/given scale streched by the given amount. A value of `1` corresponds to no change. ### subharmonics(*start*, *end*) Generate a subharmonic segment including the given start and end points. -### subharmonicsOf(*overtone*, *scale = $$*) -Obtain a copy of the current/given scale quantized to subharmonics of the given overtone. - -### subset(*degrees*, *scale = $$*) -Only keep the given degrees of the current/given scale. Omitting the zero degree rotates the scale. - -### subsetOf(*degrees*, *scale = $$*) -Obtain a copy of the current/given scale with only the given degrees kept. Omitting the zero degree rotates the scale. +### subset(*degrees*, *scale = ££*) +Obtain a copy of the popped/given scale with only the given degrees kept. Omitting the zero degree rotates the scale. ### sum(*terms = $$*) Calculate the (linear) sum of the terms or the current scale. @@ -752,11 +680,11 @@ Calculate the hyperbolic tangent of x. ### tet(*divisions*, *equave = 2*) Generate an equal temperament with the given number of divisions of the given equave/octave. -### toHarmonics(*fundamental*, *scale = $$*) -Quantize the current/given scale to harmonics of the given fundamental. +### toHarmonics(*fundamental*, *scale = ££*) +Obtain a copy of the popped/given scale quantized to harmonics of the given fundamental. -### toSubharmonics(*overtone*, *scale = $$*) -Quantize the current/given scale to subharmonics of the given overtone. +### toSubharmonics(*overtone*, *scale = ££*) +Obtain a copy of the current/given scale quantized to subharmonics of the given overtone. ### trap(*message*) Produce a function that fails with the given message when called. @@ -767,17 +695,14 @@ Find a combination of two vals that is closer to just intonation. ### tune3(*a*, *b*, *c*, *numIter = 1*, *weighting = "tenney"*) Find a combination of three vals that is closer to just intonation. -### u(*scale = $$*) -Obtain a undertonal reflection of the current/given overtonal scale. +### u(*scale = ££*) +Obtain a undertonal reflection of the popped/given overtonal scale. -### unperiostack(*array = $$*) -Convert the current/given periodic sequence of steps into inflections of the last interval as the guide generator. +### unperiostack(*array = ££*) +Convert the popped/given periodic sequence of steps into inflections of the last interval as the guide generator. -### unstack(*array = $$*) -Unstack the current/given scale into steps. - -### unstacked(*array*) -Calculate the relative steps in the current/given scale. +### unstack(*array = ££*) +Unstack the popped/given scale into steps. ### values(*record*) Obtain an array of values of the record. @@ -797,6 +722,3 @@ Calculate the Weil height of the interval. Natural logarithm of the maximum of n ### wellTemperament(*commaFractions*, *comma = 81/80*, *down = 0*, *generator = 3/2*, *period = 2*) Generate a well-temperament by cumulatively modifying the pure fifth `3/2` (or a given generator) by fractions of the syntonic/given comma. -### withOffset(*offsets*, *overflow = "drop"*, *scale = $$*) -Obtain a copy of the current/given scale with the given offset or polyoffset merged into it. `overflow` is one of 'keep', 'drop' or 'wrap' and controls what to do with offset intervals outside of current bounds. - diff --git a/documentation/advanced-dsl.md b/documentation/advanced-dsl.md index 266881de..a8219ba9 100644 --- a/documentation/advanced-dsl.md +++ b/documentation/advanced-dsl.md @@ -155,7 +155,7 @@ Ternary expressions short-circuit i.e. only the test expression and the chosen r Functions are declared using the `riff` keyword followed by the name of the function followed by the parameters of the function. ```ocaml riff subharmonics(start, end) { - return retroverted(start::end) + return /end::start } ``` Above the `return` statement is suprefluous. We could've left it out and let the result unroll out of the block. @@ -165,7 +165,7 @@ Default values for function parameters may be given using `param = value` syntax Due to popular demand there's also the `fn` alias for function declaration. ```ocaml fn pythagoras(up, down = 0) { - sorted([3^i rdc 2 for i of [-down..up]]) + sort([3^i rdc 2 for i of [-down..up]]) } ``` @@ -174,8 +174,21 @@ Once declared, functions can be called: `subharmonics(4, 8)` evaluates to `[8/7, while `pythagoras(4)` evaluates to `[9/8, 81/64, 3/2, 27/16, 2]`. The missing `down` argument defaulted to `0`. +#### Stblib conventions +You may have noticed that we passed an argument to `sort` in the body of `fn pythagoras`. We could've achieved the same with. +```ocaml +fn pythagoras(up, down = 0) { + [3^i rdc 2 for i of [-down..up]] + return sort() +} +``` + +This is because by convention built-in and standard library functions use the popped parent scale `££` (i.e. `pop$$`) as a default argument. If an argument is passed in, the pop doesn't happen and anything the riff/function produces is concatenated onto the current scale instead of replacing its contents. + +Some functions like `sort` and `reverse` have in-place variants (`sortInPlace` and `reverseInplace`) that return nothing but modify the contents of the input array instead. + ### Lambda expressions -Functions can be defined inline using the arrow (`=>`). e.g. `const subharmonics = ((start, end) => retroverted(start::end))`. +Functions can be defined inline using the arrow (`=>`). e.g. `const subharmonics = ((start, end) => retrovert(start::end))`. ## Throwing To interupt execution you can throw a string message. @@ -207,7 +220,7 @@ sort() ``` This first results in `$ = [3, 5, 7, 11, 13, 17]` which gets reduced to `$ = [3/2, 5/4, 7/4, 11/8, 13/8, 17/16]`. Adding the octave and sorting gives the final result `$ = [17/16, 5/4, 11/8, 3/2, 13/8, 7/4, 2]`. -Or the same with a oneliner `sorted(primes(17) rdc 2)` demonstrating the utility of broadcasting and *ceiling reduction* in a context where the unison is implicit and coincides with repeated octaves. +Or the same with a oneliner `sort(primes(17) rdc 2)` demonstrating the utility of broadcasting and *ceiling reduction* in a context where the unison is implicit and coincides with repeated octaves. ## Stdlib SonicWeave comes with batteries included. diff --git a/documentation/intermediate-dsl.md b/documentation/intermediate-dsl.md index 8c4096cc..40aa783b 100644 --- a/documentation/intermediate-dsl.md +++ b/documentation/intermediate-dsl.md @@ -616,7 +616,7 @@ The logarithmic rounding operator (`by`) measures closeness geometrically `dista The non-ceiling a.k.a floor variants of modulo behave as they commonly do in mathematics where `x mod x` evaluates to `0` while the ceiling variants are more useful in a musical context. -Just as the clockface starts from 12 `12 modc 12` evaluates to `12`. The fact that `P1 modc P8` evaluates to `P8` and that the unison is implicit in SonicWeave environments like Scale Workshop means that the major pentatonic scale becomes a simple oneliner `sorted([-1..3] * P5 modc P8)` evaluating to: +Just as the clockface starts from 12 `12 modc 12` evaluates to `12`. The fact that `P1 modc P8` evaluates to `P8` and that the unison is implicit in SonicWeave environments like Scale Workshop means that the major pentatonic scale becomes a simple oneliner `sort([-1..3] * P5 modc P8)` evaluating to: ```ocaml M2 P4 diff --git a/documentation/technical.md b/documentation/technical.md index af67be6d..89bfa5a8 100644 --- a/documentation/technical.md +++ b/documentation/technical.md @@ -287,32 +287,72 @@ The Basic Latin block is listed in full. Other blocks only where used. | U+007D | } | Block end, record end | | U+007E | ~ | Universal operator preference wing | | U+007F | *DEL* | *N/A* | -| TODO | Â | TODO | -| TODO | ⊕ | TODO | -| TODO | ⊖ | TODO | -| TODO | × | TODO | -| TODO | ÷ | TODO | -| TODO | ⊗ | TODO | -| TODO | √ | TODO | -| TODO | ∧ | TODO | -| U+2228 | ∨ | TODO | -| TODO | ° | TODO | -| TODO | ¢ | TODO | -| TODO | € | TODO | -| TODO | ¶ | TODO | -| TODO | ¼ | TODO | -| TODO | ½ | TODO | -| TODO | ¾ | TODO | -| TODO | 𝄪 | TODO | -| TODO | 𝄫 | TODO | -| TODO | 𝄳 | TODO | -| TODO | 𝄲 | TODO | -| TODO | ♯ | TODO | -| TODO | ‡ | TODO | -| TODO | ♮ | TODO | -| TODO | ♭ | TODO | -| TODO | ¥ | TODO | -| TODO | (Greek lowercase) | TODO | +| U+00A2 | ¢ | Cents unit | +| U+00A3 | £ | Popped scale | +| U+00A5 | ¥ | Template argument | +| U+00B0 | ° | Edosteps unit | +| U+00B6 | ¶ | Pilcrowspoob (meme) | +| U+00BC | ¼ | One-quarter accidental prefix | +| U+00BD | ½ | Semi accidental prefix, interordinal interval | +| U+00BE | ¾ | Three-quarters accidental prefix | +| U+00C2 | Â | Augmented interval | +| U+00D7 | × | Multiplication | +| U+00F7 | ÷ | Unary inversion, binary division (loose binding) | +| U+03B1 | α | Semioctave pitch alpha | +| U+03B2 | β | Semioctave pitch beta | +| U+03B3 | γ | Semioctave pitch gamma | +| U+03B4 | δ | Semioctave pitch delta | +| U+03B5 | ε | Semioctave pitch epsilon | +| U+03B6 | ζ | Semioctave pitch zeta | +| U+03B7 | η | Semioctave pitch eta | +| U+03B8 | θ | *Reserved pitch theta* | +| U+03B9 | ι | *Reserved pitch iota* | +| U+03BA | κ | *Reserved pitch kappa* | +| U+03BB | λ | *Reserved pitch lambda* | +| U+03BC | μ | *Reserved pitch mu* | +| U+03BD | ν | *Reserved pitch nu* | +| U+03BE | ξ | *Reserved pitch xi* | +| U+03BF | ο | *Reserved pitch omicron* | +| U+03C0 | π | *Reserved pitch pi* | +| U+03C1 | ρ | *Reserved pitch rho* | +| U+03C2 | ς | *Reserved pitch final sigma* | +| U+03C3 | σ | *Reserved pitch sigma* | +| U+03C4 | τ | *Reserved pitch tau* | +| U+03C5 | υ | *Reserved pitch upsilon* | +| U+03C6 | φ | Semiquartal pitch phi | +| U+03C7 | χ | Semiquartal pitch chi | +| U+03C8 | ψ | Semiquartal pitch psi | +| U+03C9 | ω | Semiquartal pitch omega | +| U+2021 | ‡ | Semisharp accidental | +| U+20AC | € | Jorp (meme) | +| U+2150 | ⅐ | One-seventh accidental prefix | +| U+2151 | ⅑ | One-ninth accidental prefix | +| U+2152 | ⅒ | One-tenth accidental prefix | +| U+2153 | ⅓ | One-third accidental prefix | +| U+2154 | ⅔ | Two-thirds accidental prefix | +| U+2155 | ⅕ | One-fifth accidental prefix | +| U+2156 | ⅖ | Two-fifths accidental prefix | +| U+2157 | ⅗ | Three-fifths accidental prefix | +| U+2158 | ⅘ | Four-fifths accidental prefix | +| U+2159 | ⅙ | One-sixth accidental prefix | +| U+215A | ⅚ | Five-sixths accidental prefix | +| U+215B | ⅛ | One-eighth accidental prefix | +| U+215C | ⅜ | Three-eighths accidental prefix | +| U+215D | ⅝ | Five-eighths accidental prefix | +| U+215E | ⅞ | Seven-eighths accidental prefix | +| U+221A | √ | Unary square root | +| U+2227 | ∧ | Unary up | +| U+2228 | ∨ | Unary down | +| U+226F | ♯ | Sharp accidental | +| U+2295 | ⊕ | Lens-addition | +| U+2296 | ⊖ | Lens-subtraction | +| U+2297 | ⊗ | Tensor product | +| U+266E | ♮ | Natural accidental | +| U+266D | ♭ | Flat accidental | +| U+1D12A | 𝄪 | Double-sharp accidental | +| U+1D12B | 𝄫 | Double-flat accidental | +| U+1D133 | 𝄳 | Semiflat accidental | +| U+1D132 | 𝄲 | Semisharp accidental | ## Next steps diff --git a/examples/hexany.sw b/examples/hexany.sw index c1696d16..7930815c 100644 --- a/examples/hexany.sw +++ b/examples/hexany.sw @@ -19,11 +19,11 @@ for (const i in factors) { (* Now we have a scale with combinations {3, 5, 7, 15, 21, 35} (in some order) *) -(* Make 3 the root *) -combo => combo % 3 +(* Make 3 the root using implicit mapping *) +combo => combo / 3 -(* Reduce each by the octave *) -combo => combo rd 2 +(* Reduce each by the octave using vector broadcasting over the popped scale *) +pop$ rd 2 (* Sort in ascending order *) sort() diff --git a/src/parser/__tests__/expression.spec.ts b/src/parser/__tests__/expression.spec.ts index 60285e92..5210e7ed 100644 --- a/src/parser/__tests__/expression.spec.ts +++ b/src/parser/__tests__/expression.spec.ts @@ -685,7 +685,7 @@ describe('SonicWeave expression evaluator', () => { }); it('has array comprehensions', () => { - const pyth12 = evaluate('map(str, sorted([3^i rdc 2 for i of [-4..7]]))'); + const pyth12 = evaluate('map(str, sort([3^i rdc 2 for i of [-4..7]]))'); expect(pyth12).toEqual([ '2187/2048', '9/8', @@ -1742,7 +1742,7 @@ describe('SonicWeave expression evaluator', () => { }); it('can sort an array of strings', () => { - const swac = evaluate('sorted([..."SonicWeave"])') as string[]; + const swac = evaluate('sort([..."SonicWeave"])') as string[]; expect(swac.join('')).toBe('SWaceeinov'); }); diff --git a/src/parser/__tests__/source.spec.ts b/src/parser/__tests__/source.spec.ts index 6893efe5..1e15bc68 100644 --- a/src/parser/__tests__/source.spec.ts +++ b/src/parser/__tests__/source.spec.ts @@ -159,7 +159,7 @@ describe('SonicWeave parser', () => { const scale = parseSource(` const segment = [1..5]; segment; - reverse(segment); + reverseInPlace(segment); map(i => i + 10, segment); `); expect(scale).toHaveLength(10); @@ -1313,9 +1313,9 @@ describe('SonicWeave parser', () => { const scale = expand(`{ riff popSort(i) { if (isArray(i)) { - return sorted(popAll(i)); + return sort(popAll(i)); } - return sorted(popAll($$)); + return sort(popAll($$)); } 5:8:7:9:6:10; @@ -1850,7 +1850,7 @@ describe('SonicWeave parser', () => { 5/4 3/2 2/1 - sorted(%£ rdc pop$[-1]) + sort(%£ rdc pop$[-1]) `); expect(scale).toEqual(['4/3', '8/5', '2']); }); @@ -1881,4 +1881,26 @@ describe('SonicWeave parser', () => { '12\\12', ]); }); + + it('can label intervals after generating', () => { + const scale = parseSource(` + 4/3 + 3/2 + 2 + £ ["fourth", "fifth", "octave"] + `); + expect(scale).toHaveLength(3); + expect(scale[0].label).toBe('fourth'); + expect(scale[1].label).toBe('fifth'); + expect(scale[2].label).toBe('octave'); + }); + + it('can paint the whole scale', () => { + const scale = expand('3::6;white;£ "bob"'); + expect(scale).toEqual([ + '4/3 "bob" white', + '5/3 "bob" white', + '6/3 "bob" white', + ]); + }); }); diff --git a/src/parser/__tests__/stdlib.spec.ts b/src/parser/__tests__/stdlib.spec.ts index 6a0821f5..db854926 100644 --- a/src/parser/__tests__/stdlib.spec.ts +++ b/src/parser/__tests__/stdlib.spec.ts @@ -400,19 +400,19 @@ describe('SonicWeave standard library', () => { }); it('can spell the just major chord sub-overtonally', () => { - const subOvertone = parseSource('revposed(6:5:4)'); + const subOvertone = parseSource('revpose(6:5:4)'); expect(subOvertone[0].totalCents()).toBeCloseTo(386.313714); // major third expect(subOvertone[1].totalCents()).toBeCloseTo(701.955001); // perfect fifth }); it('can spell the just major chord retroverted', () => { - const retroversion = parseSource('retroverted(10:12:15)'); + const retroversion = parseSource('retrovert(10:12:15)'); expect(retroversion[0].totalCents()).toBeCloseTo(386.313714); // major third expect(retroversion[1].totalCents()).toBeCloseTo(701.955001); // perfect fifth }); it('can spell the just major chord reflected', () => { - const reflection = parseSource('reflected(15:12:10)'); + const reflection = parseSource('reflect(15:12:10)'); expect(reflection[0].totalCents()).toBeCloseTo(386.313714); // major third expect(reflection[1].totalCents()).toBeCloseTo(701.955001); // perfect fifth }); @@ -438,13 +438,13 @@ describe('SonicWeave standard library', () => { }); it('can spell the just minor chord in first inversion with root on 1/1', () => { - const firstInversion = parseSource('reflected(3:5:4)'); + const firstInversion = parseSource('reflect(3:5:4)'); expect(firstInversion[0].totalCents()).toBeCloseTo(-884.358713); // major sixth expect(firstInversion[1].totalCents()).toBeCloseTo(-498.044999); // perfect fourth }); it('can spell the just minor chord in second inversion with root on 1/1', () => { - const secondInversion = parseSource('reflected(6:5:8)'); + const secondInversion = parseSource('reflect(6:5:8)'); expect(secondInversion[0].totalCents()).toBeCloseTo(315.641287); // major sixth expect(secondInversion[1].totalCents()).toBeCloseTo(-498.044999); // perfect fourth }); @@ -498,25 +498,12 @@ describe('SonicWeave standard library', () => { }); it('has a copying repeater', () => { - const bigScale = expand('repeated(2, 3::6)'); + const bigScale = expand('repeat(2, 3::6)'); // XXX: Would be cool if that last 4/1 was 12/3, but can't come up // with formatting rules that wouldn't mess up everything else. expect(bigScale).toEqual(['4/3', '5/3', '6/3', '8/3', '10/3', '4/1']); }); - it('can label intervals after generating', () => { - const scale = parseSource(` - 4/3 - 3/2 - 2 - label(["fourth", "fifth", "octave"]) - `); - expect(scale).toHaveLength(3); - expect(scale[0].label).toBe('fourth'); - expect(scale[1].label).toBe('fifth'); - expect(scale[2].label).toBe('octave'); - }); - it('preserves color upon reflection', () => { const scale = parseSource('4/3 green;3/2;2/1 red;reflect()'); expect(scale).toHaveLength(3); @@ -757,8 +744,8 @@ describe('SonicWeave standard library', () => { const ls = labelsOf() clear() 3::6 - label(cs) - label(ls) + £ cs + £ ls }`); expect(scale).toEqual(['4/3 "one" red', '5/3 "two"', '6/3']); }); @@ -808,8 +795,8 @@ describe('SonicWeave standard library', () => { it('has inline labeling', () => { const pythagoras = expand(` - labeled(['F', 'C', 'G', 'D', 'A', 'E', 'B'], [3^i rdc 2 white for i of [-2..4]]) - labeled(['Gb', 'Db', 'Ab', 'Eb', 'Bb'], [3^i rdc 2 black for i of [-7..-3]]) + ['F', 'C', 'G', 'D', 'A', 'E', 'B'] [3^i rdc 2 white for i of [-2..4]] + ['Gb', 'Db', 'Ab', 'Eb', 'Bb'] [3^i rdc 2 black for i of [-7..-3]] sort() `); expect(pythagoras).toEqual([ @@ -920,15 +907,6 @@ describe('SonicWeave standard library', () => { ]); }); - it('can paint the whole scale', () => { - const scale = expand('3::6;white;label("bob")'); - expect(scale).toEqual([ - '4/3 "bob" white', - '5/3 "bob" white', - '6/3 "bob" white', - ]); - }); - it('can detect domains (linear)', () => { const scale = expand( '10/8;12/10;7/6;stack();i => simplify(i) if isLinear(i) else i' @@ -1008,12 +986,12 @@ describe('SonicWeave standard library', () => { }); it('is stacked', () => { - const scale = expand('stacked([5/4, 6/5])'); + const scale = expand('stack([5/4, 6/5])'); expect(scale).toEqual(['5/4', '3/2']); }); it("isn't that stacked actually", () => { - const scale = expand('unstacked([5/4, 3/2])'); + const scale = expand('unstack([5/4, 3/2])'); expect(scale).toEqual(['5/4', '6/5']); }); diff --git a/src/parser/expression.ts b/src/parser/expression.ts index 30af9bc8..3ea5227e 100644 --- a/src/parser/expression.ts +++ b/src/parser/expression.ts @@ -902,6 +902,15 @@ export class ExpressionVisitor { return empty; } + if ( + node.start === null && + node.second === null && + node.penultimate === false && + node.end === null + ) { + return typeof object === 'string' ? object : ([...object] as Interval[]); + } + let start = 0; let step = 1; const pu = node.penultimate; diff --git a/src/parser/statement.ts b/src/parser/statement.ts index 2f8cc2d0..8bc945ed 100644 --- a/src/parser/statement.ts +++ b/src/parser/statement.ts @@ -7,7 +7,7 @@ import { repr, upcastBool, SonicWeavePrimitive, - sort, + sortInPlace, temper, absolute, } from '../stdlib'; @@ -866,7 +866,7 @@ export class StatementVisitor { } const tail = scale.slice(-entries.length); scale.length = scale.length - tail.length; - sort.bind(subVisitor)(tail); + sortInPlace.bind(subVisitor)(tail); scale.push(...tail); } else { this.spendGas(scale.length); diff --git a/src/stdlib/builtin.ts b/src/stdlib/builtin.ts index 6872c2c1..d03cb996 100644 --- a/src/stdlib/builtin.ts +++ b/src/stdlib/builtin.ts @@ -27,7 +27,7 @@ import { } from '../monzo'; import {type ExpressionVisitor} from '../parser'; import {MosOptions, mos, nthNominal} from 'moment-of-symmetry'; -import {expressionToString} from '../ast'; +import {Expression, expressionToString} from '../ast'; import { BasisElement, FJSFlavor, @@ -65,7 +65,7 @@ import { tenneyHeight as pubTenney, wilsonHeight as pubWilson, track as pubTrack, - sort as pubSort, + sortInPlace as pubSortInPlace, repr as pubRepr, str as pubStr, centsColor as pubCentsColor, @@ -77,6 +77,18 @@ const {version: VERSION} = require('../../package.json'); // === Library === +const PARENT_SCALE: Record = { + // Only one of these ends up being used. + scale: { + type: 'Identifier', + id: '$$', + }, + array: { + type: 'Identifier', + id: '$$', + }, +}; + // == Constants const E = new Interval(TimeReal.fromValue(Math.E), 'linear'); const LN10 = new Interval(TimeReal.fromValue(Math.LN10), 'linear'); @@ -1223,11 +1235,12 @@ flatten.__doc__ = flatten.__node__ = builtinNode(flatten); function clear(this: ExpressionVisitor, scale?: Interval[]) { + // Remember that built-ins don't have full scope construction so this is the parent scale. scale ??= this.currentScale; scale.length = 0; } clear.__doc__ = 'Remove the contents of the current/given scale.'; -clear.__node__ = builtinNode(clear); +clear.__node__ = builtinNode(clear, PARENT_SCALE); function tail( this: ExpressionVisitor, @@ -1717,7 +1730,10 @@ Object.defineProperty(hasConstantStructure_, 'name', { }); hasConstantStructure_.__doc__ = 'Returns `true` if the current/given scale has constant structure (i.e. every scale degree is unambiguous).'; -hasConstantStructure_.__node__ = builtinNode(hasConstantStructure_); +hasConstantStructure_.__node__ = builtinNode( + hasConstantStructure_, + PARENT_SCALE +); function stepString_(this: ExpressionVisitor, scale?: Interval[]) { scale ??= this.currentScale; @@ -1731,7 +1747,7 @@ Object.defineProperty(stepString_, 'name', { }); stepString_.__doc__ = 'Obtain the step string associated with the scale e.g. "LLsLLLs" for Ionian.'; -stepString_.__node__ = builtinNode(stepString_); +stepString_.__node__ = builtinNode(stepString_, PARENT_SCALE); function slice( array: string | SonicWeavePrimitive[], @@ -1891,39 +1907,43 @@ function maximum( maximum.__doc__ = 'Obtain the argument with the maximum value.'; maximum.__node__ = builtinNode(maximum); -function sort( +function sortInPlace( this: ExpressionVisitor, scale?: SonicWeaveValue, compareFn?: SonicWeaveFunction ) { // XXX: The implementation works for strings, we just cheat the types here. - pubSort.bind(this)(scale as Interval[], compareFn); + pubSortInPlace.bind(this)(scale as Interval[], compareFn); } -sort.__doc__ = 'Sort the current/given scale in ascending order.'; -sort.__node__ = builtinNode(sort); +sortInPlace.__doc__ = 'Sort the current/given scale in ascending order.'; +sortInPlace.__node__ = builtinNode(sortInPlace, PARENT_SCALE); /** - * Obtain a sorted copy of the current/given scale in ascending order. + * Obtain a sorted copy of the popped/given scale in ascending order. * @param this {@link ExpressionVisitor} instance providing the current scale and context for comparing across echelons. * @param scale Musical scale to sort (defaults to context scale). * @param compareFn SonicWeave riff for comparing elements. */ -function sorted( +function sort( this: ExpressionVisitor, scale?: SonicWeavePrimitive[], compareFn?: SonicWeaveFunction ) { - scale ??= this.currentScale; + // Remember that built-ins don't have full scope construction so this is the parent scale. + scale ??= this.popScale(false); + if (!Array.isArray(scale)) { + throw new Error('Only arrays can be sorted.'); + } scale = [...scale]; - sort.bind(this)(scale, compareFn); + sortInPlace.bind(this)(scale, compareFn); return scale; } -sorted.__doc__ = - 'Obtain a sorted copy of the current/given scale in ascending order.'; -sorted.__node__ = builtinNode(sorted); +sort.__doc__ = + 'Obtain a sorted copy of the popped/given scale in ascending order.'; +sort.__node__ = builtinNode(sort); -function uniquesOf(this: ExpressionVisitor, scale?: SonicWeaveValue) { - scale ??= this.currentScale; +function keepUnique(this: ExpressionVisitor, scale?: SonicWeaveValue) { + scale ??= this.popScale(false); if (!Array.isArray(scale)) { throw new Error('An array is required.'); } @@ -1939,17 +1959,8 @@ function uniquesOf(this: ExpressionVisitor, scale?: SonicWeaveValue) { } return result; } -uniquesOf.__doc__ = - 'Obtain a copy of the current/given scale with only unique intervals kept.'; -uniquesOf.__node__ = builtinNode(uniquesOf); - -function keepUnique(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { - scale ??= this.currentScale; - const uniques = uniquesOf.bind(this)(scale); - scale.length = 0; - scale.push(...uniques); -} -keepUnique.__doc__ = 'Only keep unique intervals in the current/given scale.'; +keepUnique.__doc__ = + 'Obtain a copy of the popped/given scale with only unique intervals kept.'; keepUnique.__node__ = builtinNode(keepUnique); function automos(this: ExpressionVisitor) { @@ -1981,22 +1992,24 @@ automos.__doc__ = 'If the current scale is empty, generate absolute Diamond-mos notation based on the current config.'; automos.__node__ = builtinNode(automos); -function reverse(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { +function reverseInPlace( + this: ExpressionVisitor, + scale?: SonicWeavePrimitive[] +) { scale ??= this.currentScale; scale.reverse(); } -reverse.__doc__ = 'Reverse the order of the current/given scale.'; -reverse.__node__ = builtinNode(reverse); +reverseInPlace.__doc__ = 'Reverse the order of the current/given scale.'; +reverseInPlace.__node__ = builtinNode(reverseInPlace, PARENT_SCALE); -function reversed(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { - scale ??= this.currentScale; +function reverse(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { + scale ??= this.popScale(false); scale = [...scale]; - reverse.bind(this)(scale); + reverseInPlace.bind(this)(scale); return scale; } -reversed.__doc__ = - 'Obtain a copy of the current/given scale in reversed order.'; -reversed.__node__ = builtinNode(reversed); +reverse.__doc__ = 'Obtain a copy of the popped/given scale in reversed order.'; +reverse.__node__ = builtinNode(reverse); function pop( this: ExpressionVisitor, @@ -2021,7 +2034,7 @@ function pop( } pop.__doc__ = 'Remove and return the last interval in the current/given scale. Optionally an index to pop may be given.'; -pop.__node__ = builtinNode(pop); +pop.__node__ = builtinNode(pop, PARENT_SCALE); function popAll(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { scale ??= this.currentScale; @@ -2030,7 +2043,7 @@ function popAll(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { return result; } popAll.__doc__ = 'Remove and return all intervals in the current/given scale.'; -popAll.__node__ = builtinNode(popAll); +popAll.__node__ = builtinNode(popAll, PARENT_SCALE); function push( this: ExpressionVisitor, @@ -2060,7 +2073,7 @@ function push( } push.__doc__ = 'Append an interval onto the current/given scale. Optionally an index to push after may be given.'; -push.__node__ = builtinNode(push); +push.__node__ = builtinNode(push, PARENT_SCALE); function shift(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { scale ??= this.currentScale; @@ -2071,7 +2084,7 @@ function shift(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { } shift.__doc__ = 'Remove and return the first interval in the current/given scale.'; -shift.__node__ = builtinNode(shift); +shift.__node__ = builtinNode(shift, PARENT_SCALE); function unshift( this: ExpressionVisitor, @@ -2084,7 +2097,7 @@ function unshift( } unshift.__doc__ = 'Prepend an interval at the beginning of the current/given scale.'; -unshift.__node__ = builtinNode(unshift); +unshift.__node__ = builtinNode(unshift, PARENT_SCALE); function insert( this: ExpressionVisitor, @@ -2104,7 +2117,7 @@ function insert( } insert.__doc__ = 'Insert an interval into the current/given scale keeping it sorted.'; -insert.__node__ = builtinNode(insert); +insert.__node__ = builtinNode(insert, PARENT_SCALE); function dislodge( this: ExpressionVisitor, @@ -2131,7 +2144,7 @@ function dislodge( } dislodge.__doc__ = 'Remove and return the first element equal to the given one from the current/given scale.'; -dislodge.__node__ = builtinNode(dislodge); +dislodge.__node__ = builtinNode(dislodge, PARENT_SCALE); function extend( this: ExpressionVisitor, @@ -2167,7 +2180,7 @@ function length(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) { return fromInteger(scale.length); } length.__doc__ = 'Return the number of intervals in the scale.'; -length.__node__ = builtinNode(length); +length.__node__ = builtinNode(length, PARENT_SCALE); function map( this: ExpressionVisitor, @@ -2182,7 +2195,7 @@ function map( ); } map.__doc__ = 'Map a riff over the given/current scale producing a new scale.'; -map.__node__ = builtinNode(map); +map.__node__ = builtinNode(map, PARENT_SCALE); function remap( this: ExpressionVisitor, @@ -2197,7 +2210,7 @@ function remap( } remap.__doc__ = 'Map a riff over the given/current scale replacing the contents.'; -remap.__node__ = builtinNode(remap); +remap.__node__ = builtinNode(remap, PARENT_SCALE); function filter( this: ExpressionVisitor, @@ -2213,7 +2226,7 @@ function filter( } filter.__doc__ = 'Obtain a copy of the given/current scale containing values that evaluate to `true` according to the `tester` riff.'; -filter.__node__ = builtinNode(filter); +filter.__node__ = builtinNode(filter, PARENT_SCALE); function distill( this: ExpressionVisitor, @@ -2228,7 +2241,7 @@ function distill( } distill.__doc__ = 'Remove intervals from the given/current scale that evaluate to `false` according to the `tester` riff.'; -distill.__node__ = builtinNode(distill); +distill.__node__ = builtinNode(distill, PARENT_SCALE); function arrayReduce( this: ExpressionVisitor, @@ -2261,7 +2274,7 @@ function arrayReduce( } arrayReduce.__doc__ = 'Reduce the given/current scale to a single value by the `reducer` riff which takes an accumulator, the current value, the current index and the array as arguments.'; -arrayReduce.__node__ = builtinNode(arrayReduce); +arrayReduce.__node__ = builtinNode(arrayReduce, PARENT_SCALE); function arrayRepeat( this: ExpressionVisitor, @@ -2277,10 +2290,11 @@ function arrayRepeat( return []; } scale ??= this.currentScale; + // XXX: Should these be independent copies? return [].concat(...Array(c).fill(scale)); } arrayRepeat.__doc__ = 'Repeat the given/current array or string `count` times.'; -arrayRepeat.__node__ = builtinNode(arrayRepeat); +arrayRepeat.__node__ = builtinNode(arrayRepeat, PARENT_SCALE); function some( this: ExpressionVisitor, @@ -2298,7 +2312,7 @@ function some( } some.__doc__ = "Test whether at least one element in the array passes the test implemented by the provided function. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise it returns false. It doesn't modify the array. If no array is provided it defaults to the current scale. If no test is provided it defaults to truthiness."; -some.__node__ = builtinNode(some); +some.__node__ = builtinNode(some, PARENT_SCALE); function every( this: ExpressionVisitor, @@ -2316,7 +2330,7 @@ function every( } every.__doc__ = "Tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value. It doesn't modify the array. If no array is provided it defaults to the current scale. If no test is provided it defaults to truthiness."; -every.__node__ = builtinNode(every); +every.__node__ = builtinNode(every, PARENT_SCALE); /** * Obtain an array of `[key, value]` pairs of the record. @@ -2599,12 +2613,11 @@ export const BUILTIN_CONTEXT: Record = { random, randomCents, sort, - sorted, + sortInPlace, keepUnique, - uniquesOf, automos, reverse, - reversed, + reverseInPlace, pop, popAll, push, diff --git a/src/stdlib/prelude.ts b/src/stdlib/prelude.ts index 386d3988..0c512d7f 100644 --- a/src/stdlib/prelude.ts +++ b/src/stdlib/prelude.ts @@ -1,15 +1,14 @@ export const PRELUDE_VOLATILES = ` (* XXX: This is only here to bypass scope optimization so that Scale Workshop can hook warn(). *) -riff reduce(scale = $$) { - "Reduce the current/given scale by its equave. Issue a warning if the scale was already reduced."; - if (not scale) { - return; - } +riff reduce(scale = ££) { + "Obtain a copy of the popped/given scale reduced by its equave. Issue a warning if the scale was already reduced."; + if (not scale) + return []; if (every(scale >= 1 vand scale <= scale[-1])) { warn("The scale was already reduced by its equave. Did you mean 'simplify'?"); - return; + return scale[..]; } - equaveReduce(scale); + return equaveReduce(scale); } `; @@ -262,42 +261,32 @@ riff mul(...factors) { return prod factors; } -riff stackLinear(array = $$) { - "Cumulatively sum the numbers of the current/given array."; - $ = array; +riff stackLinear(array = ££) { + "Cumulatively sum the numbers of the popped/given array."; + array; let i = 0r; const len = real(length($)); while (++i < len) $[i] ~+= $[i-1r]; - return; } riff cumsum(array) { "Calculate the cumulative sums of the terms in the array."; - array; - stackLinear(); + return stackLinear(array); } -riff stack(array = $$) { - "Cumulatively stack the current/given intervals on top of each other."; - $ = array; +riff stack(array = ££) { + "Cumulatively stack the popped/given intervals on top of each other."; + array; let i = 0r; const len = real(length($)); while (++i < len) $[i] ~*= $[i-1r]; - return; } riff cumprod(array) { "Calculate the cumulative products of the factors in the array i.e. logarithmic cumulative sums."; - array; - stack(); -} - -riff stacked(array) { - "Obtain a copy of the current/given intervals cumulatively stacked on top of each other."; - array; - stack(); + return stack(array); } riff diff(array) { @@ -308,85 +297,49 @@ riff diff(array) { $[i] ~-= $[i - 1r]; } -riff unstack(array = $$) { - "Unstack the current/given scale into steps."; - $ = array; +riff unstack(array = ££) { + "Unstack the popped/given scale into steps."; + array; let i = real(length($)); while (--i) $[i] ~%= $[i - 1r]; - return; } riff geodiff(array) { "Calculate the geometric differences between the factors."; - array; - unstack(); + return unstack(array); } -riff unstacked(array) { - "Calculate the relative steps in the current/given scale."; +riff unperiostack(array = ££) { + "Convert the popped/given periodic sequence of steps into inflections of the last interval as the guide generator."; array; - unstack(); -} - -riff unperiostack(array = $$) { - "Convert the current/given periodic sequence of steps into inflections of the last interval as the guide generator."; - $ = array; const first = $[0] ~% $[-1]; let i = real(length($)); while (--i) $[i] ~%= $[i - 1r]; $[0] = first; - return; } riff periodiff(array) { "Calculate the geometric differences of the periodic interval pattern."; - array; - unperiostack(); + return unperiostack(array); } -riff periostack(guideGenerator, array = $$) { - "Stack the current/given inflections along with the guide generator into a periodic sequence of steps."; +riff periostack(guideGenerator, array = ££) { + "Stack the popped/given inflections along with the guide generator into a periodic sequence of steps."; if (not isInterval(guideGenerator)) throw "Guide generator must be an interval."; - $ = array; + array; $[0] ~*= guideGenerator; let i = 0r; const len = real(length($)); while (++i < len) $[i] ~*= $[i-1r]; - return; } riff antiperiodiff(constantOfIntegration, array) { "Calculate the cumulative geometric sums of a periodic difference pattern. Undoes what periodiff does."; - array; - periostack(constantOfIntegration); -} - -riff label(labels, scale = $$) { - "Apply labels (or colors) from the first array to the current/given scale. Can also apply a single color to the whole scale."; - if (isArray(labels)) { - let i = -1r; - const len = real(length(labels) min length(scale)); - while (++i < len) - scale[i] = scale[i] labels[i]; - } else { - remap(i => i labels, scale); - } -} - -riff labeled(labels, scale = $$) { - "Apply labels (or colors) from the first array to a copy of the current/given scale. Can also apply a single color to the whole scale."; - if (isArray(labels)) { - for (const [i, l] of zip(scale, labels)) { - i l; - } - scale[length(labels)..]; - } else { - scale labels; - } + return periostack(constantOfIntegration, array); } riff enumerate(array = $$) { @@ -402,7 +355,7 @@ riff tune(a, b, numIter = 1, weighting = 'tenney') { const y = a + b; const z = 2 * b - a; - [a, b] = sorted([a, b, x, y, z], (u, v) => cosJIP(v, weighting) - cosJIP(u, weighting)); + [a, b] = sort([a, b, x, y, z], (u, v) => cosJIP(v, weighting) - cosJIP(u, weighting)); } return a; } @@ -430,7 +383,7 @@ riff tune3(a, b, c, numIter = 1, weighting = 'tenney') { b + c - a, ]; - [a, b, c] = sorted(combos, (u, v) => cosJIP(v, weighting) - cosJIP(u, weighting)); + [a, b, c] = sort(combos, (u, v) => cosJIP(v, weighting) - cosJIP(u, weighting)); } return a; } @@ -478,9 +431,9 @@ riff mos(numberOfLargeSteps, numberOfSmallSteps, sizeOfLargeStep = 2, sizeOfSmal mosSubset(numberOfLargeSteps, numberOfSmallSteps, sizeOfLargeStep, sizeOfSmallStep, up, down); const divisions = abs $[-1]; if (equave == 2) - £ \\ divisions; + return $ \\ divisions; else - £ \\ divisions ed equave; + return $ \\ divisions ed equave; } riff rank2(generator, up, down = 0, period = 2, numPeriods = 1, generatorSizeHint = niente, periodSizeHint = niente) { @@ -506,10 +459,10 @@ riff rank2(generator, up, down = 0, period = 2, numPeriods = 1, generatorSizeHin const periods = ceil(hint ~/_ periodSizeHint) - 1; const scale = generator ~^ [-down..up] ~% period ~^ periods; const scales = zip(scale, hint ~rdc periodSizeHint); - sort(scales, (a, b) => compare(a[1], b[1])); + sortInPlace(scales, (a, b) => compare(a[1], b[1])); [a for [a, b] of scales]; period vor pop(); - repeat(numPeriods); + return repeat(numPeriods); } riff cps(factors, count, equave = 2, withUnity = false) { @@ -521,7 +474,7 @@ riff cps(factors, count, equave = 2, withUnity = false) { ground(); equave; equaveReduce(); - sort(); + return sort(); } riff wellTemperament(commaFractions, comma = 81/80, down = 0, generator = 3/2, period = 2) { @@ -557,17 +510,13 @@ riff parallelotope(basis, ups = niente, downs = niente, equave = 2, basisSizeHin const axis = generator ~^ [-down..up]; axis[down] = bleach(axis[down]); - axis ~tns~ popAll($$); + axis ~tns~ ££; } - if (basisSizeHints == niente and equaveSizeHint == niente) { - $ = $$; - £ ~rdc equave; - sort(); - return $; - } + if (basisSizeHints == niente and equaveSizeHint == niente) + return sort($ ~rdc equave); - const scale = popAll(); + const scale = £; basis = basis_; ups = ups_; downs = downs_; @@ -584,15 +533,14 @@ riff parallelotope(basis, ups = niente, downs = niente, equave = 2, basisSizeHin const up = pop(ups); const down = pop(downs); - generator ~^ [-down..up] ~tns~ popAll($$); + generator ~^ [-down..up] ~tns~ ££; } - const hint = popAll(); + const hint = £; const equaves = ceil(hint ~/_ equaveSizeHint) - 1; const scales = zip(scale ~% equave ~^ equaves, hint ~rdc equaveSizeHint); - sort(scales, (a, b) => compare(a[1], b[1])); - [a for [a, b] of scales]; + [a for [a, b] of sort(scales, (a, b) => compare(a[1], b[1]))]; } riff eulerGenus(guide, root = 1, equave = 2) { @@ -621,10 +569,11 @@ riff octaplex(b0, b1, b2, b3, equave = 2, withUnity = false) { b1 ~^ s1 ~* b2 ~^ s2; sort(); - if (not withUnity) ground(); + if (not withUnity) + ground(); equave; equaveReduce(); - sort(); + return sort(); } riff gs(generators, size, period = 2, numPeriods = 1) { @@ -635,7 +584,7 @@ riff gs(generators, size, period = 2, numPeriods = 1) { period; equaveReduce(); sort(); - repeat(numPeriods); + return repeat(numPeriods); } riff csgs(generators, ordinal = 1, period = 2, numPeriods = 1, maxSize = 100) { @@ -654,12 +603,12 @@ riff csgs(generators, ordinal = 1, period = 2, numPeriods = 1, maxSize = 100) { if (length($$) > maxSize) { throw "No constant structure found before reaching maximum size."; } - sort($$); + sortInPlace($$); if (hasConstantStructure($$)) { void(--ordinal); } } - repeat(numPeriods); + return repeat(numPeriods); } riff vao(denominator, maxNumerator, divisions = 12, tolerance = 5.0, equave = 2) { @@ -696,10 +645,10 @@ riff concordanceShell(denominator, maxNumerator, divisions = 12, tolerance = 5.0 } } equave = divisions * step; - if (equave not of result) { + if (equave not of result) equave; - } - sorted(result); + result; + return sort(); } riff oddLimit(limit, equave = 2) { @@ -708,10 +657,8 @@ riff oddLimit(limit, equave = 2) { while (++remainder < equave) { [remainder, remainder ~+ equave .. limit]; } - const odds = popAll(); - [n % d for n of odds for d of odds if gcd(n, d) == 1]; - £ rdc equave; - sort(); + [n % d for n of £ for d of £ if gcd(n, d) == 1]; + return sort($ rdc equave); } riff realizeWord(word, sizes, equave = niente) { @@ -750,100 +697,64 @@ riff realizeWord(word, sizes, equave = niente) { for (const letter of word) { sizes[letter]; } - stack(); + return stack(); } (** Scale modification **) -riff equaveReduce(scale = $$) { - "Reduce the current/given scale by its equave."; - remap(i => i ~rdc scale[-1], scale); -} - -riff equaveReduced(scale = $$) { - "Obtain a copy of the current/given scale reduced by its equave."; - scale; - equaveReduce(); +riff equaveReduce(scale = ££) { + "Obtain a copy of the popped/given scale reduced by its equave."; + return scale ~rdc scale[-1]; } -riff reduced(scale = $$) { - "Obtain a copy of the current/given scale reduced by its equave. Issue a warning if the scale was already reduced."; +riff revpose(scale = ££) { + "Obtain a copy of the popped/given scale that sounds in the opposite direction." scale; - reduce(); -} - -riff revpose(scale = $$) { - "Change the sounding direction. Converts a descending scale to an ascending one." - $ = scale; const equave = pop(); £ ~% equave; reverse(); %equave; - return; } -riff revposed(scale = $$) { - "Obtain a copy of the current/given scale that sounds in the opposite direction." +riff retrovert(scale = ££) { + "Obtain an retroverted copy of the popped/given scale (negative harmony i.e. reflect and transpose)."; scale; - revpose(); -} - -riff retrovert(scale = $$) { - "Retrovert the current/given scale (negative harmony i.e reflect and transpose)."; - $ = scale; const equave = pop(); equave %~ £; reverse(); equave; - return; -} - -riff retroverted(scale = $$) { - "Obtain an retroverted copy of the current/given scale (negative harmony i.e. reflect and transpose)."; - scale; - retrovert(); -} - -riff reflect(scale = $$) { - "Reflect the current/given scale about unison."; - remap(i => %~i, scale); } -riff reflected(scale = $$) { - "Obtain a copy of the current/given scale reflected about unison."; - map(i => %~i, scale); +riff reflect(scale = ££) { + "Obtain a copy of the popped/given scale reflected about unison."; + return %~scale; } -riff u(scale = $$) { - "Obtain a undertonal reflection of the current/given overtonal scale."; - return reflected(scale) +riff u(scale = ££) { + "Obtain a undertonal reflection of the popped/given overtonal scale."; + return reflect(scale); }; -riff o(scale = $$) { - "Obtain a copy of the current/given scale in the default overtonal interpretation."; +riff o(scale = ££) { + "Obtain a copy of the popped/given scale in the default overtonal interpretation."; scale; } -riff rotate(onto = 1, scale = $$) { - "Rotate the current/given scale onto the given degree."; - $ = scale; +riff rotate(onto = 1, scale = ££) { + "Obtain a copy of the popped/given scale rotated onto the given degree."; + scale; onto = real(onto mod length($)); - if (not onto) return; + if (not onto) + return $; const equave = $[-1]; - while (--onto) equave *~ shift(); + while (--onto) + equave *~ shift(); const root = shift(); £ ~% root; equave colorOf(root) labelOf(root); - return; } -riff rotated(onto = 1, scale = $$) { - "Obtain a copy of the current/given scale rotated onto the given degree."; - scale; - rotate(onto); -} - -riff repeated(times = 2, scale = $$) { - "Stack the current/given scale on top of itself."; +riff repeat(times = 2, scale = ££) { + "Stack the popped/given scale on top of itself."; if (not times) return []; scale; @@ -852,17 +763,8 @@ riff repeated(times = 2, scale = $$) { scale ~* level; } -riff repeat(times = 2, scale = $$) { - "Stack the current scale on top of itself. Clears the scale if the number of repeats is zero."; - $ = scale; - const segment = $[..]; - clear(); - repeated(times, segment); - return; -} - -riff repeatedLinear(times = 2, scale = $$) { - "Repeat the current/given scale shifted linearly each time."; +riff repeatLinear(times = 2, scale = ££) { + "Repeat the popped/given scale shifted linearly each time."; if (not times) return []; scale; @@ -871,55 +773,28 @@ riff repeatedLinear(times = 2, scale = $$) { (scale ~- 1) ~+ level ~+ 1; } -riff repeatLinear(times = 2, scale = $$) { - "Repeat the current/given scale shifted linearly each time. Clears the scale if the number of repeats is zero."; - $ = scale; - const segment = $[..]; - clear(); - repeatedLinear(times, segment); - return; +riff flatRepeat(times = 2, scale = ££) { + "Repeat the popped/given intervals as-is without accumulating equaves."; + return arrayRepeat(times, scale); } -riff flatRepeat(times = 2, scale = $$) { - "Repeat the current/given intervals as-is without accumulating equaves. Clears the scale if the number of repeats is zero."; - $ = scale; - const segment = $[..]; - clear(); - arrayRepeat(times, segment); - return; -} - -riff ground(scale = $$) { - "Use the first interval in the current/given scale as the implicit unison."; - $ = scale; +riff ground(scale = ££) { + "Obtain a copy of the popped/given scale that uses the first interval as the implicit unison."; + scale; const root = shift(); - £ ~% root; - return; + return $ ~% root; } -riff grounded(scale = $$) { - "Obtain a copy of the current/given scale that uses the first interval as the implicit unison."; +riff elevate(scale = ££) { + "Obtain a copy of the popped/given scale with denominators removed and the root made explicit."; scale; - ground(); -} - -riff elevate(scale = $$) { - "Remove denominators and make the root explicit in the current/given scale."; - $ = scale; unshift(sanitize($[-1]~^0)); const root = sanitize(%~gcd()); - £ ~* root; - return; + return $ ~* root; } -riff elevated(scale = $$) { - "Obtain a copy of the current/given scale with denominators removed and the root made explicit."; - scale; - elevate(); -} - -riff subsetOf(degrees, scale = $$) { - "Obtain a copy of the current/given scale with only the given degrees kept. Omitting the zero degree rotates the scale."; +riff subset(degrees, scale = ££) { + "Obtain a copy of the popped/given scale with only the given degrees kept. Omitting the zero degree rotates the scale."; scale = scale[..]; const equave = pop(scale); unshift(equave ~^ 0, scale); @@ -928,61 +803,30 @@ riff subsetOf(degrees, scale = $$) { equave; } -riff subset(degrees, scale = $$) { - "Only keep the given degrees of the current/given scale. Omitting the zero degree rotates the scale."; - $ = scale; - const result = subsetOf(degrees); - clear(); - result; - return; -} - -riff toHarmonics(fundamental, scale = $$) { - "Quantize the current/given scale to harmonics of the given fundamental."; - $ = scale; - £ to~ %~fundamental colorOf(£) labelOf(£); - return; +riff toHarmonics(fundamental, scale = ££) { + "Obtain a copy of the popped/given scale quantized to harmonics of the given fundamental."; + return scale to~ %~fundamental colorOf(scale) labelOf(scale); } -riff harmonicsOf(fundamental, scale = $$) { - "Obtain a copy of the current/given scale quantized to harmonics of the given fundamental."; - scale; - toHarmonics(); -} - -riff toSubharmonics(overtone, scale = $$) { - "Quantize the current/given scale to subharmonics of the given overtone."; - $ = scale; - %~(%~£ to~ %~overtone) colorOf(£) labelOf(£); - return; -} - -riff subharmonicsOf(overtone, scale = $$) { +riff toSubharmonics(overtone, scale = ££) { "Obtain a copy of the current/given scale quantized to subharmonics of the given overtone."; - scale; - toSubharmonics(); + return %~(%~scale to~ %~overtone) colorOf(scale) labelOf(scale); } -riff equalize(divisions, scale = $$) { - "Quantize the current/given scale to given equal divisions of its equave."; - $ = scale; +riff equalize(divisions, scale = ££) { + "Obtain a copy of the popped/given scale quantized to given equal divisions of its equave."; + scale; let step = 1 \\ divisions; if ($[-1] <> 2) step ed= $[-1]; - £ by~ step colorOf(£) labelOf(£); - return; + return $ by~ step colorOf($) labelOf($); } -riff equalized(divisions, scale = $$) { - "Obtain a copy of the current/given scale quantized to given equal divisions of its equave."; +riff mergeOffset(offsets, overflow = 'drop', scale = ££) { + "Obtain a copy of the popped/given scale with the given offset or polyoffset merged into it. \`overflow\` is one of 'keep', 'drop' or 'wrap' and controls what to do with offset intervals outside of current bounds."; + if (not isArray(offsets)) + offsets = [offsets]; scale; - equalize(); -} - -riff mergeOffset(offsets, overflow = 'drop', scale = $$) { - "Merge the given offset or polyoffset of the current/given scale onto itself. \`overflow\` is one of 'keep', 'drop' or 'wrap' and controls what to do with offset intervals outside of current bounds."; - if (not isArray(offsets)) offsets = [offsets]; - $ = scale; const equave = pop(); unshift(equave ~^ 0); @@ -1002,49 +846,31 @@ riff mergeOffset(offsets, overflow = 'drop', scale = $$) { if (overflow <> 'keep') { equave; } - keepUnique(); - return; + return keepUnique(); } -riff withOffset(offsets, overflow = 'drop', scale = $$) { - "Obtain a copy of the current/given scale with the given offset or polyoffset merged into it. \`overflow\` is one of 'keep', 'drop' or 'wrap' and controls what to do with offset intervals outside of current bounds."; - scale; - mergeOffset(offsets, overflow); +riff stretch(amount, scale = ££) { + "Obtain a copy of the popped/given scale streched by the given amount. A value of \`1\` corresponds to no change."; + return scale ~^ amount; } -riff stretch(amount, scale = $$) { - "Stretch the current/given scale by the given amount. A value of \`1\` corresponds to no change."; - $ = scale; - £ ~^ amount; - return; -} - -riff stretched(amount, scale = $$) { - "Obtain a copy of the current/given scale streched by the given amount. A value of \`1\` corresponds to no change."; - scale ~^ amount; -} - -riff randomVariance(amount, varyEquave = false, scale = $$) { - "Add random variance to the current/given scale."; - $ = scale; +riff randomVariance(amount, varyEquave = false, scale = ££) { + "Obtain a copy of the popped/given scale with random variance added."; + scale; let equave; - if (not varyEquave) equave = pop(); + if (not varyEquave) + equave = pop(); i => i ~* (amount ~^ (2 * random() - 1)); - if (not varyEquave) equave; - return; -} - -riff randomVaried(amount, varyEquave = false, scale = $$) { - "Obtain a copy of the current/given scale with random variance added."; - scale; - randomVariance(amount, varyEquave); + if (not varyEquave) + equave; } -riff coalesced(tolerance = 3.5, action = 'simplest', preserveBoundary = false, scale = $$) { - "Obtain a copy of the current/given scale where groups of intervals separated by \`tolerance\` are coalesced into one.\\ +riff coalesce(tolerance = 3.5, action = 'simplest', preserveBoundary = false, scale = ££) { + "Obtain a copy of the popped/given scale where groups of intervals separated by \`tolerance\` are coalesced into one.\\ \`action\` is one of 'simplest', 'wilson', 'lowest', 'highest', 'avg', 'havg' or 'geoavg'.\\ If \`preserveBoundary\` is \`true\` intervals close to unison and the equave are not eliminated."; - if (not scale) return []; + if (not scale) + return []; let last; let group = []; @@ -1061,11 +887,9 @@ riff coalesced(tolerance = 3.5, action = 'simplest', preserveBoundary = false, s } else if (action == 'geoavg') { geoavg(...group); } else if (action == 'wilson') { - sort(group, (a, b) => wilsonHeight(a) - wilsonHeight(b)); - group[0]; + sort(group, (a, b) => wilsonHeight(a) - wilsonHeight(b))[0]; } else { - sort(group, (a, b) => tenneyHeight(a) - tenneyHeight(b)); - group[0]; + sort(group, (a, b) => tenneyHeight(a) - tenneyHeight(b))[0]; } group = []; } @@ -1079,24 +903,14 @@ riff coalesced(tolerance = 3.5, action = 'simplest', preserveBoundary = false, s void(pop($$)); } scale[-1]; - if (length(scale) == 1) return; + if (length(scale) == 1) + return $; while ($[-1] ~= $[-2]) void(pop()); } -riff coalesce(tolerance = 3.5, action = 'simplest', preserveBoundary = false, scale = $$) { - "Coalesce intervals in the current/given scale separated by \`tolerance\` (default 3.5 cents) into one. \\ - \`action\` is one of 'simplest', 'wilson', 'lowest', 'highest', 'avg', 'havg' or 'geoavg' defaulting to 'simplest'.\\ - If \`preserveBoundary\` is \`true\` intervals close to unison and the equave are not eliminated."; - $ = scale; - scale = $[..]; - clear(); - coalesced(tolerance, action, preserveBoundary, scale); - return; -} - -riff replaced(interval, replacement, scale = $$) { - "Obtain a copy of the current/given scale with occurences of \`interval\` replaced by \`replacement\`."; +riff replace(interval, replacement, scale = ££) { + "Obtain a copy of the popped/given scale with occurences of \`interval\` replaced by \`replacement\`."; for (const existing of scale) { if (existing == interval) { replacement; @@ -1106,46 +920,22 @@ riff replaced(interval, replacement, scale = $$) { } } -riff replace(interval, replacement, scale = $$) { - "Replace occurences of \`interval\` in the current/given scale by \`replacement\`."; - $ = scale; - scale = $[..]; - clear(); - replaced(interval, replacement, scale); - return; -} - -riff replaceStep(step, replacement, scale = $$) { - "Replace relative occurences of \`step\` in the current/given scale by \`replacement\`."; - $ = scale; - unstack(); +riff replaceStep(step, replacement, scale = ££) { + "Obtain a copy of the popped/given scale with relative occurences of \`step\` replaced by \`replacement\`."; + unstack(scale); replace(step, replacement); - stack(); - return; -} - -riff stepReplaced(step, replacement, scale = $$) { - "Obtain a copy of the current/given scale with relative occurences of \`step\` replaced by \`replacement\`."; - return cumprod(replaced(step, replacement, geodiff(scale))); + return stack(); } -riff organize(tolerance = niente, action = 'simplest', preserveBoundary = false, scale = $$) { - "Reduce the current/given scale by its last interval, sort the result and filter out duplicates.\\ +riff organize(tolerance = niente, action = 'simplest', preserveBoundary = false, scale = ££) { + "Obtain a copy of the popped/given scale reduced by its last interval, sorted and with duplicates filtered out.\\ If \`tolerance\` is given near-duplicates are coalesced instead using the given \`action\`.\\ If \`preserveBoundary\` is \`true\` intervals close to unison and the equave are not eliminated."; - $ = scale; - equaveReduce(); - if (tolerance == niente) keepUnique(); + equaveReduce(scale); + if (tolerance == niente) + keepUnique(); sort(); - if (tolerance <> niente) coalesce(tolerance, action, preserveBoundary); - return; -} - -riff organized(tolerance = niente, action = 'simplest', preserveBoundary = false, scale = $$) { - "Obtain a copy of the current/given scale reduced by its last interval, sorted and with duplicates filtered out.\\ - If \`tolerance\` is given near-duplicates are coalesced instead using the given \`action\`.\\ - If \`preserveBoundary\` is \`true\` intervals close to unison and the equave are not eliminated."; - scale; - organize(tolerance, action, preserveBoundary); + if (tolerance <> niente) + coalesce(tolerance, action, preserveBoundary); } `; diff --git a/src/stdlib/public.ts b/src/stdlib/public.ts index a4134a9d..a2174edb 100644 --- a/src/stdlib/public.ts +++ b/src/stdlib/public.ts @@ -270,7 +270,7 @@ export function track(this: ExpressionVisitor, interval: Interval) { * @param scale Musical scale to sort (defaults to context scale). * @param compareFn SonicWeave riff for comparing elements. */ -export function sort( +export function sortInPlace( this: ExpressionVisitor, scale?: Interval[], compareFn?: Function diff --git a/src/stdlib/runtime.ts b/src/stdlib/runtime.ts index 3aef5b1b..4864ba3e 100644 --- a/src/stdlib/runtime.ts +++ b/src/stdlib/runtime.ts @@ -3,6 +3,7 @@ import { type ArrowFunction, type FunctionDeclaration, type Parameter, + Expression, } from '../ast'; import {Color, Interval, Val} from '../interval'; import {TimeMonzo, TimeReal} from '../monzo'; @@ -101,7 +102,10 @@ export function linearOne() { * @param builtin Function to extract node information from. * @returns Virtual {@link FunctionDeclaration} to be attached to `builtin.__node__`. */ -export function builtinNode(builtin: Function): FunctionDeclaration { +export function builtinNode( + builtin: Function, + defaults?: Record +): FunctionDeclaration { const parameters: Parameter[] = builtin .toString() .split('(', 2)[1] @@ -131,10 +135,13 @@ export function builtinNode(builtin: Function): FunctionDeclaration { } } else if (parameter.id === 'scale') { parameter.defaultValue = { - type: 'Identifier', - id: '$$', + type: 'PopScale', + parent: true, }; } + if (defaults && parameter.id in defaults) { + parameter.defaultValue = defaults[parameter.id]; + } } return { type: 'FunctionDeclaration',