Giter Club home page Giter Club logo

bach's People

Contributors

slurmulon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

bach's Issues

Change beat units to be based on whole note instead of whole measure

Problem

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.

Solution

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.

Modes

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.

Implications

This is a breaking change and will involve performing a major release to 1.0

Duration calculation is off when the lowest beat is less than 1 measure

Problem

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).

Example

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.

Bach

@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
]

Bach.JSON

{
    "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
    }
}

Chords are being ignored during compilation

Problem

The Chord entity seems to be getting ignored during parsing and/or compilation.

Example

In this case, the resulting data field is entirely empty.

Bach

@Tempo = 68

:G = Chord('G3')
:D = Chord('D3')
:A = Chord('A3m')

!Play [
  1 -> :G
  1 -> :D
  2 -> :A
]

Bach.JSON

{
    "data": [],
    "headers": {
        "audio": "",
        "desc": "",
        "link": "",
        "lowest-beat": 1,
        "ms-per-beat": 2000.0,
        "tags": [],
        "tempo": 120,
        "time": [
            4,
            4
        ],
        "title": "Untitled",
        "total-beats": 0
    }
}

Improve error reporting

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.

Allow variables to be exported (and deprecate !Play)

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.

Nested quotes are broken

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)"

Research pragmatic bach.json to bach conversions in CLJS

Problem

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.

Solution

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.

Alternative

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.

[Bug] Time signature values are being reduced

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]).

Port to ClojureScript

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.

Optimize bach-cljs bundle and performance

Problem

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.

Solution

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.

Ensure that beat notes are always provided as a collection

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"
      }
    }
  ]
}

Rename project to `bach`

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 ๐Ÿ™‚

Consider allowing headers to be overridden at any point in time

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).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.