slurmulon / bach Goto Github PK
View Code? Open in Web Editor NEW:musical_score: Semantic music notation
Home Page: http://codebach.tech
License: MIT License
:musical_score: Semantic music notation
Home Page: http://codebach.tech
License: MIT License
Right now the unit of a beat is scaled to the defined meter/time signature and a single beat unit applies to an entire bar/measure . For example:
@Time = 6|8
!Play [
1 -> Chord('Cmaj7')
]
will play a Cmaj7
chord for an entire measure, the equivalent of six 1/8th notes.
Likewise, if you had the following:
@Time = 6|8
!Play [
1/2 -> Chord('Cmaj7')
]
It would play Cmaj7
for half a measure, or three 1/8th notes.
This works well for time signatures with an even number of beats in them, but quickly falls apart when dealing with time signatures such as 5|8
, 7|4
, etc.
For instance, if you are in 5|8
time and want to play a chord (or whatever) for the equivalent of three 1/8th notes (or 60% of the measure/bar), it starts to become very obtuse:
@Time = 5|8
!Play [
6/10 -> Chord('Cmaj7')
]
This is also contrary to traditional Western concepts, in which a whole note always equates to four 1/4 notes, regardless of the time signature. It's simply non-intuitive given that most people are trained in and familiar with Western music theory.
The path of least resistance (and of greatest benefit) is to update Bach such that beat durations are scaled to the traditional concept of a whole note, instead of a whole bar/measure.
As I see it, there are potentially three modes we could eventually support.
It may be useful for users to be able to decide which mode they want their beat units in, and this could be done at either a global/track level or at a contextual/time-specific level.
But this is a large undertaking and is reserved as a separate task.
:trad
This should be the default mode since it aligns with traditional Western music theory concepts.
This mode means that a beat unit of 1 would represent a whole note (instead of a whole bar like it is now), or four 1/4 notes, regardless of time signature.
This mode works about just as well in time signatures with both even and odd numbers of beats, but can get a little verbose (albeit clear and direct) in time signatures that don't have a quarter note as the base unit (such as 6|8).
:bar
This is the mode that Bach 0.X was/is built around.
A beat duration of 1 equates to an entire bar/measure scaled to the time signature.
So a beat duration of 1 in 4/4 would mean 4 quarter notes, while a duration of 1 in 6/8 means 6 eighth notes.
This is very clean for measures with an even number of beats, but not so great with measures with an odd number of beats.
:meter
This mode means that a beat's duration maps directly with the beat unit defined in the time signature/meter.
So a beat duration of 1 in 6|8 time would equate to a single 1/8th beat.
The advantage of this mode is that it works across all time signatures (even or odd).
It does, however, get a little strange in even common time. For instance, a duration of 1/2 ends up equating to an 1/8th beat in 4|4:
@Time = 4|4
!Play [
1/2 -> Note('A')
1/2 -> Note('B')
1/2 -> Note('C')
1/2 -> Note('D')
1/2 -> Note('E')
1/2 -> Note('F')
1/2 -> Note('G')
1/2 -> Note('A')
]
Therefore this mode is arguably best suited for rarely encountered time signatures.
This is a breaking change and will involve performing a major release to 1.0
The resulting duration for each beat is not considering the lowest beat in the track, resulting in incorrect values.
This issue is also related to get-ms-per-beat
, as it needs to be updated to consider the lowest-beat
in a similar way (otherwise the duration
values will not align properly).
In this case, the duration
of the :ED
and :EP
beats should be 8
instead of 4
.
Once that is fixed, the ms-per-beat
should be 4285.7144
instead of 2142.8572
.
@Tempo = 56
:ED = Scale('E3 dorian')
:EP = Scale('E3 phrygian')
:DP = Scale('D3 minorpentatonic')
:EbP = Scale('Eb3 minorpentatonic')
:EP = Scale('E3 minorpentatonic')
!Play [
4 -> :ED
4 -> :EP
1/2 -> :DP
1/2 -> :EbP
3 -> :EP
]
{
"data": [
[
{
"duration": 4,
"notes": {
"atom": {
"init": {
"arguments": [
"E3 dorian"
]
},
"keyword": "Scale"
}
}
},
null
],
[
null,
null
],
[
null,
null
],
[
null,
null
],
[
{
"duration": 4,
"notes": {
"atom": {
"init": {
"arguments": [
"E3 phrygian"
]
},
"keyword": "Scale"
}
}
},
null
],
[
null,
null
],
[
null,
null
],
[
null,
null
],
[
{
"duration": 0.5,
"notes": {
"atom": {
"init": {
"arguments": [
"D3 minorpentatonic"
]
},
"keyword": "Scale"
}
}
},
{
"duration": 0.5,
"notes": {
"atom": {
"init": {
"arguments": [
"Eb3 minorpentatonic"
]
},
"keyword": "Scale"
}
}
}
],
[
{
"duration": 3,
"notes": {
"atom": {
"init": {
"arguments": [
"E3 phrygian"
]
},
"keyword": "Scale"
}
}
},
null
],
[
null,
null
],
[
null,
null
]
],
"headers": {
"audio": "",
"desc": "",
"link": "",
"lowest-beat": 0.5,
"ms-per-beat": 2142.8572,
"tags": [],
"tempo": 56,
"time": [
4,
4
],
"title": "Cool Mellow SRV Style",
"total-beats": 12
}
}
The Chord
entity seems to be getting ignored during parsing and/or compilation.
In this case, the resulting data
field is entirely empty.
@Tempo = 68
:G = Chord('G3')
:D = Chord('D3')
:A = Chord('A3m')
!Play [
1 -> :G
1 -> :D
2 -> :A
]
{
"data": [],
"headers": {
"audio": "",
"desc": "",
"link": "",
"lowest-beat": 1,
"ms-per-beat": 2000.0,
"tags": [],
"tempo": 120,
"time": [
4,
4
],
"title": "Untitled",
"total-beats": 0
}
}
Because it has the same number of characters as body
(i.e. it reads nice) and it's consistent with HTML's conventions (i.e. generally more familiar).
Right now bach.track/compile-track
will return a default empty track if there are any syntax errors that occur in bach.ast/parse
.
This might be okay in the long run (that way interpreters don't have to constantly ask "Is this valid bach.json
or is it an error?"), but we should also be providing any errors
that occur in the track.
Right now the only "export" construct we have is !Play
, but it's highly limited as there can only be a single definition.
We should replace !Play
with simply !
, which can then be applied to any variable and can be made available in a separate section of bach.json
(such as refs
).
The new !
keyword could then be paired with ! default <Element>
in order to completely eliminate the need for !Play
.
We simply need to add a new header called @Clef
and then provide adequate documentation for it.
Values should be limited to those defined here: https://en.wikipedia.org/wiki/Clef
Users should be able to nest single quotes inside of double quotes (or vice-versa). Right now this causes a syntax error during parsing
Parse error at line 1, column 16:
@Title = "1960's Memphis Soul Bar Blues Backing Track [Bb - 110 BPM]"
^
Expected one of:
!Play
@
#":[a-zA-Z0-9]+"
{
[
(
#"[0-9]+"
#"#[a-fA-F0-9xX]{6}"
#"['|\"](.*?)[\"|']"
#"(`|~|Note|Scale|Chord|Mode|Triad)"
Right now the conversion to bach
to bach.json
is straight-forward, however the reverse conversion is not.
It's most noticeable in JS land, since only bach.track/compose
is exported right now.
If you wanted changes to bach.json
to be reflected in bach
source code, you would need to use hacky regex patterns to perform the search and replacements in the bach
source code.
If you're using a reactive web framework like Vue or React then you can be more declarative using string templates, but it still involves reflecting/re-implementing what bach.ast
does already. The resulting code code is still dense and brittle, and the developer should not be expected to write it.
bach.ast
, and the instaparse hiccup tree it provides, is already the lowest-level data structure in bach
, and it can naturally be used to support bach.json
-> bach
conversions.
It would involve creating a new method in the bach
core library that takes composed bach.json
and then converts it bach into an AST.
This AST can then be used to re-compose the bach
data.
This involves updating shadow-cljs
and the bach-cljs
package in general to export not only bach.ast
but also any new modules or methods that are introduced with this work.
We could achieve the same resulting by baking all of the string template and regex pattern weirdness into bach
, but that solution is not particularly pleasant or speedy to write. It does, however, have better performance than converting bach.json
into both bach.ast
and bach
, and that's why I mention it here to keep in mind.
For instance, 6/8
will be reduced to 3/4
(undesired).
This stems from Clojure's core logic around ratios. It will always to reduce them to their simplest form.
If you do (numerator 6/8)
the result will be 3
because it reduces 6/8
to 3/4
before returning the function.
To fix this, the use of ratios should be removed entirely (if possible) and replaced with a fixed-sized vector (i.e. 6/8
should instead become [6 8]
).
InstaParse works in ClojureScript - so, why not? ๐
This would prevent people from having to run JVM microservices just to compile bach
into bach.json
(e.g. slurmulon/bach-rest-api), and would instead allow conversion to happen directly in the browser.
The current bundle for bach-cljs
package is quite large (around 400kb), and it also performs poorly, taking around 250ms to compile a simple three part track (yikes).
The same tracks return much quicker via CLI and bach-rest-api
, so this almost certainly has to do with ClojureScript, and, I presume, the Google Closure dependencies.
Further research and isolate the cause of the poor performance in JavaScript.
Should also dig into bach.track/normalize-measures
since this is by far the most complex method, and could be causing GC issues in JS land.
Also research shadow-cljs
more deeply and determine if there are ways to optimize/tree-shake dependencies, most specifically the goog
library.
Right now if you have a pair that only contains a single element:
2 -> Chord('Fm')
When you compile the track into Bach.JSON (via back.track/compile-track
) you will receive the following:
{
"duration": 2,
"notes": {
"atom": {
"init": {
"arguments": [
"Fm"
]
},
"keyword": "Chord"
}
}
}
This makes high-level parsing more complicated because you have to determine whether or not notes
is a collection before you can interpret it.
Instead, we should always receive a collection regardless of how many notes/elements are in a beat pair:
{
"duration": 2,
"notes": [
{
"atom": {
"init": {
"arguments": [
"Fm"
]
},
"keyword": "Chord"
}
}
]
}
warble
means to sing softly with a succession of constantly changing notes, but I recently found out that it also refers to a disgusting boil on the neck of an animal or the maggot of a warble fly... so yeah, let's rename it to bach
๐
We need to allow users to specify Lists and Sets that should be repeated (https://en.wikipedia.org/wiki/Repeat_sign)
Could potentially support the ๐ and ๐ Unicode characters, but that would be pretty different from the syntax we support now.
Right now headers are global and can only be defined once. However, for certain headers, such as @Key
and @Tempo
, it would be useful to be able to change their value as a track progresses (i.e. "contextual" headers).
There are certainly many implications to think through here, but it's a nice concept. It might be best to just say that @Key
and @Tempo
no longer make sense as headers (but that's probably less likely).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.