Open oubiwann opened 2 months ago
Maybe something like this would make sense:
#(note (; arbitrary k/v pairs for data/metadata
#( ... )
#( ... )
...))
We could use this in the undertheory
project along these lines:
#(note (#(name ...) #(value ...) #(dots ...))
#(rest (#(value ...) #(dots ...))
Where value
would be note-head value (e.g., half, 8th, quarter, etc.). One or more utility functions could be used to do things like:
And in the undermidi
project, like so:
#(note (#(pitch ...) #(velocity ...) #(value ...) #(dots ...))
However ... if the k/v pairs will contain different pairs for different libs/use cases, we want to be able to easily match (check for presence/absence) and ordering doesn't matter for this part ... so we probably want to use maps instead of proplists:
#(note #m(...)); map with arbitrary data/metadata
Which gives us this for undertheory
use:
#(note #m(name ... value ... dots ...))
#(rest #m(value ... dots ...))
And this for the undermidi
project:
#(note #m(pitch ... velocity ... value ... dots ...))
For notes, ordering does matter, so a proplist is good for that:
#(notes (#(note #m(...))
#(note #m(...))
...))
And for tuplets, use the note definitions:
#(tuplet #(ratio n m) #(for o p) #(notes (#(note ...) #(rest ...) ...))
The fields value
and dots
would have the same meaning, use, logic, etc., across all projects, so logic for that would live in undertheory
. The name
field would too, but it wouldn't really be useful outside the project. The undermidi
project could take full responsibility for defining fields useful by itself and any other projects consuming its MIDI capabilities, so could handle logic associated withpitch
and velocity
fields.
Tuplets are a bit of a tricky one and there's a need to be highly-specified with them, since you can do some pretty crazy stuff with tuplets in notation and performance, but you have to be extremely precise with them to get it right. However, for many of the common cases, we can support under-specifying which then get expanded to various defaults, e.g.:
#(duplet #(notes ...))
--> #(tuplet 2 #(notes ...))
--> #(tuplet #(ratio 2 8) #(for 3 8) #(notes ...))
#(triplet #(notes ...))
--> #(tuplet 3 #(notes ...))
--> #(tuplet #(ratio 3 8) #(for 2 8) #(notes ...))
#(tuplet 3 16 #(notes ...))
--> #(tuplet #(ratio 3 16) #(for 2 16) #(notes ...))
#(quintuplet #(notes ...))
--> #(tuplet 5 #(notes ...))
--> #(tuplet #(ratio 5 8) #(for 4 8) #(notes ...))
#(sextuplet #(notes ...))
--> #(tuplet 6 #(notes ...))
--> #(tuplet #(ratio 6 16) #(4 16) #(notes ...))
#(septuplet #(notes ...))
--> #(tuplet 7 #(notes ...))
--> #(tuplet #(ratio 7 16) #(4 16) #(notes ...))
#(nonuplet #(notes ...))
--> #(tuplet 9 #(notes ...))
--> #(tuplet #(ratio 9 16) #(8 16) #(notes ...))
One of the nice things about this tuplet notation (and the fact that we're using Lisp) is that it easily supports nested tuplets. We can represent this:
with the following:
'#(quintuplet
#(notes
(#(note #m(pitch 60 velocity 64 value 1/8))
#(tuplet 3 16 #(notes
(#(note #m(pitch 60 velocity 64 value 1/16))
#(note #m(pitch 60 velocity 64 value 1/16))
#(note #m(pitch 60 velocity 64 value 1/16)))))
#(note #m(pitch 60 velocity 64 value 1/4))
#(tuplet 7 32 #(notes
(#(note #m(pitch 60 velocity 64 value 1/32))
#(note #m(pitch 60 velocity 64 value 1/32))
#(note #m(pitch 60 velocity 64 value 1/32))
#(note #m(pitch 60 velocity 64 value 1/32))
#(note #m(pitch 60 velocity 64 value 1/32))
#(note #m(pitch 60 velocity 64 value 1/32))
#(note #m(pitch 60 velocity 64 value 1/32)))))))
Tasks: