In this log I'll be writing a bit about the software design side of the project. More specifically, I'll discuss some of the compromises that needed to be made with respect to the limited amount of flash memory in the ATtiny84. I might also veer into some more technical details on how FATCATs playback engine works.
When starting the project I was by no means aiming for the stars with what I planned to use for FATCATs built-in sound library. I was fully aware of how little I had to work with so I planned to use what I considered to be a very modest set of wavetables.
The base
Proto-Fatcat (discussed in the first project log) had used a simple squarewave base generated in CTC timer mode. The FATCAT code was now in the process of being completely rewritten to feature proper wavetable syntesis and channel mixing. I tried out some other waveforms for the base as I now had infinite options.
First off, sine and triangle waves were too quiet even at maximal amplitude so they were out. I had realized that when using complex waveforms with overtone frequencies, the timbre of the sound could be varied using simple bit shift operations. That would be a cool effect to have on the base for sure. But I also realized I had to be economical. Musically, the original simple square waveform is actually a pretty good option for base, and it has the great benefit of being easy to calculate on the fly using a simple algorithm, so I wouldn't even need to store any waveform data for this instrument.
That saved me 256 bytes of flash right away.
The arp
For the arp track I wanted a sound that complemented the base well and was suitable either as an arpeggiated chord or as a lead instrument. I started off with the same process of trying out some different wavetables. I found that simple waveforms just sounded like some awful 80's chordless phone signal when arpeggiated. And it's hard to use a too complex wavetable that's also versatile enough as a single instrument. The size of wavetable I was aiming for isn't well suited for using samples of actual real instruments.
As a primer for writing FATCATs playback engine, I'd been reading a chapter on sound synthesis and mixing in the excellent book "Make: AVR Programming. Learning to Write Software for Hardware" by Elliott Williams. The online material of that book included a simple Python script for generating wavetables from different variations of waveforms. After experimenting with a few of these I found a complex sawtooth wave, with 15 overtones of itself mixed in, that I became very impressed with. I found it to be very versatile as it varied in character depending on playback octave. For example, at lower octaves it reminded me a bit of an electric piano sound. It also performed very well with the bit shift trick mentioned earlier. But most importantly it had a pleasant sound to it (relatively speaking) and it also blended well with the base sound. So I decided to stick with it.
The drums
As with the base, the story of the drum sounds goes back to good old proto-FATCAT. It had just three drum sounds: Kick, snare and a closed hihat. With proto-FATCAT I decided to have each drum sample be 1k in size for simplicity. It was a bit of work to edit together samples of that size that actually sounded well played together through a piezo speaker. Only the kick sound ended up being based on an actual drum sample. I found that the snare and hihat sounded better if I just used pink and white noise (respectively) instead of actual sampled drums. In the end each wavetable ended up being 800 bytes long.
Since I had already put in the work in designing a drumkit for proto-FATCAT, I decided to just keep using that in this project. Some weeks into the project, as FATCATs code size kept growing at an alarming rate, I realized that the 3 x 800 byte allowance for the drumkit was simply far to extravagant. FATCAT needed to go on a diet!
For generating white noise I realized that I could just traverse a pointer through program code, eliminating the need for the hihat wavetable, which turned out to have been a big waste of space the whole time. Having only done any kind low-level programming for the last couple of years, I'm still frequently becoming aware of little programming tricks like that.
Relentlessly the code size kept increasing. I had to whittle down the two remaining samples, first to 400 bytes, then to 256 bytes. I compensated for the corresponding reduction in drum sound duration by mixing in a whiff of white noise at the end of each sound, which actually turned out to be an improvement in some ways. The noise added a bit of brightness and decay to the sounds. I also wanted to have an alternate sound option for the kick and snare, which I achieved within my allotted memory budget by mixing the original wavetables at different frequencies in two channels.
The patchbay
Later in the project I started working on the patchbay portion of the playback engine. It's function is to allow the user to alter the music in different ways during playback. As ever the memory allotted to each patch function couldn't be very large in size. That meant that the patches mainly trigger simple altering of variables or combining already existing functionality in new ways. One such patch activated recombination I discovered was to have the baseline momentarily use the last triggered drum sound as it's wavetable, instead of it's regular squarewave. That effect turned out to be surprisingly awesome, as both the base and snare sounded very cool as temp workers for the base. The "base=kick" had a vocodery, babbley sound to it when combined with the base portamento effect, and the "base=snare" sounded sort of like synth brass or something. And they worked great together too. The result is demonstrated in the short demo video in "Project Details", which was recorded right around the time I was working on this.
Killing a darling
A bit further still into the project I was faced with bumping up right against the 8k code limit. And still a few hundred bytes of flash needed to be freed up for that last piece of functionality I just had to implement. Everything I could think of to reduce actual code size had already been done. (I'm sure that in actuality there's still some fat to cut here and there -I'm not an expert C programmer.) So once more I was forced to reach for the knife, and this time ask myself: Who's it gonna be? The kick or the snare?
Well, if you don't have a kick you don't have a drumkit, it's as simple as that. So it would have to be the snare. I then had to carefully stitch together a new snare sound from a high pitched kick and some more white noise. It sounded pretty good actually, but that "base=kick / base=snare" patch hasn't been quite the same since. And I still haven't managed to create a snare variation sound I'm satisfied with.
Oh well, it was all done for a greater good.
Summing up this whole exercise, what I'm left with in terms of actual wavetables for this "wavetable synth" is just the sawtooth for the arp track and the kick sample weighing in at a total of 500 bytes.
But hey, wavetables shmavetables! Who needs them?
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.