At the end of last year (2023) and for a lot of 2024, I became interested in learning more about the creation of digital music and its history. Particularly, chip-music or chiptunes, which I've always quite enjoyed the sound of.
I was also learning about writing a game engine and its components using lower-level multimedia libraries. So when I figured out that I could pass audio samples I generated with code, directly to my computer's audio output using SDL2 (Simple Directmedia Layer 2), I became quite excited.
The first thing I tried was to generate some pure tones using various kinds of waveform. Pretty soon, I was able to create any kind of classic synth wave shape I wanted with quite simple functions. I was blasting square/pulse waves, triangle waves, sine waves and - my personal favourite - sawtooth waves at a, somewhat grating, 1khz. The next step was to extend this to actual note values and to add multiple channels. And - as usual - I went a bit overboard writing a relatively complete synthesis library with multiple channels, basic filter effects and volume envelopes. To test it out I threw together a very spartan test interface, with completely unlabelled faders for controlling the envelope parameters and per-channel volume.
Cynth Github RepositoryAnd one thing that really got me excited, was that I could create an oscilloscope visualisation for the waves, given that I already had all the sample data in memory as I was creating them. This is what you can see on the right of the image above; the four separate channels and the mix output at the bottom.
The thing that really motivated me, though, was the idea of creating actual music the same way that they had for retro games consoles like the NES or Commodore64. So, with my emulation of a sound generation chip in hand, I set out to expand the code for the simple tone generator to include handling sequences of music data for multiple channels. This was also around the time I was creating a simple 2D game-engine using SDL2, so I had the perfect use-case.
However, of course I had to take it completely overboard - as I do with all my personal projects - and added too many features, as well as leaving out some pretty crucial ones. And while I did try to use an existing music data format, like MIDI (which was its own adventure into file parsing...) it ultimately didn't really work with what I had planned, so I also ended up creating my own music data file format. In these custom files, I could include a per-channel stream of bytecode instructions to tell the synthesiser which note to set for the channel, and when to trigger its envelope.
...and of course, if I made my own file format... I would have to create my
own editor...
Not that I went really that far (thank goodness), but I did write a simple
assembler-style program that would take a text file with a list of synth
instructions, convert them to bytecode and put them in the binary music file.
The program additionally included a simple visualiser, to show some of the
channels' instructions and to edit instrument parameters.
In the video you can see me tweaking instrument settings with the current instrument in the left oscilloscope, and also playing a sequenced song with the mix output in the right oscilloscope. It's a little janky and you can't actually edit the songs in this view, but it gets the job done and it was quite fun to make.
This was all great fun, and lead to many more experiments with audio, including adapting the engine to allow basic wavetable synthesis. This is a simple form of sample playback, in which short samples are looped to create a repeating oscillation. By playing the sample back at faster or slower speeds, you can resample it to any frequency you like. For this I intentionally used low-fidelity sample resolutions (like 8- or 16-bit) to achieve that nostalgically crispy sound.
Once I had this proof-of-concept wavetable synth whipped up, I made some extremely simple samples for it to test and even let it take multiple values with a delay between them, to create simple sequences. It sounded very interesting, but I was a bit too lazy to create more samples by hand (using a hex editor), so I decided to employ ChatGPT. I was also curious as to what kinds of samples it might produce, and what kind of sequences it might make, so I asked it to simply spit out a series of bytes, explained what their format was and let it churn away.
Here's the wave/sample it generated, which is quite interesting.
It seems to be a quite small, truncated sine wave with some samples
removed to make a sort of comb effect, which adds in some higher
harmonics. It sounds kinda interesting...
Take a listen:
Surprisingly melodic!
After only explaining the format in few sentences and asking ChatGPT
to produce an inscrutable string of hexadecimal, I really hadn't
expected it to sound like much of anything. I did have to ask it to try
again once though, as it had output an uneven number of bytes the first
time. This lead the program to interpret the delay bytes as pitches which
were all obviously extremely low and quite creepy as a first result.
It's also interesting to note, that it got one of the notes wrong in
the conversion process, so it converted both D4 and E4 to 0x40
I had also intended to make a semi-modular synthesiser like this using very interconnectable wavetable-like modules, and try to make that its own minimal synth engine, but I haven't gotten around that that yet.