From 8006c2de66bfc04121dab8b589452809858043f9 Mon Sep 17 00:00:00 2001 From: Lumi Pakkanen Date: Thu, 23 May 2024 21:12:15 +0300 Subject: [PATCH] Replace C-style comments with nestable OCaml-style comments ref #330 --- README.md | 9 +- documentation/advanced-dsl.md | 56 ++--- documentation/dsl.md | 133 +++++----- documentation/interchange.md | 39 +-- documentation/intermediate-dsl.md | 246 ++++++++++--------- documentation/technical.md | 12 +- documentation/tempering.md | 14 +- examples/barbados9.sw | 10 +- examples/blackdye.sw | 2 +- examples/bohlen-pierce-just.sw | 10 +- examples/centaura.sw | 6 +- examples/dtmf.sw | 8 +- examples/dynamic-smitonic.sw | 8 +- examples/hexany.sw | 30 +-- examples/interchange.sw | 8 +- examples/keen12.sw | 18 +- examples/lemba10-vars.sw | 12 +- examples/neutral-dorian.sw | 2 +- examples/notation/12edo-srutal.sw | 2 +- examples/notation/25edo-antidiatonic.sw | 8 +- examples/notation/9edo.sw | 4 +- examples/pajara10.sw | 10 +- examples/porcusmine7.sw | 4 +- examples/pote-orgone.sw | 8 +- examples/pythagoras12.sw | 26 +- src/cli.ts | 2 +- src/grammars/base.pegjs | 25 +- src/grammars/sonic-weave.pegjs | 6 +- src/parser/__tests__/expression.spec.ts | 20 ++ src/parser/__tests__/modules.spec.ts | 8 +- src/parser/__tests__/paren-counter.spec.ts | 9 +- src/parser/__tests__/sonic-weave-ast.spec.ts | 44 +++- src/parser/__tests__/source.spec.ts | 73 +++--- src/stdlib/prelude.ts | 22 +- 34 files changed, 484 insertions(+), 410 deletions(-) diff --git a/README.md b/README.md index 4609a1c3..17ff1efd 100644 --- a/README.md +++ b/README.md @@ -22,22 +22,22 @@ Appendix: [tempering](https://github.com/xenharmonic-devs/sonic-weave/blob/main/ ## Highlights Harmonic segment from the 8th harmonic to the 16th (repeating at the octave). -```c +```ocaml 8::16 ``` 10-tone equal temperament -```c +```ocaml tet(10) ``` The major scale in Pythagorean tuning i.e. 3-limit just intonation. -```c +```ocaml sorted(3^[-1..5] rdc 2) ``` Scale title, colors and labels. -```c +```ocaml "Japanese pentatonic koto scale, theoretical. Helmholz/Ellis p.519, nr.110" 9/8 white "Major 2nd" @@ -65,6 +65,7 @@ SonicWeave looks like Javascript with Python semantics, has Haskell ranges and o * Python - Guido van Rossum et. al. * Haskell - Lennart Augustsson et. al. * Zig - Andrew Kelley et. al. +* OCaml - Xavier Leroy et. al. * NumPy - Travis Oliphant et. al. * Scala - Manuel Op de Coul * Scale Workshop 1 - Sean Archibald et. al. diff --git a/documentation/advanced-dsl.md b/documentation/advanced-dsl.md index e1391ffa..3ff22b8e 100644 --- a/documentation/advanced-dsl.md +++ b/documentation/advanced-dsl.md @@ -3,7 +3,7 @@ This document describes programming in the SonicWeave domain-specific language. ## Record broadcasting Records behave like arrays in that operations are broadcast over their values e.g. `{a: 1, b: 2, c:3} * 5` evaluates to -```c +```ocaml { a: 1*5, b: 2*5, @@ -30,7 +30,7 @@ The current scale of the parent block can be accessed using `$$`. ## Defer Defer is used to execute a statement while exiting the current block. -```c +```ocaml let x = 5; { defer x += 2; @@ -41,7 +41,7 @@ assert(x == 7); When there are multiple defers in a single block, they are executed in reverse order. -```c +```ocaml let x = 5; { defer x += 2; @@ -54,7 +54,7 @@ Defer is useful for pushing implicit tempering and general housekeeping to the t ## While "While" loops repeat a statement until the test becames *falsy* e.g. -```c +```ocaml let i = 5 while (--i) { i @@ -64,7 +64,7 @@ results in `$ = [4, 3, 2, 1]`. ## For...of "For..of" loops iterate over array contents or record values e.g. -```c +```ocaml for (const i of [1..5]) { 2 ^ (i % 5) } @@ -73,7 +73,7 @@ results in `$ = [2^1/5, 2^2/5, 2^3/5, 2^4/5, 2]`. ## For..in "For..in" loops iterate over array indices or record keys e.g. -```c +```ocaml for (const k in {a: 123, b: 456}) { 1 k } @@ -82,7 +82,7 @@ results in `$ = [1 "b", 1 "a"]`. With records the order of iteration is indeterm ## Break "While" and "for" loops can be broken out of using the `break` keyword. -```c +```ocaml for (const i of [1..10]) { i \ 6 if (i >= 6) @@ -93,7 +93,7 @@ results in `$ = [1\6, 2\6, 3\6, 4\6, 5\6, 6\6]`. ## Continue The `continue` keyword terminates the execution of the current iteration of a "while" or "for" loop and continues with the next iteration. -```c +```ocaml let i = 8 while (++i <= 16) { if (i > 12 and i mod 2) { @@ -106,7 +106,7 @@ results in `$ = [9/8, 10/8, 11/8, 12/8, 14/8, 16/8]`. ## While..else and for..of..else The `else` branch of a "while" or "for" loop is executed if no `break` statement was encountered e.g. this computes all primes below 12: -```c +```ocaml for (const i of [2..12]) { for (const j of [2..i-1]) { if (i mod j == 0) break @@ -119,14 +119,14 @@ result is `$ = [2, 3, 5, 7, 11]`. ### Array comprehensions "For" loops have an inline counterpart in array comprehensions e.g. -```c +```ocaml [2 ^ i/7 for i of [1..7]] ``` results in `$ = [2^1/7, 2^2/7, 2^3/7, 2^4/7, 2^5/7, 2^6/7, 2]`. #### If clause To conditionally include elements use an `if` clause e.g. -```c +```ocaml [i for i of [1..9] if i mod 3 <> 0] ``` results in `$ = [1, 2, 4, 5, 7, 8]` i.e. all [throdd](https://en.xen.wiki/w/Threeven) numbers below 10. @@ -135,7 +135,7 @@ Above the `<> 0` part is unnecessary. `[i for i of [1..9] if i mod 3]` works the ## If...else Conditional statements are evaluated if the test expression evaluates to `true` otherwise the `else` branch is taken. -```javascript +```ocaml if (3/2 > 700.) { print("The Pythagorean fifth is larger than the perfect fifth of 12-TET") } else { @@ -150,7 +150,7 @@ Ternary expressions short-circuit i.e. only the test expression and the chosen r ## Function declaration Functions are declared using the `riff` keyword followed by the name of the function followed by the parameters of the function. -```javascript +```ocaml riff subharmonics(start, end) { return retroverted(start::end) } @@ -160,7 +160,7 @@ Above the `return` statement is suprefluous. We could've left it out and let the 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. -```javascript +```ocaml fn pythagoras(up, down = 0) { sorted([3^i rdc 2 for i of [-down..up]]) } @@ -176,19 +176,19 @@ Functions can be defined inline using the arrow (`=>`). e.g. `const subharmonics ## Throwing To interupt execution you can throw a string message. -```javascript +```ocaml throw "Something wrong here!" ``` ## Exception handling To resume execution after an error use `try`, `catch` and `finally`. -```javascript +```ocaml try { - fraction(PI) // This throws because PI is irrational. + fraction(PI) (* This throws because PI is irrational. *) print("This won't print"); } catch (e) { print("Caught an exception!"); - print(e); // Prints: "Input is irrational and no tolerance given." + print(e); (* Prints: "Input is irrational and no tolerance given." *) } finally { print("This will print regardless."); } @@ -196,7 +196,7 @@ try { ## Implicit mapping Recall that the default action when encountering a function is to remap the current scale using it. Arrow functions are very handy here: -```javascript +```ocaml primes(3, 17) prime => prime rd 2 2 @@ -264,8 +264,8 @@ You can even use square roots in the basis e.g. `[-1 1>@√2.√3` is monzo repr ## Universal monzos The monzo basis also supports the special symbols `s`, `Hz`, `-1`, `0`, `rc` and `1°`. A conversion like `monzo(-440Hz)` evaluates to -```c -// Hz, -1, 2, 3, 5, 7, 11 +```ocaml +(* Hz, -1, 2, 3, 5, 7, 11 *) [ 1, 1, 3, 0, 1, 0, 1 >@Hz.-1.2.. ``` or `1Hz * (-1) * 2^3 * 5^1 * 11^1` if you break it down linearly. @@ -276,7 +276,7 @@ Two dots after a prime number indicate that exponents of successive primes follo The stepspan of the octave is odd so the semioctave `P8 / 2` doesn't have an obvious ordinal associated with it like how the semififth `P5 / 2` is obviously a third. The semioctave is between a fourth and a fifth therefore SonicWeave designates it as a 4½th. It is the period of [10L 4s](https://en.xen.wiki/w/10L_4s) (with the semififth as the generator) therefore the designated quality is *perfect*. -```c +```ocaml "These two are the same" P8 / 2 "Split octave" P4½ "The perfect fourth-and-a-halfth" @@ -318,11 +318,11 @@ SonicWeave designates Greek counterparts to each of the seven diatonic Latin nom By their construction these nominals are all found in 12-tone equal temperament and are also well-suited for notating 22-TET which is connected to 12-TET along the diaschismic axis. -```c +```ocaml "Srutal[12] a.k.a. Diaschismic[12] tuned to 22-TET" defer 22@ -// First period: Latin-Greek-Latin +(* First period: Latin-Greek-Latin *) C4 = mtof(60) η4 D4 @@ -330,7 +330,7 @@ D4 E4 β4 -// Second period: Greek-Latin-Greek +(* Second period: Greek-Latin-Greek *) γ4 G4 δ4 @@ -338,7 +338,7 @@ A4 ε4 B4 -// Third period: (implicit repetition) +(* Third period: (implicit repetition) *) C5 ``` @@ -349,7 +349,7 @@ You can also declare ups to transport notes between the two periods if you wish The labels indicate the equivalent Greek nominal. -```c +```ocaml "Semioctave heptanominal alternative I" ^ = Aug4 - 1\2 @@ -378,7 +378,7 @@ The perfect intervals `2`, `3/2` and `4/3` are connected by `4/3 == 2 / (3/2)` s In terms of interordinals `n2.5 == P4.5 - n3` so we already have relative notation for the split fourth. Absolute semiquartal notation is obtained by mixing Greek nominals with half-sharps. -```c +```ocaml "Semiquartal 5L 4s notation" C♮4 = mtof(60) D♮4 "Octave-reduced doubled 5th" diff --git a/documentation/dsl.md b/documentation/dsl.md index 8bc0d57a..44220901 100644 --- a/documentation/dsl.md +++ b/documentation/dsl.md @@ -6,7 +6,7 @@ This document describes the SonicWeave domain-specific language for manipulating SonicWeave is related to [Scala .scl](https://www.huygens-fokker.org/scala/scl_format.html) and a successor of Scale Workshop 2. It is intended for constructing microtonal scales that repeat at the octave (or some other period). Let's start with a basic major scale in just intonation: -```c +```ocaml 9/8 5/4 4/3 @@ -19,7 +19,7 @@ Let's start with a basic major scale in just intonation: Note that there's no `1/1` to mark the root. The octave `2/1` does double duty as the root note and the interval of repetition. Let's give labels to the notes with the root on "C". -```c +```ocaml 9/8 "D" 5/4 "E" 4/3 "F" @@ -33,7 +33,7 @@ Notice again how the root and its label come last in the list. Our scale is still pretty abstract in the sense that the notes do not correspond to any specific frequencies. A tool like Scale Workshop 3 sets the reference frequency automatically but let's set it manually here: -```c +```ocaml 1/1 = 262 Hz 9/8 "D" 5/4 "E" @@ -50,7 +50,7 @@ Now the first note has the frequency of 262 oscillations per second and the note SonicWeave is a major upgrade compared to Scale Workshop 2. One of the new features is support for an extended version of the [Functional Just System](https://en.xen.wiki/w/Functional_Just_System) which allows us to spell our scale as follows: -```c +```ocaml C4 = 262 Hz D4 E4^5 @@ -66,7 +66,7 @@ C5 Another common way to notate pitch is using [cents](https://en.wikipedia.org/wiki/Cent_(music)). The octave is worth 1200 cents and following Scala's convention SonicWeave dedicates the decimal dot (`.`) for representing relative musical intervals measured in cents. Using cents up to one decimal of precision our major scale becomes: -```c +```ocaml 1/1 = 262 Hz 203.9 "D" 386.3 "E" @@ -88,7 +88,7 @@ In addition to just intonation and cents SonicWeave has notation for equal divis The notation `n \ m` denotes n steps of m-tone equal temperament. Let's update our example and use the common A440 pitch standard: -```c +```ocaml A4 = 440 Hz = 9\12 2\12 "D" @@ -104,7 +104,7 @@ The expression `440 Hz = 9\12` implicitly sets the reference frequency for 1/1 a To get a taste of the powerful tempering features in SonicWeave we'll spell our scale using (absolute) FJS but add `12@` at the end to tell the runtime to interprete everything in 12-TET. -```c +```ocaml A4 = 440 Hz = 9\12 D4 @@ -122,7 +122,7 @@ We could drop the FJS inflection `^5` from `E4` because in 12-tone equal `E4` an To highlight the power of tempering we can convert our major scale to 31-tone equal temperament simply by switching out the reference interval and the final `12@` with `31@`. -```c +```ocaml A4 = 440 Hz = 23\31 D4 @@ -140,7 +140,7 @@ C5 [FJS](https://en.xen.wiki/w/Functional_Just_System) also has notation for relative intervals like the perfect fifth `P5` between C and G or the major second `M2` between G and A. The microtonal inflections that come after the ordinal number work the same as in absolute FJS. Going back to just intonation our little major scale becomes: -```c +```ocaml P1 = 262 Hz M2 M3^5 @@ -184,7 +184,7 @@ Another breaking change is that *comma decimals* are no longer allowed in comple Microtonal scales can get complicated pretty fast so in addition to string labels we saw before SonicWeave has built-in support for CSS colors. Let's spell out all the notes of 12-tone equal temperament with labels and the usual colors you would find on a piano keyboard, and let's also introduce a handy helper function (`mtof`) from the standard library for converting MIDI note number to a frequency in the A440 pitch standard: -```c +```ocaml 0\12 = mtof(60) 1\12 "C# / Db" black @@ -212,19 +212,22 @@ Colors may be specified using ## Code comments -Anything after two slashes (`//`) is ignored until the end of the line. Everything after a slash and an asterisk (`/*`) is ignored until a corresponding pair (`*/`) is encountered. (This means you cannot nest multi-line comments, as in C and JavaScript.) +Anything between a pair `(*` and `*)` is ignored. Unlike C or JavaScript comments are nestable. -```c -1 = 432 Hz // Good vibes only... Wait what are you doing?! +```ocaml +1 = 432 Hz (* Good vibes only... Wait what are you doing?! *) 11/8 -/** +(** * The undecimal superfourth 11/8 of about 551.3¢ is the simplest superfourth in just intonation, * and as it falls about halfway between 12edo's perfect fourth and tritone, it is very xenharmonic. * * The YouTuber mannfishh has made a video revealing the terrifying truths of the 11th harmonic. - */ + * + * (* This is a nested comment *) + * This is still part of the outer comment block. + *) -// Did you know you can repeat scales at the fifth too? +(* Did you know you can repeat scales at the fifth too? *) 3/2 ``` @@ -241,56 +244,56 @@ By default[^1] adding a sharp sign (`#` or `♯`) to an absolute pitch multiplie Conversely a flat sign (`b` or `♭`) on an absolute pitch shifts its pitch down by around `113.685 c` corresponding to a multiplication by `2048/2187` ≈ `0.93644e` of the underlying frequency. Let's play around with these a bit to get a feel for them: -```c +```ocaml C4 = 262 Hz -/* -Pitch // Frequency | Cents | Ratio -*/ -Db4 // 276.016Hz | 90.225 | 1.053 -C#4 // 279.782Hz | 113.685 | 1.068 -D4 // 294.750Hz | 203.910 | 1.125 -G4 // 393.000Hz | 701.955 | 1.500 -C5 // 524.000Hz | 1200.000 | 2.000 +(* +Pitch (* Frequency | Cents | Ratio *) +*) +Db4 (* 276.016Hz | 90.225 | 1.053 *) +C#4 (* 279.782Hz | 113.685 | 1.068 *) +D4 (* 294.750Hz | 203.910 | 1.125 *) +G4 (* 393.000Hz | 701.955 | 1.500 *) +C5 (* 524.000Hz | 1200.000 | 2.000 *) ``` Looking at the frequencies or the width of the interval against the root note we can see that D flat is lower in pitch than C sharp. They differ by a [Pythagorean comma](https://en.xen.wiki/w/Pythagorean_comma) of around `23.460 c`. The art and science of musical tuning often deals with small intervals like this. One approach is to make it go away i.e. temper it out which leads to 12-tone equal temperament a.k.a. 12ed2. -```c +```ocaml C4 = 262 Hz -// Merge sharps together with the flat of the note above. +(* Merge sharps together with the flat of the note above. *) defer 12@ -/* -Pitch // Frequency | Cents | Ratio -*/ -Db4 // 277.579Hz | 100.000 | 1.059 -C#4 // 277.579Hz | 100.000 | 1.059 -D4 // 294.085Hz | 200.000 | 1.122 -G4 // 392.556Hz | 700.000 | 1.498 -C5 // 524.000Hz | 1200.000 | 2.000 +(* +Pitch (* Frequency | Cents | Ratio *) +*) +Db4 (* 277.579Hz | 100.000 | 1.059 *) +C#4 (* 277.579Hz | 100.000 | 1.059 *) +D4 (* 294.085Hz | 200.000 | 1.122 *) +G4 (* 392.556Hz | 700.000 | 1.498 *) +C5 (* 524.000Hz | 1200.000 | 2.000 *) -// Tempering using 12@ was deferred here. +(* Tempering using 12@ was deferred here. *) ``` Another thing we might notice is that the fifth at `700.0` is only about two cents flat of the frequency ratio `1.5e` of the justly intoned fifth. The major third at `200.0` on the other hand is almost four cents flat of `1.125e`. Small tuning error like these tend to compound the further you go along the chain of fifths. Let's do one final comparison in 19-tone equal temperament: -```c +```ocaml C4 = 262 Hz -// Temper to 19ed2 +(* Temper to 19ed2 *) defer 19@ -/* -Pitch // Frequency | Cents | Ratio -*/ -C#4 // 271.735Hz | 63.158 | 1.037 -Db4 // 281.831Hz | 126.316 | 1.076 -D4 // 292.302Hz | 189.474 | 1.116 -G4 // 391.365Hz | 694.737 | 1.494 -C5 // 524.000Hz | 1200.000 | 2.000 +(* +Pitch (* Frequency | Cents | Ratio *) +*) +C#4 (* 271.735Hz | 63.158 | 1.037 *) +Db4 (* 281.831Hz | 126.316 | 1.076 *) +D4 (* 292.302Hz | 189.474 | 1.116 *) +G4 (* 391.365Hz | 694.737 | 1.494 *) +C5 (* 524.000Hz | 1200.000 | 2.000 *) ``` I've switched around C# and Db because now the effect of the sharp is much more mellow. It's only worth `1\19` or around `63.158 c` here. Systems where the fifth is flatter than in 12ed2 are often nicer to notate and perform because the sharps and flats are close to the corresponding natural pitches and don't cross over like they do in Pythagorean tuning or even sharper systems. @@ -413,7 +416,7 @@ There is a well-known tension between visual similarity with fractions and the d Listing out all of the fractions in a scale can get tedious so there's a shorthand for harmonic (a.k.a. overtonal or otonal) chords where the intervals have simple relationships with each other. Let's take a look at a subset of our major scale: -```c +```ocaml 1/1 = 262 Hz 9/8 @@ -424,7 +427,7 @@ Let's take a look at a subset of our major scale: ``` Expanding the factors for a common denominator gives: -```c +```ocaml 8/8 = 262 Hz 9/8 @@ -436,14 +439,14 @@ Expanding the factors for a common denominator gives: So it's clear that they're in a 8:9:10:12:15:16 relationship. This makes our scale fragment a two liner. -```c +```ocaml 1 = 262 Hz 8:9:10:12:15:16 ``` The missing 4/3 and 5/3 are in a 3:4:5 relationship with the root. With a final call to `sort()` our full major scale becomes. -```c +```ocaml 1 = 262 Hz 8:9:10:12:15:16 3:4:5 @@ -454,19 +457,19 @@ Just as in scales, the unison is implicit in enumerated chords. Enumerations can span multiple lines so something like this is valid syntax: -```c +```ocaml 1 = 262 Hz -8 "C" : // Implicit +8 "C" : (* Implicit *) 9 "D" : 10 "E" : 12 "G" : 15 "B" -3 "C" : // Implicit +3 "C" : (* Implicit *) 4 "F" : 5 "A" : -6 "C" // Octave, interval of repetition +6 "C" (* Octave, interval of repetition *) sort() ``` @@ -475,10 +478,10 @@ sort() Simple consecutive harmonics often sound consonant or at least fuse together so there's syntax for segments of the harmonic series. -```c +```ocaml 1 = 262 Hz -// Same as 8:9:10:11:12:13:14:15:16 +(* Same as 8:9:10:11:12:13:14:15:16 *) 8::16 ``` @@ -494,7 +497,7 @@ The difference between the perspectives is that of inversion i.e. reflection abo A string of characters that is not attached to an interval becomes the title of the scale visible on export. -```c +```ocaml "Subharmonics 10 through 5" 1 = 262 Hz @@ -511,7 +514,7 @@ Equal temperaments simplify this complexity to a finite collection of distinct p Let's demonstrate with an approximation of the 5-limit major scale in 22edo: -```c +```ocaml C4 = 262 Hz D4 @@ -535,7 +538,7 @@ We can change this using a *lift declaration* `/ = (newLiftAmount)`. The syntax Declaring a lift to be worth 6 degrees of 311edo we arrive at this version of our major scale: -```c +```ocaml defer 311@ / = 6° C4 = 262 Hz @@ -570,14 +573,14 @@ We already saw `mtof(60)` and `sort()` above. There are plenty more, but here's Some helpers need to be called like `sort()` while other's like `simplify` are implicitly mapped over the current scale. Let's demonstrate with our major that has been code-golfed to obscurity: -```c +```ocaml 1=262z 24:27:30:32:36:40:45:48 ``` The intervals will format as `27/24`, `30/24`, etc. but those are just `9/8` and `5/4` with a complicated denominator. Tagging a `simplify` at the end reduces the fractions: -```c +```ocaml 1 = 262 Hz 24:27:30:32:36:40:45:48 simplify @@ -587,7 +590,7 @@ The result will format as `9/8`, `5/4`, `4/3`, etc. . With `FJS` tagged at the end -```c +```ocaml 1 = 262 Hz 24:27:30:32:36:40:45:48 FJS @@ -597,7 +600,7 @@ we obtain `M2`, `M3^5`, `P4`, etc. . To go from (absolute) FJS to fractions we can use `relin`: -```c +```ocaml A4 = 440 Hz B4 @@ -612,7 +615,7 @@ relin ``` The result is the same as if we had entered: -```c +```ocaml 1/1 = 440 Hz 9/8 @@ -626,7 +629,7 @@ The result is the same as if we had entered: The `reduce` helper allows us to be imprecise with octaves. The above spelled sloppily is: -```c +```ocaml defer reduce 1 = 440 Hz diff --git a/documentation/interchange.md b/documentation/interchange.md index 404df5a8..e743ec13 100644 --- a/documentation/interchange.md +++ b/documentation/interchange.md @@ -2,15 +2,18 @@ This documentation describes the .swi interchange format for transferring microtonal scales between programs ## Comments -Comments work as in JavaScript. Everything after `//` is ignored until the end of the line. Everything after `/*` is ignored until `*/` is encountered. (This includes other `/*` i.e. no nested grammar for comments.) -```c -// single line comment -/* +Comments work as in OCaml. Everything between a pair of `(*` and `*)` is ignored. Comments can be nested +```ocaml +(* single comment *) + +(* Comment spanning multiple lines. -*/ +(* A nested comment. *) +Still part of the comment. +*) ``` ## Empty lines @@ -30,7 +33,7 @@ Valid CSS colors include: ## Scale title The first string in the file indicates the title of the scale. -```c +```ocaml "The scale title" ``` @@ -38,8 +41,8 @@ A scale title is mandatory. Untitled scales must provide an empty string. ## Unison frequency The reference frequency is given with the syntax `1 = expr` where expressions is a valid absolute interval (described below). -```c -// Set reference frequency to 256 Hz +```ocaml +(* Set reference frequency to 256 Hz *) 1 = [1 8>@Hz.2 ``` @@ -55,7 +58,7 @@ Relative interval are to be interpreted against the reference frequency if it is Intervals with prime factors below 29 are given as ket-vectors a.k.a. monzos starting with `[` and ending with `>`. Below the labels indicate the meaning of each monzo: -```c +```ocaml [> "1/1" niente [-4 4 -1> "81/80" niente [7/12> "7 sof 12" niente @@ -65,21 +68,21 @@ Below the labels indicate the meaning of each monzo: #### Higher primes Prime factors above 23 require an explicit subgroup basis given after `@` separated by periods `.`. Subgroup basis elements must be integers. No check is made to ensure that basis elements are actually prime numbers. -```c +```ocaml [-1 1>@29.31 "31/29" niente [-9 1>@2.899 "899/512" rgb(90% 80% 70% / 70%) ``` #### Non-primes The special symbols `-1` and `0` indicate negative numbers and zero respectively. Their vector component must be `1` if present. -```c +```ocaml [1>@0 "0" niente [1 1>@-1.2 "-2" niente ``` #### Real values Scalars that cannot be expressed as fractional monzos are given as *real* cents `rc`. The corresponding vector component is a floating-point literal. The special basis element `inf` indicates floating-point infinity. A negative unity component of `inf` indicates real zero. -```c +```ocaml [-1>@inf "0r" niente [0.>@rc "1r" niente [1 1200.>@-1.rc "-2r" niente @@ -89,7 +92,7 @@ Scalars that cannot be expressed as fractional monzos are given as *real* cents ### Absolute pitches The special basis element `Hz` indicates frequencies. -```c +```ocaml [1 3 1 1>@Hz.2.5.11 "440 Hz" niente [-1 2 -2>@Hz.2.5 "10ms" niente ``` @@ -98,27 +101,27 @@ A tool such as Scale Workshop normalizes durations (periods of oscillation) to f #### Real absolute pitches The Hz exponent of real frequencies is a floating-point literal. -```c +```ocaml [1. 1981.7953553667824>@Hz.rc "PI * 1Hz" niente ``` ### Edosteps To support tempering after interchanging data, the special `1°` basis element is used. -```c +```ocaml [5>@1° "/P1" niente ``` ### Not-a-number The special combination `[1 1>@0.inf` indicates a value that can't be interpreted as an interval. -```c +```ocaml [1 1>@0.inf "asin(2)" niente ``` ## Example See [examples/interchange.sw](https://github.com/xenharmonic-devs/sonic-weave/blob/main/examples/interchange.sw) for various extreme values supported by the original SonicWeave runtime. -```c -// Created using SonicWeave 0.1.0 +```ocaml +(* Created using SonicWeave 0.4.0 *) "Various values to test the .swi interchange format" diff --git a/documentation/intermediate-dsl.md b/documentation/intermediate-dsl.md index e6f64852..5b72a50e 100644 --- a/documentation/intermediate-dsl.md +++ b/documentation/intermediate-dsl.md @@ -10,7 +10,7 @@ SonicWeave is intended for designing musical scales so a fundamental concept is The current scale starts empty (`$ = []`) and the basic action is to push intervals onto the scale. Statements can be separated with semicolons `;` or newlines. After these instructions ... -```c +```ocaml 5/4 3/2 2/1 @@ -19,7 +19,7 @@ Statements can be separated with semicolons `;` or newlines. After these instruc ### Unrolling Sub-scales are automatically unrolled onto the current scale. -```c +```ocaml 4\12 [7\12, 12\12] ``` @@ -27,7 +27,7 @@ Results in the scale `$ = [4\12, 7\12, 12\12]`. (Unrolling of a sub-scale essent ### Coloring If an expression evaluates to a color it is applied to all the intervals in the scale that don't have a color yet. -```c +```ocaml 5/4 3/2 green @@ -37,7 +37,7 @@ red Results in a scale equivalent to `$ = [5/4 #008000, 3/2 #008000, 2/1 #FF0000]`. #### Inline colors -```c +```ocaml 5/4 3/2 green 2/1 red @@ -48,7 +48,7 @@ It is up to a user interface to interprete colors. The original intent is to col ### Scale title If an expression evaluates to a string it is used as the scale title. -```c +```ocaml "My fourth and octave" 4/3 2/1 @@ -58,7 +58,7 @@ Results in the scale `$ = [4/3, 2/1]`. The title is included in the `.scl` export. #### Inline labels -```c +```ocaml 4/3 "My perfect fourth" 2/1 'octave' ``` @@ -73,7 +73,7 @@ Scales are intended to repeat from the last interval in the scale (a.k.a. *equav ### Function calls Functions have access to the current scale and may modify it. E.g. a call to `sort()` puts everything in ascending order. -```c +```ocaml 3/2 2/1 7/6 @@ -86,7 +86,7 @@ Some functions like `simplify` operate on individual intervals instead of full s Such functions can be mapped over every interval in the current scale replacing the contents. -```c +```ocaml 10/8 12/8 16/8 @@ -104,7 +104,7 @@ In addition to musical intervals SonicWeave features something known as *vals* w Upon encountering a *val* like `12@` the current scale is converted with no effect on subsequent intervals. -```c +```ocaml 4/3 3/2 @@ -120,7 +120,7 @@ To learn more see [tempering.md](https://github.com/xenharmonic-devs/sonic-weave ### Record unrolling When a record is encountered its values are sorted by size and the keys are used for labels. -```c +```ocaml 4/3 { fif: 3/2, @@ -136,21 +136,21 @@ Results in `$ = [4/3, 5/4 "my third", 3/2 "fif", 2 "octave"]`. Notice how 4/3 wa ## Variables Variables in SonicWeave can hold any type of value. They must be declared before use. Variables declared `const` cannot be re-assigned while `let` variables may change what value they refer to. -```c +```ocaml const myComma = 81/80 -myComma = 250/243 // WRONG! Throws an error. +myComma = 250/243 (** WRONG! Throws an error. **) ``` -```c +```ocaml let myComma = 81/80 -myComma = 250/243 // Valid: myComma now has the value 250/243 +myComma = 250/243 (* Valid: myComma now has the value 250/243 *) ``` **Constancy is shallow** (skin-deep): the elements of a `const` array may be re-assigned at will. -```c +```ocaml const myCommas = [81/80, 128/125]; -myCommas[1] = 250/243 // Valid: myCommas now contains [81/80, 250/243] +myCommas[1] = 250/243 (* Valid: myCommas now contains [81/80, 250/243] *) ``` Un-initialized `let` variables default to `niente`. @@ -158,7 +158,7 @@ Un-initialized `let` variables default to `niente`. ### Destructuring Variables may be declared from an array. -```c +```ocaml const [x, y] = [1, 2] y x @@ -166,7 +166,7 @@ x Results in `$ = [2, 1]`. Variables may be re-assigned from an array. -```c +```ocaml let x, y [x, y] = [1, 2] y @@ -177,18 +177,22 @@ Results in `$ = [2, 1]`. ### Rest parameter Rest declaration: -```c +```ocaml const [x, ...r] = [1, 2, 3, 4] -// x has value 1 -// r has value [2, 3, 4] +(** + * x has value 1 + * r has value [2, 3, 4] + *) ``` Rest assignment: -```c +```ocaml let x, r [x, ...r] = [1, 2, 3, 4] -// x has value 1 -// r has value [2, 3, 4] +(** + * x has value 1 + * r has value [2, 3, 4] + *) ``` ### Reassignment operator @@ -197,7 +201,7 @@ Variables can be reassigned after declaration e.g. with `let i = 2` the statemen ## Statements / line endings Statements in SonicWeave end in a semicolon. Newlines use automatic semicolon insertion where applicable. -```c +```ocaml 6/5 3/2 2 @@ -237,7 +241,7 @@ Division of logarithmic quantities is a true mind-bender: `m7` is `2 * P4` so co There are two *echelons* in SonicWeave: *absolute* and *relative*. Relative intervals are also called scalars and absolute intervals non-scalars. Frequencies are the most common non-scalars. They're required for declaring the reference frequency and we can use them as is: -```c +```ocaml 1 = 256 Hz 320 Hz 384 Hz @@ -246,24 +250,24 @@ Frequencies are the most common non-scalars. They're required for declaring the Re-declaring the reference is not recommended as it involves an implicit relative-to-absolute conversion. -```c +```ocaml 1 = 256 Hz -// Every scalar is henceforth interpreted as multiples of 256 hertz. -5/4 // 320 Hz -3/2 // 384 Hz -2 // 512 Hz +(* Every scalar is henceforth interpreted as multiples of 256 hertz. *) +5/4 (* 320 Hz *) +3/2 (* 384 Hz *) +2 (* 512 Hz *) -// Upon unison frequency re-declaration the existing content is converted to frequencies. +(* Upon unison frequency re-declaration the existing content is converted to frequencies. *) 1 = 440 Hz -// From now on scalars are multiples of 440 hertz instead. -16/11 // 640 Hz -9/5 // 792 Hz -2 // 880 Hz +(* From now on scalars are multiples of 440 hertz instead. *) +16/11 (* 640 Hz *) +9/5 (* 792 Hz *) +2 (* 880 Hz *) ``` Durations like seconds or milliseconds are also supported. They're interpreted as periods of oscillation i.e. inverse frequencies. -```c +```ocaml "Upwards sounding minor chord /6:5:4:3" 1 = 6 ms 5 ms @@ -272,33 +276,33 @@ Durations like seconds or milliseconds are also supported. They're interpreted a ``` Beware that the unison reference is always a frequency even if declared as a duration. -```c -1 = 1 ms // 1 = 1000 Hz -3/2 // 1500 Hz -2 // 2000 Hz +```ocaml +1 = 1 ms (* 1 = 1000 Hz *) +3/2 (* 1500 Hz *) +2 (* 2000 Hz *) ``` ### Operations on absolute intervals Addition of frequencies and scalar multiplication works as you'd expect: -```c +```ocaml 1 = 100 Hz -100 Hz + 50 Hz // 150 Hz -2 * 100 Hz // 200 Hz +100 Hz + 50 Hz (* 150 Hz *) +2 * 100 Hz (* 200 Hz *) ``` Division of frequencies produces scalars: -```c +```ocaml 1 = 100 Hz -4000 Hz / 2000 Hz // Same as plain 2 +4000 Hz / 2000 Hz (* Same as plain 2 *) ``` The produced scalars are in turn interpreted against the reference. In the end `4000 Hz / 2000 Hz` results in 200 Hz above. Squaring a frequency does seemingly nothing: -```c +```ocaml 1 = 200 Hz -(300 Hz)^2 // Sounds just like 300 Hz +(300 Hz)^2 (* Sounds just like 300 Hz *) 400 Hz ``` @@ -306,28 +310,28 @@ This is because absolute intervals are by their nature *projective* i.e. they're This causes an absolute pitch reference to behave in two distinct ways: -```c +```ocaml "Relative reference for absolute FJS" C4 = 1 = 200 Hz -E4^5 + Eb4_5 // Same as 5/4 * 6/5 i.e. 3/2 -C5 // Same as 2/1 +E4^5 + Eb4_5 (* Same as 5/4 * 6/5 i.e. 3/2 *) +C5 (* Same as 2/1 *) ``` ...or with an absolute reference: -```c +```ocaml "Absolute reference for absolute FJS" C4 = 200 Hz = 1 -E4^5 + Eb4_5 // Same as 250 Hz * 240 Hz -C5 // Same as 400 Hz +E4^5 + Eb4_5 (* Same as 250 Hz * 240 Hz *) +C5 (* Same as 400 Hz *) ``` That 250 Hz * 240 Hz is normalized to `sqrt(60000) Hz` i.e. a neutral third above 200 Hz. Addition means averaging with projective quantities. Scalar multiplication merely biases the weights. -```c +```ocaml "Absolute reference for absolute FJS" C4 = 200 Hz = 1 -2 * E4^5 + Eb4_5 // Same as (250 Hz)^2 * 240 Hz -C5 // Same as 400 Hz +2 * E4^5 + Eb4_5 (* Same as (250 Hz)^2 * 240 Hz *) +C5 (* Same as 400 Hz *) ``` The normalized frequency is now `cbrt(15000000) Hz` ≈ 246.62 Hz i.e. something between a neutral and a major third above 200 Hz. @@ -362,7 +366,7 @@ It is possible to separate numbers into groups using underscores for readability In addition to the value, domain and echelon there's also formatting information attached to intervals. A decimal like `12e-1` is not automatically simplified to `6/5` and neither is `6/4` fractionally reduced to `3/2` (remember that plain `reduce()` refers to octave-reduction in SonicWeave). Formatting tries to be smart to make relationships between intervals easier to see. A harmonic segment like `6::12` formats as -```c +```ocaml 7/6 8/6 9/6 @@ -371,7 +375,7 @@ Formatting tries to be smart to make relationships between intervals easier to s 12/6 ``` instead of -```c +```ocaml 7/6 4/3 3/2 @@ -382,7 +386,7 @@ instead of to make it clear that the fractions all came from a shared segment. Same goes for equal temperaments `tet(6)` formats as -```c +```ocaml 1\6 2\6 3\6 @@ -391,7 +395,7 @@ Same goes for equal temperaments `tet(6)` formats as 6\6 ``` instead of -```c +```ocaml 1\6 1\3 1\2 @@ -445,11 +449,11 @@ The vectorized boolean NOT is called `vnot` so `vnot [0, 1, 2]` evaluates to `[t Vectorized increment/decrement creates a new copy of the affected array. -```c +```ocaml let i = [1, 2]; const j = i; -++i; // Changes i to [2, 3] and pushes 2 and 3 onto the scale -j // Still [1, 2] +++i; (* Changes i to [2, 3] and pushes 2 and 3 onto the scale *) +j (* Still [1, 2] *) ``` ### Binary operators @@ -461,9 +465,9 @@ The expression `foo() lest bar()` executes `foo()` and returns the result if it In `foo() lest bar() lest baz()` execution proceeds from left to right until an operand evaluates successfully. If all fail, the exception from `baz()` is thrown. It's the inline version of `try..catch`. -```javascript -fraction(P5) lest P5 // Successfully evaluates to 3/2 -fraction(PI) lest PI // Falls back to 3.141592653589793r +```ocaml +fraction(P5) lest P5 (* Successfully evaluates to 3/2 *) +fraction(PI) lest PI (* Falls back to 3.141592653589793r *) ``` #### Coalescing @@ -477,7 +481,7 @@ Logical operators check for *truthiness*. The falsy values are `false`, `niente` Coalescing operators short-circuit. Execution stops once the value of the expression is known. -```c +```ocaml 1 and print('This executes') 0 and print("This won't execute") @@ -511,7 +515,7 @@ Key inclusion is similar to JavaScript's `in` operator e.g. `"foo" in {foo: 1, b Index inclusion allows for negative indices `-1 in [0]` evaluates to `false` while `-1 ~in [0]` evaluates to `true`. Outer product a.k.a. tensoring expands all possible products in two arrays into an array of arrays e.g. `[2, 3, 5] tns [7, 11]` evaluates to -```c +```ocaml [ [14, 22], [21, 33], @@ -520,7 +524,7 @@ Outer product a.k.a. tensoring expands all possible products in two arrays into ``` Beware that the product is domain-aware! Most of the time you want all possible stacks of intervals regardless of the domain. Use `~tns` to achieve this e.g. `[9/8, m2, M3] ~tns [P5, 8/7]` evaluates to -```c +```ocaml [ [27/16, 9/7], [m6, m3_7], @@ -602,7 +606,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: -```c +```ocaml M2 P4 P5 @@ -671,7 +675,7 @@ All binary operators vectorize over arrays starting from vectorized logical oper E.g. `2 * [3, 4, 5]` broadcasts to `[2, 2, 2] * [3, 4, 5]` evaluating to `[2*3, 2*4, 2*5]` or `[6, 8, 10]`. Broadcasting descends one level at a time so the meaning is reversed from that of NumPy where shapes are matched from tail to head instead. E.g. -```c +```ocaml [ [1, 2], [3, 4], @@ -679,7 +683,7 @@ Broadcasting descends one level at a time so the meaning is reversed from that o ] + [10, 100, 1000] ``` proceeds to -```c +```ocaml [ [1, 2] + 10, [3, 4] + 100, @@ -687,7 +691,7 @@ proceeds to ] ``` resulting in -```c +```ocaml [ [11, 12], [103, 104], @@ -701,11 +705,11 @@ However this allows us to use non-uniform shapes. `[[1, 2], [3, [4, 5]]] + [10, Domain-aware operators can be instructed to ignore domain using tildes. An expression like `x ~op y` is value-equivalent to -```c +```ocaml domainOf(x)(linear(x) op linear(y)) ``` while `x op~ y` prefers y -```c +```ocaml domainOf(y)(linear(x) op linear(y)) ``` @@ -747,13 +751,13 @@ In effect `/a:b:c` is shorthand for `1/a:1/b:1/c`. The stdlib `u()` helper offer #### Mixed enumerations Plain enumerals and harmonic segments may be freely mixed e.g. `8::10:12:14::16` is the same as `8:9:10:12:14:15:16` i.e. -```c +```ocaml 9/8 -10/8 // 5/4 -12/8 // 3/2 -14/8 // 7/4 +10/8 (* 5/4 *) +12/8 (* 3/2 *) +14/8 (* 7/4 *) 15/8 -16/8 // 2 +16/8 (* 2 *) ``` ### Array access @@ -767,7 +771,7 @@ To obtain a subset of an array use an array of indices e.g. `[1, 2, 3, 4][[0, 2] #### Using an array of booleans Another way to obtain a subset is to use an array of booleans. This works especially well with vectorized operators like `>` here: -```c +```ocaml const smallPrimes = [2, 3, 5, 7, 11] smallPrimes[smallPrimes > 4] ``` @@ -822,8 +826,8 @@ In addition to the various intervals between the unison and the octave there are You may have noticed that the gap between an augmented third and a diminished third is wider than between an augmented fourth and a diminished fourth: `cents(a3 - d3, 3)` is `341.055` while `cents(a4 - d4, 3)` is mere `227.370`. The central interval between the fourths `(dim4 + Aug4) / 2` is just `P4` but the central third lands between minor and major: -```c -(dim3 + Aug3) / 2 // n3, the neutral third +```ocaml +(dim3 + Aug3) / 2 (* n3, the neutral third *) ``` Another way to think about the neutral third is as the split fifth `n3` = `P5 / 2`. (The ordinal notation obscures the fact that the *stepspan* of `P5` is four which evenly divides into two.) @@ -879,14 +883,14 @@ Some of these can be handy for using neutral intervals as the center of just maj ## Up/lift declaration Usually you would declare ups and lifts in terms of edosteps, but nothing is preventing you from co-opting the system for notating just intonation and skipping the tempering step altogether. -```c -^ = 81/80 // Ups are syntonic now +```ocaml +^ = 81/80 (* Ups are syntonic now *) C4 = 263z = 1/1 -vE4 // 5/4 -G4 // 3/2 -^Bb4 // 9/5 -C5 // 2/1 +vE4 (* 5/4 *) +G4 (* 3/2 *) +^Bb4 (* 9/5 *) +C5 (* 2/1 *) ``` ## MOS declaration @@ -895,32 +899,32 @@ C5 // 2/1 While the intervals of such scales are readily obtained using the `mos(countL, countS)` or `rank2(generator)` helpers there's a complete system for generating relative and absolute notation for these scales. MOS can be declared in many ways. -```c +```ocaml "Brightest mode (Ryonian) of basic octave-equivalent Archeotonic" MOS 6L 1s ``` -```c +```ocaml "Specific mode (Nightmare) of basic octave-equivalent Ekic" MOS LLsLLLsL ``` -```c +```ocaml "Specific mode (Anti-phrygian) of specific hardness of octave-equivalent Antidiatonic" MOS 4333433 ``` -```c +```ocaml "Specific mode (Salmon) of specific hardness of octave-equivalent Pine" MOS 43, 43, 10, 43, 43, 43, 43, 43 ``` -```c +```ocaml "Brightest mode of the tritave-equivalent Lambda scale" MOS 4L 5s <3> ``` -```c +```ocaml "Gil mode of octave-equivalent Mosh with large step equal to 9/8" MOS { 3L 4s 5|1 @@ -951,18 +955,18 @@ The absolute pitch `J4` always corresponds to `C4` and nominals K, L, M, N, etc. Diamond-mos nominals are second-class syntax so you'll need to dodge previous syntax: `M_3` instead of `M3` (major third), `P_4` instead of `P4` (perfect fourth) and `S_9` instead of `S9` (square superparticular). The recommendation is to always use a natural sign (`_` or `♮`) with Diamond-mos pitches. -```c +```ocaml "Brightest mode (Ryonian) of basic octave-equivalent Archeotonic" MOS 6L 1s J_4 = 263 Hz -K_4 // 2\13 -L_4 // 4\13 -M_4 // 6\13 -N_4 // 8\13 -O_4 // 10\13 -P_4 // 12\13 -J_5 // 13\13 +K_4 (* 2\13 *) +L_4 (* 4\13 *) +M_4 (* 6\13 *) +N_4 (* 8\13 *) +O_4 (* 10\13 *) +P_4 (* 12\13 *) +J_5 (* 13\13 *) ``` The accidental `&` (read "am") raises pitch by `L - s` while its opposite `@` (read "at") correspondingly lowers pitch by the same amount. @@ -971,7 +975,7 @@ The accidental `e` (read "semiam") raises pitch by a half *am* while `a` (read " #### Auto-MOS The easiest way to generate Diamond-mos notation for one equave is to call the `automos()` helper. -```c +```ocaml "Specific mode (Anti-phrygian) of specific hardness of octave-equivalent Antidiatonic" MOS 4333433 J4 = 263 Hz @@ -985,35 +989,35 @@ You can also [name relative intervals](https://en.xen.wiki/w/TAMNAMS#Naming_mos_ The indexing starts from 0 instead of 1. -```c +```ocaml "Specific mode (Nightmare) of basic octave-equivalent Ekic" MOS LLsLLLsL -P0ms = 333 Hz // Same as 1 = 333 Hz -P1ms // 2\14 -M2ms // 4\14 -P3ms // 5\14 -P4ms // 7\14 -P5ms // 9\14 -M6ms // 11\14 -P7ms // 12\14 -P8ms // 14\14 +P0ms = 333 Hz (* Same as 1 = 333 Hz *) +P1ms (* 2\14 *) +M2ms (* 4\14 *) +P3ms (* 5\14 *) +P4ms (* 7\14 *) +P5ms (* 9\14 *) +M6ms (* 11\14 *) +P7ms (* 12\14 *) +P8ms (* 14\14 *) ``` Neutral (e.g. `n2ms`) and mid intervals (e.g. `n1ms`) are also available. (Semi-)augmented intervals have similar logic to their diatonic counterparts: Augmentation is counted from major upwards and diminishment from minor downwards unless the central interval has perfect quality. #### Exception for nL ns When there's only one other interval per period the bright (wide) variant is designated major while the dark (narrow) variant is designated minor. -```c +```ocaml "A scale spelled using relative Triwood intervals" MOS 3L 3s P0ms = 333 Hz -M1ms // 2\9 -P2ms // 3\9 -m3ms // 4\9 -P4ms // 6\9 -P6ms // 9\9 +M1ms (* 2\9 *) +P2ms (* 3\9 *) +m3ms (* 4\9 *) +P4ms (* 6\9 *) +P6ms (* 9\9 *) ``` ## Next steps diff --git a/documentation/technical.md b/documentation/technical.md index b944c381..1264e665 100644 --- a/documentation/technical.md +++ b/documentation/technical.md @@ -71,27 +71,27 @@ Parenthesis, `^`, `×`, `÷`, `+`, `-` follow [PEMDAS](https://en.wikipedia.org/ ### For..of The contents of arrays and records can be iterated over using -```c +```ocaml for (const element of container) { - /* body of the loop utilizing element */ + (* body of the loop utilizing element *) } ``` ### For..in The indices of arrays or keys of records can be iterated over using -```c +```ocaml for (const element in container) { - /* body of the loop utilizing element */ + (* body of the loop utilizing element *) } ``` ### While The body of a while loop is executed until the condition becomes falsy. -```c +```ocaml let i = 10; while (--i) i; -// Result is numbers from 9 to 1 pushed onto the implicit array $. +(* Result is numbers from 9 to 1 pushed onto the implicit array $. *) ``` ### Break, continue diff --git a/documentation/tempering.md b/documentation/tempering.md index 13b16ae5..4690b1a0 100644 --- a/documentation/tempering.md +++ b/documentation/tempering.md @@ -23,7 +23,7 @@ The linear unary inverse operator has no logarithmic analogue so `%logarithmic(2 By default a geometric inverse is only associated with the monzo it's the inverse of. To change the association use the `withEquave(...)` built-in function. To obtain the associated equave (as a linear quantity) use the `equaveOf(...)` function. By default co-vectors are associated with the octave so the basis of the co-logarithmic domain consists of `%logarithmic(2)`, `withEquave(%logarithmic(3), 2)`, `withEquave(%logarithmic(5), 2)`, etc. so a val literal such as `<12 19 28]` means -```c +```ocaml 12 * %logarithmic(2) + 19 * withEquave(%logarithmic(3), 2) + 28 * withEquave(%logarithmic(5), 2) ``` if spelled out in full. @@ -35,7 +35,7 @@ The dot product between a val and a monzo is straighforward enough: `<12 19 28] The association with an equave is important in tempering to know which equal temperament we're targetting. The `tmpr` operator infers the number of divisions from `val dot equaveOf(val)`. It's also more graceful with a higher prime tail and leaves it alone. The operation `v tmpr m` is equivalent to: -```c +```ocaml ((v dot relative(m)) \ (v dot equaveOf(v)) ed equaveOf(v)) ~* tail(relative(m), complexityOf(v, true)) ``` E.g. `<12 19 28] tmpr 7/5` evaluates to `[-28/12 0 0 1>`. @@ -45,7 +45,7 @@ In practice the higher prime tail is usually irrelevant and the vals have enough ### Implicit tempering Implicit tempering refers to what SonicWeave does to the scale when it encounters a val. -```c +```ocaml 5/4 3/2 2/1 @@ -65,15 +65,15 @@ While a simple `i => i by~ step` doesn't work consistently we can still think of Let's say we have this major chord as our scale `$ = [5/4, 3/2, 2]` and we wish to convert it to 12-tone equal temperament. First we'll measure out the primes: -```javascript +```ocaml 2^-2 * 3^0 * 5^1 2^-1 * 3^1 * 5^0 2^+1 * 3^0 * 5^0 ``` Then we replace each prime with their closest approximation: -```javascript -const st = 2^1/12 // One semitone +```ocaml +const st = 2^1/12 (* One semitone *) (2 by st)^-2 * (3 by st)^0 * (5 by st)^1 (2 by st)^-1 * (3 by st)^1 * (5 by st)^0 @@ -82,6 +82,6 @@ const st = 2^1/12 // One semitone Which results in `$ = [2^4/12, 2^7/12, 2^12/12]`. The the 5-limit val `12@2.3.5` = `<12 19 28]` can be obtained using this method as well: -```c +```ocaml valFromPrimeArray(([2, 3, 5] by st) /_ st) ``` diff --git a/examples/barbados9.sw b/examples/barbados9.sw index 2f33d83b..415145b2 100644 --- a/examples/barbados9.sw +++ b/examples/barbados9.sw @@ -1,12 +1,14 @@ "Enneatonic 5L 4s subset of 313edo used in Sevish's track Desert Island Rain" -// Sometimes labeled as Madagasgar[9] to emphasize the full 13-limit. -// 313edo is not optimal for Barbados[9], but the scale is generated by ~15/13 nonetheless, so it's not a misnomer. +(** + * Sometimes labeled as Madagasgar[9] to emphasize the full 13-limit. + * 313edo is not optimal for Barbados[9], but the scale is generated by ~15/13 nonetheless, so it's not a misnomer. + *) -// Temper to 313 equal tones +(* Temper to 313 equal tones *) defer 313@ -// Symmetric mode on middle C +(* Symmetric mode on middle C *) C♮4 = mtof(60) D♮4 "Octave-reduced doubled 5th" αd4 "Split 4th" diff --git a/examples/blackdye.sw b/examples/blackdye.sw index 94edc697..cdf90467 100644 --- a/examples/blackdye.sw +++ b/examples/blackdye.sw @@ -1,6 +1,6 @@ "Blackdye Lydian (LSLSLMLSLM)" -// https://en.xen.wiki/w/Blackdye +(* https://en.xen.wiki/w/Blackdye *) rank2(3/2, 4) mergeOffset(10/9) diff --git a/examples/bohlen-pierce-just.sw b/examples/bohlen-pierce-just.sw index 87d38f42..63738b91 100644 --- a/examples/bohlen-pierce-just.sw +++ b/examples/bohlen-pierce-just.sw @@ -1,16 +1,16 @@ "Bohlen-Pierce just" -// Basic housekeeping +(* Basic housekeeping *) defer organize() -// Span a 3x2 lattice on 5 (two up) and 7 (one down) +(* Span a 3x2 lattice on 5 (two up) and 7 (one down) *) [1, 5, 25] tns [1, 1/7] -// Swap out 1/1 for something more interesting +(* Swap out 1/1 for something more interesting *) shift() * 25/49 -// Add a mirrored copy +(* Add a mirrored copy *) %$ -// Add the equave +(* Add the equave *) 3 diff --git a/examples/centaura.sw b/examples/centaura.sw index 4e936a44..cef7d0e7 100644 --- a/examples/centaura.sw +++ b/examples/centaura.sw @@ -1,10 +1,10 @@ "Kraig Grady Centaura Harmonic" -// Stack some fifths +(* Stack some fifths *) 6:8:9:12 -// Merge copies of the stack +(* Merge copies of the stack *) mergeOffset([5, 7/3, 11], "wrap") -// Replace 11/6 with 9/8 +(* Replace 11/6 with 9/8 *) $[-3] = 9/8 diff --git a/examples/dtmf.sw b/examples/dtmf.sw index f3c6fd29..16958be2 100644 --- a/examples/dtmf.sw +++ b/examples/dtmf.sw @@ -1,17 +1,17 @@ "Dual-tone multi-frequency signaling (DTMF)" -// https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling +(* https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling *) -// Vertical coordinates +(* Vertical coordinates *) 1/1 = 697 Hz 770 Hz 852 Hz 941 Hz -// Horizontal coordinates +(* Horizontal coordinates *) 1209 Hz 1336 Hz 1477 Hz 1633 Hz -// Pure double-octaves for repeats and label for unison +(* Pure double-octaves for repeats and label for unison *) 4/1 "697 Hz" diff --git a/examples/dynamic-smitonic.sw b/examples/dynamic-smitonic.sw index 5d443327..ea0d7ccd 100644 --- a/examples/dynamic-smitonic.sw +++ b/examples/dynamic-smitonic.sw @@ -1,9 +1,9 @@ -// Generate Nerevarine mode of Smitonic (LLsLsLs) +(* Generate Nerevarine mode of Smitonic (LLsLsLs) *) rank2(sqrt(16/11), 6); -// The labels JKLMNOP are from the Sothic mode (LsLsLsL) +(* The labels JKLMNOP are from the Sothic mode (LsLsLsL) *) let code = charCodeAt('J') - 1; -// Remember that the unison is implicit, -// so labeling starts from the second scale degree. +(* Remember that the unison is implicit, *) +(* so labeling starts from the second scale degree. *) i => i fromCharCode(++code); diff --git a/examples/hexany.sw b/examples/hexany.sw index a61de0b0..c1696d16 100644 --- a/examples/hexany.sw +++ b/examples/hexany.sw @@ -1,38 +1,38 @@ -// This is a manual re-construction of cps([1, 3, 5, 7], 2) +(* This is a manual re-construction of cps([1, 3, 5, 7], 2) *) "Canonical 1-3-5-7 Hexany" const factors = [1, 3, 5, 7] -// These two 'for' loops could be replaced with map(prod, kCombinations(factors, 2)) +(* These two 'for' loops could be replaced with map(prod, kCombinations(factors, 2)) *) -// for..in iterates over array indices +(* for..in iterates over array indices *) for (const i in factors) { - // for..of iterates over array values + (* for..of iterates over array values *) for (const j of [i+1 .. length(factors)-1]) { - // Push a combination product onto this scale + (* Push a combination product onto this scale *) factors[i] * factors[j] } - // Unload the combination onto this scale + (* Unload the combination onto this scale *) } -// Unload everything onto the root scale +(* Unload everything onto the root scale *) -// Now we have a scale with combinations {3, 5, 7, 15, 21, 35} (in some order) +(* Now we have a scale with combinations {3, 5, 7, 15, 21, 35} (in some order) *) -// Make 3 the root +(* Make 3 the root *) combo => combo % 3 -// Reduce each by the octave +(* Reduce each by the octave *) combo => combo rd 2 -// Sort in ascending order +(* Sort in ascending order *) sort() -// Shift out the 3/3 because the unison should be implicit -// Voiding is required to avoid pushing the first element on top instead +(* Shift out the 3/3 because the unison should be implicit *) +(* Voiding is required to avoid pushing the first element on top instead *) void(shift()) -// Add the octave to finish off +(* Add the octave to finish off *) 2 -// Had we used 'rdc' instead of 'rd' above this shifting and octave pushing wouldn't've been necessary +(* Had we used 'rdc' instead of 'rd' above this shifting and octave pushing wouldn't've been necessary *) diff --git a/examples/interchange.sw b/examples/interchange.sw index 740922cb..3c7f7749 100644 --- a/examples/interchange.sw +++ b/examples/interchange.sw @@ -1,14 +1,14 @@ "Various values to test the .swi interchange format" -// Need more components for that sqrt(29/16) +(* Need more components for that sqrt(29/16) *) numComponents(20) -// MOS declaration and this comment should be ignored in .swi output +(* MOS declaration and this comment should be ignored in .swi output *) MOS 5L 2s -/* +(* Constants should also be ignored. -*/ +*) const fif = P4ms let foo = 123.4rc diff --git a/examples/keen12.sw b/examples/keen12.sw index 01f626b1..4aa021ba 100644 --- a/examples/keen12.sw +++ b/examples/keen12.sw @@ -1,21 +1,21 @@ "Keen[12] with semioctave note labels tuned to 56edo by interpolating between 12d and 22p." -// https://en.xen.wiki/w/Diaschismic_family#Keen +(* https://en.xen.wiki/w/Diaschismic_family#Keen *) -// Set root pitch for note labels. +(* Set root pitch for note labels. *) C4 = mtof(60) -// Span one period of the rank-2 scale using an array comprehension. -// 2 /^ 2 is shorthand for sqrt(2). +(* Span one period of the rank-2 scale using an array comprehension. *) +(* 2 /^ 2 is shorthand for sqrt(2). *) [3^i rdc 2/^2 for i of [-2..3]] sort() -// Stack to full octaves. +(* Stack to full octaves. *) repeat() -// Use semioctave notation and pick the nominals for labels. +(* Use semioctave notation and pick the nominals for labels. *) labelAbsoluteFJS -// See https://en.xen.wiki/w/Val#Sparse_Offset_Val_notation for the alternative notation for 12d. -// Doesn't actually matter here because we're stacking fifths, but communicates the intended interpretation. -// Same as 56@. +(* See https://en.xen.wiki/w/Val#Sparse_Offset_Val_notation for the alternative notation for 12d. *) +(* Doesn't actually matter here because we're stacking fifths, but communicates the intended interpretation. *) +(* Same as 56@. *) tune(12[v7]@, 22@, 2) diff --git a/examples/lemba10-vars.sw b/examples/lemba10-vars.sw index 4c015803..fff98156 100644 --- a/examples/lemba10-vars.sw +++ b/examples/lemba10-vars.sw @@ -1,16 +1,16 @@ "Lemba[10] notated using variables" -// Generate justly intoned nominals -// Prefer stacking 8/7, but stack one down to make M = 7/4 +(* Generate justly intoned nominals *) +(* Prefer stacking 8/7, but stack one down to make M = 7/4 *) const nominals = rank2(8/7, 3, 1) -// Destructure nominals into individual variables +(* Destructure nominals into individual variables *) const [J, K, L, M, N] = nominals -// The period is a semioctave notated using ^K +(* The period is a semioctave notated using ^K *) ^ = sqrt(2) / K -// Work around limitations of the grammar +(* Work around limitations of the grammar *) const [vJ, vK, vL, vM, vN] = v{nominals} vJ @@ -24,5 +24,5 @@ M vN N -// Temper to 26-tone equal temperament (with minimal loss in accuracy) +(* Temper to 26-tone equal temperament (with minimal loss in accuracy) *) 26@ diff --git a/examples/neutral-dorian.sw b/examples/neutral-dorian.sw index 8688ce04..d254ded9 100644 --- a/examples/neutral-dorian.sw +++ b/examples/neutral-dorian.sw @@ -8,5 +8,5 @@ n6 n7 P8 -// Extract labels from FJS literals. +(* Extract labels from FJS literals. *) map(str, $) diff --git a/examples/notation/12edo-srutal.sw b/examples/notation/12edo-srutal.sw index c27b2801..0637beae 100644 --- a/examples/notation/12edo-srutal.sw +++ b/examples/notation/12edo-srutal.sw @@ -10,7 +10,7 @@ G4 δ4 A4 ε4 -ζ4 // Not B4 to get equal number of Latin and Greek nominals +ζ4 (* Not B4 to get equal number of Latin and Greek nominals *) C5 12@ diff --git a/examples/notation/25edo-antidiatonic.sw b/examples/notation/25edo-antidiatonic.sw index 0b8a363c..5910d74e 100644 --- a/examples/notation/25edo-antidiatonic.sw +++ b/examples/notation/25edo-antidiatonic.sw @@ -7,8 +7,8 @@ D_4 vE4 E_4 ^E4 -^^E4 // Or Eb4 if you don't mind the direction -vvF4 // Or F#4 if you don't mind the direction +^^E4 (* Or Eb4 if you don't mind the direction *) +vvF4 (* Or F#4 if you don't mind the direction *) vF4 F_4 ^F4 @@ -21,8 +21,8 @@ A_4 vB4 B_4 ^B4 -^^B4 // Or Bb4 -vvC5 // Or Cb5 +^^B4 (* Or Bb4 *) +vvC5 (* Or Cb5 *) vC5 C_5 diff --git a/examples/notation/9edo.sw b/examples/notation/9edo.sw index 0460f4d5..0bda446a 100644 --- a/examples/notation/9edo.sw +++ b/examples/notation/9edo.sw @@ -2,12 +2,12 @@ C_4 = mtof(60) D_4 E_4 -vF4 // Or F#4 if you don't mind the direction +vF4 (* Or F#4 if you don't mind the direction *) F_4 G_4 A_4 B_4 -^B4 // Or Bb4 if you don't mind the direction +^B4 (* Or Bb4 if you don't mind the direction *) C_5 9@ diff --git a/examples/pajara10.sw b/examples/pajara10.sw index cd8a4f98..96164721 100644 --- a/examples/pajara10.sw +++ b/examples/pajara10.sw @@ -1,24 +1,24 @@ "Decanominal 2L 8s Pajara scale in 22edo" -// Temper to 22 equal tones +(* Temper to 22 equal tones *) defer 22@ -// First period +(* First period *) C4 = 1/1 η4 D4 α4 E4 -// Second period +(* Second period *) γ4 G4 δ4 A4 ε4 -// Repeat at the octave +(* Repeat at the octave *) C5 -// Label using nominals by stripping the octaves and accidentals +(* Label using nominals by stripping the octaves and accidentals *) map(note => str(note)[0]) diff --git a/examples/porcusmine7.sw b/examples/porcusmine7.sw index cfb7d940..e55d8e8b 100644 --- a/examples/porcusmine7.sw +++ b/examples/porcusmine7.sw @@ -1,6 +1,6 @@ "Porcusmine[7]" -// https://en.xen.wiki/w/Alternating_generator_sequence +(* https://en.xen.wiki/w/Alternating_generator_sequence *) -// The fourth non-trivial scale in the Porcusmine generator sequence family +(* The fourth non-trivial scale in the Porcusmine generator sequence family *) csgs([9/5, 50/27], 4) diff --git a/examples/pote-orgone.sw b/examples/pote-orgone.sw index b52dd633..35a02f07 100644 --- a/examples/pote-orgone.sw +++ b/examples/pote-orgone.sw @@ -1,13 +1,13 @@ "Orgone rank-2 scale (4L 3s, POTE optimized)" -// https://en.xen.wiki/w/Orgonia#Orgone -// POTE generator: ~77/64 = 323.372 +(* https://en.xen.wiki/w/Orgonia#Orgone *) +(* POTE generator: ~77/64 = 323.372 *) const generator = 323.372 const up = 3 const down = 3 rank2(generator, up, down); -// https://en.xen.wiki/w/4L_3s#Note_names -// Diamond-MOS labels based on the symmetric mode +(* https://en.xen.wiki/w/4L_3s#Note_names *) +(* Diamond-MOS labels based on the symmetric mode *) ["K", "L", "M", "N", "O", "P", "J"] diff --git a/examples/pythagoras12.sw b/examples/pythagoras12.sw index 6d6d56ea..ab309b56 100644 --- a/examples/pythagoras12.sw +++ b/examples/pythagoras12.sw @@ -1,40 +1,40 @@ "Pythagorean just intonation, 12-notes: A♭ through C♯ centered on C" -// Pitch declaration is required to use absolute pitch notation +(* Pitch declaration is required to use absolute pitch notation *) C4 = 1/1 -// One sharp is always equal to 7 steps along the chain of fifths +(* One sharp is always equal to 7 steps along the chain of fifths *) C#4 black -// By default absolute pitch notation is untempered so D is equal to 9/8 against C +(* By default absolute pitch notation is untempered so D is equal to 9/8 against C *) D4 white -// Octaves come after accidentals. CSS colors are supported +(* Octaves come after accidentals. CSS colors are supported *) Eb4 black -// You can also use long hexadecimal colors +(* You can also use long hexadecimal colors *) E4 #FFFFFF -// Short hexadecimal works too +(* Short hexadecimal works too *) F4 #fff -// You can use unicode ♯ if you like +(* You can use unicode ♯ if you like *) F♯4 #000 -// Relative notation also exists. Against C this is a G +(* Relative notation also exists. Against C this is a G *) P5 white -// This is Ab4 +(* This is Ab4 *) m6 black -// This is 'A4' or 'A♮4' ('a4' is interpreted as an augmented fourth instead) +(* This is 'A4' or 'A♮4' ('a4' is interpreted as an augmented fourth instead) *) M6 white -// You can also use unicode ♭ +(* You can also use unicode ♭ *) B♭4 black -// Unicode natural sign works too +(* Unicode natural sign works too *) B♮4 white -// The equal sign is the ASCII equivalent of ♮ +(* The low dash is the ASCII equivalent of ♮ *) C_5 white diff --git a/src/cli.ts b/src/cli.ts index 5494d2c8..7297d118 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -63,7 +63,7 @@ export function toSonicWeaveInterchange(source: string) { if (!context) { throw new Error('Missing root context.'); } - const lines = [`// Created using SonicWeave ${version}`, '']; + const lines = [`(* Created using SonicWeave ${version} *)`, '']; lines.push(JSON.stringify(context.title)); lines.push(''); if (context.unisonFrequency) { diff --git a/src/grammars/base.pegjs b/src/grammars/base.pegjs index 1f6c7b29..3340eb2f 100644 --- a/src/grammars/base.pegjs +++ b/src/grammars/base.pegjs @@ -142,12 +142,16 @@ _ 'whitespace' = (WhiteSpace / LineTerminatorSequence / Comment)* __ 'inline whitespace' - = (WhiteSpace / MultiLineCommentNoLineTerminator)* + = (WhiteSpace / CommentNoLineTerminator)* + +NonEmptyInlineWhiteSpace = (WhiteSpace / CommentNoLineTerminator) { + return ' '; +} // Automatic Semicolon Insertion EOS = _ ';' - / __ SingleLineComment? LineTerminatorSequence - / __ &"}" + / __ LineTerminatorSequence + / __ &'}' / _ EOF EOF @@ -199,19 +203,10 @@ ID_Continue = c:SourceCharacter &{ return ID_CONTINUE_RE.test(c); } Comment - = MultiLineComment - / SingleLineComment - -MultiLineComment = '/*' $(!'*/' SourceCharacter)* '*/' - -MultiLineCommentNoLineTerminator - = "/*" (!("*/" / LineTerminator) SourceCharacter)* "*/" - -SingleLineComment - = '//' $SingleLineCommentChar* + = '(*' ((!('(*' / '*)') SourceCharacter) / Comment)* '*)' -SingleLineCommentChar - = !LineTerminator SourceCharacter +CommentNoLineTerminator + = '(*' ((!('(*' / '*)' / LineTerminator) SourceCharacter) / CommentNoLineTerminator)* '*)' // Chord parser needs to know what monzo components look like VectorComponent diff --git a/src/grammars/sonic-weave.pegjs b/src/grammars/sonic-weave.pegjs index 62524ed8..51527d36 100644 --- a/src/grammars/sonic-weave.pegjs +++ b/src/grammars/sonic-weave.pegjs @@ -642,7 +642,7 @@ ImportAllStatement } EmptyStatement - = (_ ';' / __ SingleLineComment LineTerminatorSequence) { + = (_ ';' / __ LineTerminatorSequence) { return { type: 'EmptyStatement', }; @@ -880,7 +880,7 @@ UnaryExpression // Val literals must be excluded to keep comparisons working ImplicitCallExpression - = head: CallExpression tail: (@' ' __ !'<' @CallExpression)* { + = head: CallExpression tail: (@NonEmptyInlineWhiteSpace __ !'<' @CallExpression)* { return tail.reduce(operatorReducerLite, head); } @@ -889,7 +889,7 @@ Label = TrueCallExpression / TrueAccessExpression / Identifier / TemplateArgument / ColorLiteral / StringLiteral / NoneLiteral LabeledCommaDecimal - = __ object: CommaDecimal labels: (@' ' __ @Label)* { + = __ object: CommaDecimal labels: (@NonEmptyInlineWhiteSpace __ @Label)* { return labels.reduce(operatorReducerLite, object); } diff --git a/src/parser/__tests__/expression.spec.ts b/src/parser/__tests__/expression.spec.ts index 44cf0556..60285e92 100644 --- a/src/parser/__tests__/expression.spec.ts +++ b/src/parser/__tests__/expression.spec.ts @@ -2416,4 +2416,24 @@ describe('Poor grammar / Fun with "<"', () => { expect(interval.valueOf()).toBeNaN(); expect(interval.value).toBeInstanceOf(TimeReal); }); + + it('can stack ups', () => { + const {interval} = parseSingle('^^^P1 tmpr 12@'); + expect(interval.totalCents()).toBe(300); + }); + + it('can stack downs', () => { + const {interval} = parseSingle('vvvP1 tmpr 12@'); + expect(interval.totalCents()).toBe(-300); + }); + + it('can stack lifts', () => { + const {interval} = parseSingle('///P1 tmpr 12@'); + expect(interval.totalCents()).toBe(1500); + }); + + it('can stack drops', () => { + const {interval} = parseSingle(String.raw`\\\P1 tmpr 12@`); + expect(interval.totalCents()).toBe(-1500); + }); }); diff --git a/src/parser/__tests__/modules.spec.ts b/src/parser/__tests__/modules.spec.ts index 77f21d13..09e76cb3 100644 --- a/src/parser/__tests__/modules.spec.ts +++ b/src/parser/__tests__/modules.spec.ts @@ -29,7 +29,7 @@ describe('SonicWeave module system', () => { { import * from foo; - bar; // 1 + bar; (* 1 *) module corge { 2/1 "block-scope modules make little sense, but banning them is more work." @@ -38,12 +38,12 @@ describe('SonicWeave module system', () => { import baz as grault from corge; - grault(10); // 11 + grault(10); (* 11 *) } import bar, baz as quux from foo; - quux(100); // 101 - bar + 1000; // 1001 + quux(100); (* 101 *) + bar + 1000; (* 1001 *) `); expect(scale).toEqual(['1', '11', '101', '1001']); }); diff --git a/src/parser/__tests__/paren-counter.spec.ts b/src/parser/__tests__/paren-counter.spec.ts index 46ffadfd..68ec43d2 100644 --- a/src/parser/__tests__/paren-counter.spec.ts +++ b/src/parser/__tests__/paren-counter.spec.ts @@ -32,15 +32,16 @@ describe('Parenthesis counter', () => { it('ignores curly braces inside comments #1', () => { const counts = parse(` for (const i of $) { - /* Stealthy curly brace } */ - // More stealthy braces {{{ - // Oh no it's an unpaired square bracket [ ! + (* Stealthy curly brace } *) + (* More stealthy braces {{{ + (* Oh no it's an unpaired square bracket [ ! *) + *) `); expect(counts).toEqual({parens: 0, squares: 0, curlies: 1}); }); it('ignores curly braces inside comments #2', () => { - const counts = parse('if (0) {\n//}\n'); + const counts = parse('if (0) {\n(*//}*)\n'); expect(counts).toEqual({parens: 0, squares: 0, curlies: 1}); }); diff --git a/src/parser/__tests__/sonic-weave-ast.spec.ts b/src/parser/__tests__/sonic-weave-ast.spec.ts index 1da07e09..8bc2c5f5 100644 --- a/src/parser/__tests__/sonic-weave-ast.spec.ts +++ b/src/parser/__tests__/sonic-weave-ast.spec.ts @@ -972,7 +972,7 @@ describe('SonicWeave Abstract Syntax Tree parser', () => { }); it('parses comments after dot product', () => { - const ast = parseSingle('3 dot 12@ // Are the comments fixed now?'); + const ast = parseSingle('3 dot 12@ (* Are the comments fixed now? *)'); expect(ast).toEqual({ type: 'ExpressionStatement', expression: { @@ -1239,6 +1239,44 @@ describe('Automatic semicolon insertion', () => { }); }); +describe('Nested comments', () => { + it('works inline like whitespace', () => { + const ast = parseSingle('foo(* inline comment *)bar'); + expect(ast).toEqual({ + type: 'ExpressionStatement', + expression: { + type: 'BinaryExpression', + operator: ' ', + left: {type: 'Identifier', id: 'foo'}, + right: {type: 'Identifier', id: 'bar'}, + preferLeft: false, + preferRight: false, + }, + }); + }); + + it('spans multiple lines', () => { + const ast = parse(`(* + multi- + line + comment + *)`); + expect(ast.body).toHaveLength(0); + }); + + it('can be nested', () => { + const ast = parse(` + (* inline (*nesting*) *) + (* + * Multiline + * (*nesting*) + * test + *) + `); + expect(ast.body).toHaveLength(0); + }); +}); + describe('Negative tests', () => { it('rejects numbers without digits', () => { expect(() => parse('._')).toThrow(); @@ -1306,4 +1344,8 @@ describe('Negative tests', () => { it('rejects empty arrow function parameters', () => { expect(() => parse('=> 0')).toThrow(); }); + + it('rejects unclosed comments', () => { + expect(() => parse('(* comment )')).toThrow(); + }); }); diff --git a/src/parser/__tests__/source.spec.ts b/src/parser/__tests__/source.spec.ts index 8e55a4e9..41ced8b7 100644 --- a/src/parser/__tests__/source.spec.ts +++ b/src/parser/__tests__/source.spec.ts @@ -55,7 +55,7 @@ describe('SonicWeave parser', () => { }); it('can declare variables', () => { - const ast = parseAST('const i = 676/675 /* The Island comma */;'); + const ast = parseAST('const i = 676/675 (* The Island comma *);'); const visitor = new StatementVisitor(); visitor.rootContext = new RootContext(); visitor.visit(ast.body[0]); @@ -64,11 +64,11 @@ describe('SonicWeave parser', () => { it('can invert a scale', () => { const scale = parseSource(` - 2;3;4;5;6;7;8; // Build scale - const equave = pop(); // Pop from the scale - i => equave %~ i; // Functions map over the scale implicitly - reverse(); // Reverse the current scale - equave; // The default action is to push onto the current scale + 2;3;4;5;6;7;8; (* Build scale *) + const equave = pop(); (* Pop from the scale *) + i => equave %~ i; (* Functions map over the scale implicitly *) + reverse(); (* Reverse the current scale *) + equave; (* The default action is to push onto the current scale *) `); expect(scale.map(i => i.toString()).join(';')).toBe( '8/7;4/3;8/5;2;8/3;4;8' @@ -225,27 +225,28 @@ describe('SonicWeave parser', () => { it('can spell diaschismic antisymmetrically', () => { const scale = parseSource(` - // Scale Workshop 3 will add this line automatically. - // Declare base nominal and frequency. + (* Scale Workshop 3 will add this line automatically. + (* Declare base nominal and frequency. *) + *) C0 = 1/1 = 261.6 Hz; - // First cycle (Greek - Latin - Greek...) - eta0; // Or η0 if you want to get fancy. + (* First cycle: Greek - Latin - Greek... *) + eta0; (* Or η0 if you want to get fancy. *) D0; alp0; E0; - bet0; // Or F0 depending on taste. - gam0; // Period + bet0; (* Or F0 depending on taste. *) + gam0; (* Period *) - // Second cycle (Latin - Greek - Latin ...) + (* Second cycle: Latin - Greek - Latin ... *) G0; del0; A0; eps0; - B0; // Or zet0 depending on taste. - C1; // Equave = 2 * period + B0; (* Or zet0 depending on taste. *) + C1; (* Equave = 2 * period *) - // Temperament + (* Temperament *) 12@; `); expect(scale).toHaveLength(12); @@ -284,16 +285,16 @@ describe('SonicWeave parser', () => { it('can construct well-temperaments (manual)', () => { const scale = parseSource(` - // Bach / Louie 2018 + (* Bach / Louie 2018 *) const g = logarithmic(3/2); const p = logarithmic(531441/524288); const equave = logarithmic(2); - // Down + (* Down *) -g; $[-1] - g; $[-1] - g; $[-1] - g; - // Up + (* Up *) g - p % 6; $[-1] + g - p % 6; $[-1] + g - p % 6; @@ -301,10 +302,10 @@ describe('SonicWeave parser', () => { $[-1] + g - p % 6; $[-1] + g - p % 18; $[-1] + g - p % 18; - // Reduce + (* Reduce *) i => i mod equave; sort(); - // Equave + (* Equave *) equave; cents; `); @@ -322,7 +323,7 @@ describe('SonicWeave parser', () => { ^m6 P8 - // Break fragiles + (* Break fragiles *) ^ = 1° FJS @@ -1276,19 +1277,19 @@ describe('SonicWeave parser', () => { it('freezes existing content on unison frequency re-declaration', () => { const scale = parseSource(` 1 = 256 Hz - // Every scalar is henceforth interpreted as multiples of 256 hertz. - 5/4 // 320 Hz - 3/2 // 384 Hz - 2 // 512 Hz + (* Every scalar is henceforth interpreted as multiples of 256 hertz. *) + 5/4 (* 320 Hz *) + 3/2 (* 384 Hz *) + 2 (* 512 Hz *) - // Upon re-declaration of the unison frequency the existing content is converted to frequencies. + (* Upon re-declaration of the unison frequency the existing content is converted to frequencies. *) 1 = 440 Hz - // From now on scalars are multiples of 440 hertz instead. - 16/11 // 640 Hz - 9/5 // 792 Hz - 2 // 880 Hz + (* From now on scalars are multiples of 440 hertz instead. *) + 16/11 (* 640 Hz *) + 9/5 (* 792 Hz *) + 2 (* 880 Hz *) - // Manual conversion + (* Manual conversion *) absolute `); const freqs = scale.map(i => i.valueOf()); @@ -1318,7 +1319,7 @@ describe('SonicWeave parser', () => { } 5:8:7:9:6:10; - popSort; // Pushes [sorted($), [], [], ...] onto the scale + popSort; (* Pushes [sorted($), [], [], ...] onto the scale *) }`); expect(scale).toEqual(['6/5', '7/5', '8/5', '9/5', '10/5']); }); @@ -1477,10 +1478,10 @@ describe('SonicWeave parser', () => { J3 = 100 Hz = 1 K3 L3 - M_3 // Looks like major third - N3 // Let's not do capital neutrals, OK? + M_3 (* Looks like major third *) + N3 (* Let's not do capital neutrals, OK? *) O3 - P3 // Perfect thirds don't exist + P3 (* Perfect thirds don't exist *) Q3 R3 J4 diff --git a/src/stdlib/prelude.ts b/src/stdlib/prelude.ts index d167384b..68e3cdea 100644 --- a/src/stdlib/prelude.ts +++ b/src/stdlib/prelude.ts @@ -1,5 +1,5 @@ export const PRELUDE_VOLATILES = ` -// XXX: This is only here to bypass scope optimization so that Scale Workshop can hook warn(). +(* 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) { @@ -14,11 +14,13 @@ riff reduce(scale = $$) { `; export const PRELUDE_SOURCE = ` -// == Root context dependents == +(** Root context dependents **) -// Note that this could be golfed to: -// const ablin = i => i linear absolute, -// but it would lead to weird behavior if i is a function. +(** + * Note that this could be golfed to: + * const ablin = i => i linear absolute, + * but it would lead to weird behavior if i is a function. + *) riff ablin(interval) { "Convert interval to absolute linear representation."; return absolute(linear interval); @@ -59,11 +61,11 @@ riff absoluteHEJI(interval) { return absoluteFJS(interval, 'h'); } -// == Constants == +(** Constants **) const edostep = 1°; const edosteps = edostep; -// == Functions == +(** Functions **) riff vbool(value) { "Convert value to a boolean. Vectorizes over arrays."; return vnot vnot value; @@ -454,7 +456,7 @@ riff edColors(divisions = 12, offset = 0, equave = 2) { return edColor; } -// == Scale generation == +(** Scale generation **) riff tet(divisions, equave = 2) { "Generate an equal temperament with the given number of divisions of the given equave/octave."; if (equave == 2) @@ -551,7 +553,7 @@ riff parallelotope(basis, ups = niente, downs = niente, equave = 2, basisSizeHin const up = pop(ups); const down = pop(downs); - // Don't impose color on unity. + (* Don't impose color on unity. *) const axis = generator ~^ [-down..up]; axis[down] = bleach(axis[down]); @@ -751,7 +753,7 @@ riff realizeWord(word, sizes, equave = niente) { stack(); } -// == Scale modification == +(** Scale modification **) riff equaveReduce(scale = $$) { "Reduce the current/given scale by its equave."; remap(i => i ~rdc scale[-1], scale);