Bazza Nava Devlog #1 - Music Sheet


This week I've been able to focus on the sheet system, that allows the game to generate harmonies that progress with the song. Let's dissect it step by step.

The Metronome

In order to trigger events as the song progress, we need to track its beats and measure. This is something I've done several time for rhythm based games, in Unity and JS. The principle is the same in Godot! I took inspiration from the Godot Mixing Desk plugin. The basis is: knowing a song BPM (beats per minute), we calculate how long a beat is in seconds (60 / BPM). Given this length, we know when the next beat should occur (at the start of the song, it's simply beat_length). Once the song is started, at each frame, we check the position we are in the song. If it's equal or above the next_beat value, then we trigger the beat! Then we increments next_beat with beat_length (it is important to always increment and never try to calculate a new value, as some approximations could create delay).

The only thing the Metronome does when a beat is trigger is to emit a signal beat. Signals are a very powerful feature in Godot, that you should definitely use as most as possible! It is an implementation of the Observer pattern. It allows a node to be completely independent, and just trigger events by sending signals (possibly with some data). Those signals can be connected in parents node to callbacks. This way, one signal can be re-used for several purpose, without complicating the child node or adding dependencies! For my Metronome, I can easily use its beat with any element of the game, making it easy to have elements moving to the beat, or implementing a rhythm mechanic.

Note that this Metronome also sends a signal for new measure. Since we know how many beat there is in a measure (usually 4), it just sends that signal every time that number of beat is reached. It also send the position of each beat in the current measure (0, 1, 2, 3). This way we know exactly where we are in the song!

The Sheet

To describe how the song is composed, I wanted to use a JSON file. Easy to edit, and to use in code. Albeit it's a bit tricky to type properly (I wanted to create another plugin to import it, but with a custom Resource it's not easily doable), and reading it force me to export me with the executables, it's still a functional system.

Here is how a sheet looks. I used the song from Void Garden as a test:

{
  "scale": {
    "note": "A",
    "mode": "LYDIAN"
  },
  "measures": [
    {
      "measure": 2,
      "chord": 0
    },
    {
      "measure": 3,
      "chord": 1
    },
    {
      "measure": 4,
      "chord": 0
    },
    {
      "measure": 5,
      "chord": 1
    },
    {
      "measure": 6,
      "chord": 0
    },
    {
      "measure": 7,
      "chord": 1
    },
    {
      "measure": 8,
      "chord": 0
    },
    {
      "measure": 9,
      "chord": 1
    },
    {
      "measure": 10,
      "chord": 5
    },
    {
      "measure": 11,
      "chord": 6
    },
    {
      "measure": 12,
      "chord": 5
    },
    {
      "measure": 13,
      "chord": 6
    },
    {
      "measure": 14,
      "chord": 5
    },
    {
      "measure": 15,
      "chord": 6
    },
    {
      "measure": 16,
      "chord": 5
    },
    {
      "measure": 17,
      "chord": 6
    }
  ]
}

The data generated from this file is pretty much the same as what it describes. We first read the scale of the song to initialize our players. Then we take the first block in measures, and we set it as our next_measure. Once the song is playing and we reach measure number 2, we change player's harmony to what this measure describe, and we change next_measure to the one after! Then repeat.

How a measure is read is simple.

  • measureas you may have guessed, indicates the number of the measure
  • chord is a number indicating the base chord in the current scale (between 0 and 7). For a C Major scale, 0 is C Major, 1 is D Minor, 2 is E Minor, etc…
  • scale can also be set in a measure to change the global scale of the song! This allows to have interesting change, like switching from C Major to C Minor.

I'm not fully satisfied with this format yet. The main issue is measure. As you can see, I have to set it differently for every single measure, which is pretty laborious! When a section repeat itself, I want to be able to copy-paste it! So instead of measure, I plan to set length, which will be the number of measure to wait before the next one in the array. Also, I plan to add an offset option, that indicates a number of beats, in order to make a change in the middle of a measure.

The Conductor

Finally we have to dispatch instructions to instruments according to the sheet and the metronome! I like to call that algorithm "conductor", as it follow the sheet and the metronome to send order to music players.

At every new measure, the Conductor look at the next_measure in the sheet (if it is reached). Then, it calls set_harmony to every players with the information it gathered. For that, I use another great feature of Godot: groups! Groups allow to simply make a call to every node in the tree that belongs to that group. As easy to implement than it is to use!

But how do I use the information I have in the sheet to change player's notes? Well, remember what we said last week: for every musical scale, we can calculate any other scale by starting from another note! This is were using numbers for chords become interesting! If we start from C Major, 2 gives E Phrygian, 5 is A Minor, etc… We are still playing in the C Major mode, but using another scale inside!

Thus, we call set_harmony with a new note (root + chord, using the same algorithm used by the sampler to calculate the nth note in the scale), and a new mode as well (base_mode + chord, again). This way we change the notes probabilities, making the notes from the current chord more likely to happen!


However, the result melodies can still be improved. First, the probabilities I use currently are not satisfying. The base of each chord are not played enough. In Top 365, it was nice to have varied notes, as the song didn't have a strict harmony. But here I need to emphasis them more! Plus, there is the 7th. I said last week that we didn't want to hear it too often. Well, that might be true for one mode, but not for another! In jazz, the 7th can give a strong consistence to a harmony! I should see if I add parameters to increase its probabilities, as well as the 9th or other "interesting" notes.

But I also read an article about randomness in Tetris recently, and maybe probabilities are not the only way to go? Maybe I could use something like a pool, to make sure that the notes stay varied while some are still repeated enough.


But the next week will be a bit special. This week-end, I will participate in the Godot Wild Jam #13! I'm quite excited by it. But it means I won't progress on Bazza Nava, and it's likely I won't find much time during the week either (as I might want to improve the jam entry). So maybe the next update will be in two weeks. The objective will be to improve the sheet and probabilities system, and hopefully I will be able to show you a little demo!

Get Bazza Nava

Leave a comment

Log in with itch.io to leave a comment.