-
Fixed a minor bug in the parser: there was an edge case where a "get variable" event wasn't being disambiguated from its earlier, less-specific "name" form if the "get variable" event happened to be the last thing in the definition of another variable. (#64)
Thanks to elyisgreat for spotting the bug!
-
Fixed a minor bug where parsing an invalid score like
piano: undefinedVariable
would returnnil
instead of throwing the error.The bug was that when a score is syntactically valid but throws an exception while trying to build the score (in the case of this example, because the referenced variable is undefined), the exception is thrown inside a core.async channel and does not affect the main thread -- essentially it gets swallowed, which is a known caveat of exceptions in core.async.
Now, any errors thrown while building the score are passed through the parsing pipeline so that they can be thrown when we're ready to return a result (or throw an exception).
-
Fixed a bug where the parser did not correctly parse nested events in some situations, for example a set-variable expression containing a CRAM expression containing a chord. (#55)
Thanks to elyisgreat for reporting this issue!
-
Fixed a bug in the way the program path is determined when a server starts workers. (That code lives in alda.util, in this repo.) The bug was showing itself when the path to the
alda
(oralda.exe
) executable contained spaces or other special characters.Thanks to Hemaolle for the detective work and PR to fix this issue!
-
Added a
reference-pitch
(alias:tuning-constant
) attribute, which will have an affect on the pitch of each note in Hz. This number is the desired pitch of A4 (the note A in the 4th octave). The default value is 440 Hz.However, please note that this value is not currently used. We are still figuring out how to tune MIDI notes in Java -- it is more difficult that one might expect. If you're interested in helping with this, please let us know!
-
Added a
transposition
(alias:transpose
) attribute, which moves all notes (either per-instrument, or globally, depending on whether you are usingtranspose
ortranspose!
) up or down by a desired number of semitones. Positive numbers represent increasing semitones, and negative numbers represent decreasing semitones.This attribute can be used to make writing parts for transposing instruments more convenient. To see
transpose
in use, see this example score, a transcription of a saxophone quartet by Juan Santiago Jiménez.Saxophones are transposing instruments; soprano and tenor saxophones are considered "Bb" instruments, and alto and baritone saxophones are considered "Eb" instruments. This means that an instrument part written for a baritone saxophone, for example, might appear to be written in C major, but when read and performed by a baritone saxophonist, it will sound like Eb major, the intended key.
Thanks, pzxwang, for implementing these new features!
-
Minor improvement to the new
tempo
function overload andmetric-modulation
function: the supplied note-length can be a string representing multiple note lengths tied together, e.g.:(tempo "4~16" 120)
Thanks to elyisgreat for the issue and pzxwang for the pull request!
-
Added an overload of
tempo
that allows you to specify the tempo in terms of a note value other than (the default) a quarter note.For example, "♩. = 150" can be expressed as:
(tempo! "4." 150)
(NB: the note value can be either a number or a string containing a number followed by dots.)
It is still OK to leave out the note value; the default behavior is to set the tempo relative to a quarter note. "♩ = 60" can still be expressed as:
(tempo! 60)
-
Added a new function,
metric-modulation
, which sets the tempo based on a metric modulation, i.e. shifting from one meter to another.Say, for example, that you're writing a score that starts in 9/8 -- three beats per measure, where each beat is a dotted quarter note.
At a certain point in the piece, you want to transition into a 3/2 section -- still three beats per measure, but now each beat is a half note. You want the "pulse" to stay the same, but now each beat is subdivided into 4 half notes instead of 3. How do you do it?
In traditional notation, it is common to see annotations like "♩. = 𝅗𝅥 " at the moment in the score where the time signature changes. This signifies that at that moment, the pulse stays the same, but the amount of time that used to represent a dotted quarter note now represents a half note. When the orchestra arrives at this point in the score, the conductor continues to conduct at the same "speed," but each musician mentally adjusts his/her perception of how to read his/her part, mentally subdividing each beat into 4 eighth notes instead of 3 eighth notes.
In Alda, you can now express a metric modulation like "♩. = 𝅗𝅥 " as:
(metric-modulation! "4." 2)
Thanks, pzxwang, for the PR to add these new features!
-
Added the following modes:
:ionian
:dorian
:phrygian
:lydian
:mixolydian
:aeolian
:locrian
These can be used as an alternative to
:major
and:minor
when specifying a key signature.For example:
piano: (key-sig [:d :locrian]) d8 e f g a b > c d8~1
Thanks, iggar, for this contribution!
-
Fixed a parser bug where a rest
r
followed by a newline inside of a variable definition would not be considered part of the variable definition.Thanks, elyisgreat, for reporting this issue!
Thanks, pzxwang for contributing the changes in this release in PR #50!
-
Non-integer decimal note lengths are now accepted. For example,
c0.5
(or a double whole note, in Western classical notation) is twice the length ofc1
(a whole note). -
Added a convenient
set-note-length
function to alda.lisp.This is an alternative to
set-duration
, which, somewhat unintuitively, sets the current duration to its argument as a number of beats.To set the note length to a quarter note (1 beat), for example, you can now use either
(set-duration 1)
or(set-note-length 4)
.
-
Fixed error handling in
parse-input
when parsing in:score
mode (which is the default). I overlooked the fact that core-asyncgo-loop
doesn't play nice with error handling, so you have to do something like send the error on the channel and then throw it outside of thego-loop
.Before this fix, if an error occurred, the server would attempt to send the exception object itself as a success response and then fail because the exception is not serializable as JSON.
Now, with
parse-input
properly throwing exceptions, the server will send an error response if one is thrown.
- Fixed issue #41, where
r
followed by e.g.]
would trigger a parser error.
-
Fixed a handful of bugs in the new parser implementation where a one-line variable definition, e.g.:
foo = d8 e f+ g a b4.
...might fail to parse if it ends with certain events.
-
Re-implemented the parser from the ground up in a more efficient way. The new parser implementation uses core.async channels to complete the stages of the parsing pipeline in parallel.
Performance is roughly the same (only slightly better) for scores under ~100 lines, but significantly better for larger scores.
More importantly, parsing asynchronously opens the door for us to make playing a score happen almost immediately in the near future.
See #37 for more details.
-
An added benefit of the new parser implementation is that it fixes issue #12. Line and column numbers are now correct, and error messages are more informative when a score fails to parse.
-
The
alda.parser-util
namespace, which included theparse-to-*-with-context
functions, has been removed. See this commit for more details. -
The Alda parser no longer generates alda.lisp code.
Originally, the Alda parser created a score by generating alda.lisp code and then evaluating it. This actually changed some time ago to a system where the parser generated a sequence of events directly and then used them to build the score. We kept the code that generates alda.lisp code, even though it was no longer an implementation detail of the parser, just an alternate "mode" of parsing.
With these changes to the parser, it would take some additional work to generate alda.lisp code. Since it is no longer necessary to do that, generating alda.lisp code is no longer a feature of Alda. We could re-implement this feature in the future as part of the new parser, if there is a demand for it.
-
Miscellaneous implementation changes that could be relevant if you use Alda as a Clojure library:
-
alda.parser/parse-input
returns a score map, rather than an unevaluated S-expression. Calling this function will require and referalda.lisp
for you if you haven't already done so in the namespace where you're using it. -
alda.lisp/alda-code
does not throw an exception by itself if the code is not valid Alda; instead, the output contains an Exception object, which gets thrown when used inside of a score -
Whereas
alda.lisp/pitch
used to return a function to be applied to the current octave and key signature, now it returns a map that includes its:letter
and:accidentals
. This is more consistent with other alda.lisp functions, and it allows notes to have equality semantics.In other words, whereas
(= (note (pitch :c)) (note (pitch :c)))
used to befalse
, now it istrue
because we aren't comparing anonymous functions. -
(alda.lisp/barline)
now returns{:event-type :barline}
instead ofnil
.
-
-
Fixed #27, a bug where, when using note durations specified in seconds/milliseconds, the subsequent "default" note duration was not being set.
Thanks to damiendevienne for reporting this bug!
- Removed the
voices
(voice group) event, as bbqbaron and I figured out that it's not necessary. It turns out that eachvoice
event manages its voice group implicitly. For more discussion, see alda-lang/alda#286.
- Extracted alda-core from the main Alda repo as of version 1.0.0-rc50.