:musical_score: Semantic music notation
bach
is a semantic music notation designed to be both human and computer friendly.
The project is pre-alpha and is not should not be considered stable for production use.
bach
engines should be easy!bach
tracks are ultimately interpreted by a higher-level bach
engine, such as gig
.
This module, by itself, can only parse and compile plaintext bach
data into bach.json
.
bach.json
is a JSON micro-format that makes it trivial for bach
engines to sequentially process a bach
music track and synchronize it in real-time with audio.
The following bach
track represents the scale progression of a blues song:
@Audio = 'http://api.madhax.io/track/q2IBRPmNq9/audio/mp3'
@Title = 'Jimi Style 12-Bar-Blues Backing Track in A'
@Instrument = 'guitar'
@Time = 4|4
@Tempo = 42
:A = Scale('A3 minorpentatonic')
:D = Scale('D3 minorpentatonic')
:E = Scale('E3 minorpentatonic')
:Track = [
1 -> :A
1 -> :D
2 -> :A
2 -> :D
2 -> :A
1 -> :E
1 -> :D
2 -> :A
]
!Play :Track
and is interpreted like so:
:A
, or A3 minorpentatonic
, will be played for 1
measure, then:D
, or D3 minorpentatonic
, will be played for 1
measure, then:A
will be played for 2
measures, thenTo find a list of every construct supported by bach
(such as Note
, Chord
, etc.), please refer to the "Constructs" section.
[bach "1.0.0"]
compile "bach:bach:1.0.0"
<dependency>
<groupId>bach</groupId>
<artifactId>bach</artifactId>
<version>1.0.0</version>
</dependency>
$ lein install
$ lein test
First be sure that you have a binary executable (requires lein
to be installed) available on your PATH
:
$ lein bin
Then you can execute the resulting binary like so:
$ target/bach -i /path/to/track.bach compile
The executable currently supports the following actions:
parse
: creates an Abstract Syntax Tree (AST) from vanilla bach
datacompile
: parses and compiles vanilla bach
data into bach.json
, an intermediary JSON micro-format that allows for simple interpretation of trackshelp
(ns my.namespace
(:require [bach.ast :refer [parse]]
[bach.track :refer [compile-track]]))
; parses and compiles raw bach data into an interpretable hash-map
(compile-track (parse "!Play [1 -> Chord('A'), 1 -> Chord('C')]"))
An Extended Backus-Naur Form (EBNF) formatted definition of the grammar can be found in grammar.bnf.
Beats
represent either an Element
or a Collection
of Elements
that are played for a duration of time.
Elements
are either Chords
, Scales
, Notes
, Rests
(~
), or Collections
.
The duration that a Beat
is played for is specified using the tuple symbol, ->
:
<duration> -> <element>
Beats
defined in Lists
will be played sequentially in the natural order (left to right) and will not overlap.
[<duration> -> <element>, <duration> -> <element>]
Beats
defined in Sets
will be played in parallel and may overlap.
{<duration> -> <element>, <duration> -> <element>}
Sets
or Lists
defined in other Sets
(i.e. nested Sets
) may be defined without a duration.
{
[
<duration> -> <element>
<duration> -> <element>
],
[
<duration> -> <element>
<duration> -> <element>
]
}
The value of a Beat
's <duration>
can be:
1 = Whole note (or one entire measure in 4|4)
1/2 = Half note
1/4 = Quarter note
1/8 = Eighth note
1/16 = Sixteenth note
...
1/512 = Minimum duration
To adhere with music theory, durations are strictly based on common time (4|4
).
This means that 1
always means 4 quarter notes, and only equates with a full measure when the number of beats in a measure is 4 (as in 4|4
, 3|4
, 5|4
, etc.).
The examples in the remainder of this section assume common time, since this is the default when a @Time
header is not provided.
A List
playing a Note('C2')
for an entire measure, starting at the first Beat
, would be specified like so:
[1 -> Note('C2')]
If you wanted to start playing the note on the second Beat
of the measure, then simply rest (~
) on the first Beat
:
[1/4 -> ~, 1 -> Note('C2')]
When a Beat
tuple is not provided in an an assignment or a Collection
, both the position and duration of the Beat
will be implied at run-time to be the index of each respective element as they are played.
The position and duration are both determined by the time signature (the default is common time, or 4|4
).
For instance:
[1/4 -> Note('C2'), 1/4 -> Note('F2')]
is the same as:
[Note('C2'), Note('F2')]
Beat
durations can also use basic mathematical operators. This makes the translation between sheet music and bach
an easy task.
1 + 1/2 -> Chord'(C2min6')
This is usefeul for specifying more complicated rhythms, like those seen in jazz.
:Mutliple = [
1/2 -> Chord('D2min7')
1+1/2 -> Chord('E2maj7')
1+1/2 -> Chord('C2maj7')
]
You may also use the -
, *
and /
operators.
All Elements
, unless already nested in a List
or Set
, must be instantiated in a Beat
tuple (or implicitly converted into one, as shown in the previous section).
The first parameter of every Element
is a string formatted in scientific pitch notation (SPN)
(surrounded with '
or "
) such as 'C2'
, which is a second octave C
note.
As a convenience, Elements
may also be defined implicitly, specified using a #
:
:Note = #('C2')
:Chord = #('C2Maj7')
:Scale = #('C2 Minor')
Determining the semantic value of implicit Elements
(i.e. whether it's a Note
, Chord
, etc.) is the responsibility of the bach
interpreter.
It's suggested that you primarily use implicits as they will save you a lot of typing over time.
To assign a variable, prefix a unique name with the :
operator and provide a value (<element>
):
:MyLoop = [1 -> Note('C2'), 1 -> Note('E2')]
Once assigned a name, variables may be dynamically referenced anywhere else in the track:
:CoolLooop = :MyLoop
In music it's common to see cadence sections labeled as A
, B
, C
, and so on. bach
's syntax favors this nicely:
:A = Chord('F2maj')
:B = Chord('G2maj')
:C = Chord('C2maj')
:Song = [
1 -> :A
1 -> :B
1 -> :C
1 -> :A
]
!Play :Song
Destructured list assignments will soon be supported and will also favor cadences (currently unsupported):
:[A, B, C, D] = [Chord('E7'), Chord('Emin7'), Chord('Cmaj7'), Chord('Dmaj7')]
Arbitrary attributes may be associated with Elements
using the <key>: <value>
syntax. These attributes allow you to cusotmize the representations and interpretations of your Elements
.
For instance, colors are useful for succesfully expressing a variety of data to the user at once. You might also want to specify the specific voicing of a chord.
:ABC = [
1 -> {
Scale('C2min', color: #6CB359)
Chord('D2min7', color: #AA5585, voicing: 1)
},
1 -> Chord('G2maj7', color: #D48B6A, voicing: 2)
2 -> Chord('C2maj7', color: #FFDCAA, voicing: 2)
]
Optional header information, including the tempo and time signature, is specified with assignments at the top of the file and prefixed with the @
operator:
Headers outside of those defined in the documentation are allowed and can be interpreted freely by the end user, just like X-
headers in HTTP. The value of custom headers can be of any primitive type.
@Title = 'My bach track'
@Time = 4|4
@Tempo = 90
@Tags = ['test', 'lullaby']
@Custom = 'so special'
:ABC = [
1/2 -> Chord('D2min7')
1/2 -> Chord('G2min7')
1 -> Chord('C2maj7')
]
Because bach
supports references, it requires a mechanism for specifying which data should be used for playing the track. You can think of Play
as your main method or default export.
In other words, you need to tell it which values should ultimately be made available to the bach
interpreter.
Any Elements
that aren't being referenced or used by the value exported with !Play
will be ignored during compilation.
:Ignored = [1 -> Chord('D2min6'), 1 -> Chord('A2min9')]
:Utilized = [1 -> Chord('C2Maj7'), 1 -> Chord('A2Maj7')]
!Play :Utilized
Only one !Play
definition is allowed per track file.
Note
= Single note in scientific notationScale
= Scale in scientific notationChord
= Chord in scientific notationMode
= Mode in scientific notationTriad
= Triad of notes in scientific notation~
= Rest#
= Implicit (the interpreter determines if it's scale, chord or note based on the notation itself)[]
= List (sequential / ordered){}
= Set (parallel / unordered)Tempo
(integer, beats per minute)Time
(meter, time signature. ex: 6|8
, 4|4
)Key
(string, key signature)Audio
(url)Instrument
(string, arbitrary)Title
(string, arbitrary)Artist
(string, arbitrary)Desc
(string, arbitrary)Tags
(list or set of strings, arbitrary)Link
(string, url)+
= Add-
= Subtract/
= Divide*
= Multiply|
= Meter (for time signatures, not arbitrary mathematical expressions)'foo'
or "bar"
= string123
or 4.5
= number#000000
= colorbach-json-schema
contains the official JSON Schema definition for the bach.json
formatbach-rest-api
is a RESTful HTTP service that allows compilation of bach
tracks into bach.json
gig
is the official NodeJS bach
interpreter libraryIf you encounter any problems or have any questions then please feel free to open up an issue.
Contributions are always welcome. Simply fork, make your changes and then create a pull request with thorough tests.
@Dynamics
contextual meta construct (alternatives: @Intensity
, @Volume
)@Accent
contextual meta construct!Play
with more generic export
keyword[... 1/4 -> Scale('C2')]
)Note('C2', class: "blue")
)Chord('C2maj7', inversion: 1)
)Beats
in nested List
s (i.e. should nested Beats
/ Elements
be able to surpass their parents?)Can you improve this documentation? These fine people already did:
evavro & Erik VavroEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close