# Music Sequencing

## Overview

Before I continue, I want to point out that I wrote this in the new 2.3 update, but this can be changed around to work in previous versions. With that out of the way, we’re going to make a simple music sequencer. This is really primitive, only allowing basic waveforms and every instrument is monophonic.

## Algorithm

1. Define a song’s properties (tempo, subdivisions, length)
2. Define a song’s instruments, with including volume and waveform properties.
3. Define the instuments’ notes.
4. Define a sample rate (I’ll use 32k)
5. Create a buffer to hold the audio data.
6. Generate samples based on instrument data. (This step is way too long to explain here; check the code)
7. Turn that audio buffer into an audio ID to play.

## Code

``````// Song Properties (BPM = 100, 1 "beat" = 8th note, length = 32 beats, sample rate = 32k)
tempo = 100
subdivision = 2
length = 32
sampleRate = 32000

// To hold the audio data
audioBuffer = -1
audioID = -1

// Instrument Definition
instruments = [
// Variable Pulse Wave Bass
{
sample: function(_info) {
// frac(_x) will always be in range [0, 1) for single cycle waveform use
// floor(_x) can be used for changing the waveform
var _x = _info[1] /_info[0];
var _phase = (frac((_x div 10)/10))/4 + 0.375;
return ((_x % 1) > _phase ? -1 : 1)
},

// Attack and decay are in seconds
// The max volume we can have is [-32768, 32767], so choose your volumeBase wisely
attack: 0,
decay: 0.5,
sustain: 0.75,
volumeBase: 1600,
volume: function(_time) {
if ((_time) < attack) { return _time/attack; }
if ((_time) < attack+decay) { return lerp(1, sustain, (_time-attack)/decay); }
return sustain;
},

pattern: [ // 0 = A4, -1 = G#4, 1 = A#4, etc.; "" = don't play
-17, "", "", "", -13, "", "", "",
-12, "", "", "", -15, "", "", "",
-13, "", "", "", -17, "", "", "",
-12, "", "", "", -15, "", "", ""
]

},
// 25% Pulse Wave
{
sample: function(_info) {
var _x = _info[1] /_info[0];
return frac(_x) < 0.25 ? -1 : 1;
},

attack: 0,
decay: 0.25,
sustain: 0.25,
volumeBase: 2000,
volume: function(_time) {
if ((_time) < attack) { return _time/attack; }
if ((_time) < attack+decay) { return lerp(1, sustain, (_time-attack)/decay); }
return sustain;
},

pattern: [
7, 11, 23, 19, 11, 19, 23, 14,
4,  9, 24, 21,  9, 21, 24, 16,
4, 11, 23, 19, 11, 19, 23, 16,
4, 12, 24, 19,  9, 21, 26, 18
]
}
]

GenerateAudio = function() {
// Get total samples by the ratio of sample rate and beats per second, multiplied by total length
var _samples = (sampleRate / ((tempo*subdivision)/60)) * length;
// Multiply it by 2 here because we're gonna be dealing with 2-byte values
audioBuffer = buffer_create(_samples*2, buffer_fixed, 1);
buffer_seek(audioBuffer, buffer_seek_start, 0);

// Store the last beat
// Store info for each of the instruments (current note, elapsed samples, and elapsed seconds)
var _beatLast = -1;
var _noteInfo = array_create(array_length(instruments), array_create(3));

for (var _i = 0; _i < _samples; _i++) {
// Keep track of current position in song
var _beat = floor((_i/sampleRate)*((tempo*subdivision)/60));
var _newBeat = false;
// If new beat, update beat and set flag for later
if (_beatLast != floor(_beat)) {
_newBeat = true;
_beatLast = _beat;
}

// Keep track of final value
var _v = 0;
// Loop through instruments
for (var _j = 0; _j < array_length(instruments); _j++) {
// Get instrument
var _inst = instruments[_j];
if (_newBeat) {
// If a new beat has occurred, get current note, and trigger it
var _note = _inst.pattern[_beat];
if is_real(_note) {
// Dividing the sample rate by the note's value in Hz
// This finds the number of samples needed to generate the note at its frequency
// Also, reset timer values
_noteInfo[_j][0] = sampleRate / (440 * power(2, (_note-12)/12));
_noteInfo[_j][1] = 0;
_noteInfo[_j][2] = 0;
}
}
// Get instrument's sample and volume, and add it to the current value
// Add 1 to the elapsed samples, and 1/sampleRate for elapsed seconds
_v += _inst.sample(_noteInfo[_j]) * _inst.volume(_noteInfo[_j][2]) * _inst.volumeBase;
_noteInfo[_j][1] += 1;
_noteInfo[_j][2] += 1/sampleRate;
}
// Write the final value to the buffer
buffer_write(audioBuffer, buffer_s16, _v);
}
// Create sound ID out of the buffer
audioID = audio_create_buffer_sound(audioBuffer,
buffer_s16,
sampleRate,
0,
buffer_get_size(audioBuffer),
audio_mono);
}

GenerateAudio();
audio_play_sound(audioID, 1, false);
``````

## Example

Here is the little tune I used to test this, and this is what the exact code above will give you.

## Final Thoughts

Although this little sequencer is prefectly find for the project I’m working on, I can possibly add things like effects and PCM samples in the future. I can also possibly just turn this into a full-on .MOD player, but I don’t want to overwork myself. I’m sorry this post is more of a devlog, but I hope this “tutorial” was of any use to you.