Glitch: beyond the bytebeat
Glitch started as a Bytebeat music synthesizer. Bytebeat has some really unique features: simple concept, terse notation, dirty old-school sound. But at the same point Bytebeat is limited by its ultra-simplicity.
Many curious minds today experiment with algorithmic music composition and programmable music. In most cases this means configuring some software and learning some new programming languages.
Glitch aims to combine the simplicity of Bytebeat with the flexibility of a full-featured programmable music environment.
The core idea of Glitch is borrowed from Bytebeat — a musician enters a math expression, then Glitch evaluates it for each discrete time frame and the returned values describe the amplitude of the waveform. Output is assumed to be in the range [-1..1] and it is clipped if it goes beyond the given range. You can use mathematical expressions, variables and some functions. Everything is a number, which means you can combine things easily by passing numbers from one function to another. Now, how many functions shall we add to turn Glitch into a real music programming tool?
With Bytebeat techniques turning input time variable t into a waveform is likely to result in a saw-tooth or square wave and that’s why you get that buzzing sound. To get something more melodic we would probably need to have sine and triangular oscillators. So, Glitch now has some oscillator functions:
- sin(freq) — generates sine wave of the given frequency
- tri(freq) — generates triangular wave of the given frequency
- saw(freq) — generates saw-tooth wave of the given frequency
- sqr(freq, [width]) — generates square wave of the given frequency. Pulse width can be specified, too. By default it’s 0.5.
All these functions advance the oscillator phase internally and apply the frequency changes in a smart way to reduce clicks. For example, the formula w=(t>>10)&7,(tri(w*220)+sin(w*220))/2 (click it to open in Glitch) mixes together triangular and sine oscillators playing the same frequency (which varies as time t increases). Here we normalize the volume dividing it by two to avoid buzzing when amplitude overflows.
Oscillators can be combined in parallel (like in the example above when amplitude values are added) or sequentially, when output of one oscillator becomes the input of another one, e.g. w=(t>>7)&31,sin(100*tri(w*4)+400). Here the w value (which varies in time in the range 0..31) is an input to the triangular oscillator which generates a signal with some low frequency. The output of that oscillator modifies the frequency of the outer sine oscillator and it results in a frequency-modulated sound in the diapason 400..500 Hz.
Don’t forget that there is also a special r([max]) function which returns a random number in the range [0..max). It can be used as an oscillator in some cases (e.g. synthesizing drums).
A better way to build frequency-modulated sound is to use a special function fm(freq, [f1, a1, f2, a2, f3, a3]). The function imitates a 3-operator FM-synth with the following topology:
If you’re unfamiliar with how FM synthesis works — you can read more here.
The fm function is a roughly equivalent of sin(freq + a1*sin(freq*f1 + a3*sin(freq*f3)) + a2*sin(freq*f2)).
Don’t panic if this formula looks complicated. Let’s see what happens if all parameters except for the frequency are set to zero. In this case fm(440) is identical to sin(440) and you will hear a simple sine wave. Now, let’s configure the first operator. It affects the frequency of the main (carrier) oscillator. The first parameter f1 is the frequency multiplier. Let’s set it to 2 so that our oscillator #1 will be running at frequency 2*440= 480 Hz. Also, to make it audible let’s set amplitude gain a1 to 1. We get fm(440, 2, 1) function which is identical to sin(440 + sin(880)) and you can hear how the sound gets some higher pitches and how it varies in time. Try tweaking f1 and a1 parameters to hear how they affect the main oscillator.
Now you can enhance your synthesizer by adding another oscillator in parallel (changing f2 and a2). In this case the output of those two oscillators will be summed and used to shift the frequency of the main oscillator. Or you can set f2 and a2 to zero using f3 and a3 instead. The output of the third oscillator will be forwarded into the first oscillator that controls the main oscillator. Compare how fm(440, 0.5, 0.5, 0, 0, 0.5, 0.8) differs from fm(440, 0.5, 0.5). Of course nothing stops you from setting parameters to all three oscillators, so feel free to share if you get an interesting sound!
This FM topology is simple enough to understand and allows to build a basic single-operator FM synth, two-operator FM synths O1+O2 and O1(O3), and a more complex O1(O3)+O2 variant. Did I mention that you can combine multiple FM synths as well since they are just functions?
Playing a single tone is boring. Let’s play some tunes. But how can we write a melody without notes? Well, we can use frequencies… In the first version of Glitch there was a special “array” function a(index, [a0, a1, a2, …]) which returns its argument defined by the index. Index will overflow if it goes out of the array range and it is the most simple sequencer you can build — for example sin(a(t>>10, 440, 523, 659)) plays a simple arpeggio looping though A4, C5 and E5 notes.
But nobody wants to remember all note frequencies. That’s why new Glitch has a helper hz(note) that returns note frequency for a given note. Zero stands for A note of the 4th octave. You can also use floating point note values to get microtonal music.
Another helper function is scale(note, [mode]) which returns the note at the given position in the given scale. Default mode is Ionian mode, which is a traditional major scale. Modes 1..6 are dorian, phrygian, lydian, mixolydian, aeolian and locrian modes. If you’re unfamiliar with music theory just remember that zero is major scale and five is minor scale. There are two other minor scales — harmonic minor which is mode #7 and melodic minor mode #8. Guitar players may probably check out the pentatonic scales: #9 is major pentatonic and #10 is minor pentatonic. #11 is a blues scale. #12 is whole tone scale and #13 is octatonic scale. Values above 13 fall back to the chromatic scale, the one that includes all 12 notes in a row.
Thinking in notes instead of pitches simplifies things, but we also want to think about rhythmic patterns, not just arrays of numbers. So here two sequencer functions:
- loop(bpm, …) — loops though its arguments at the given tempo
- seq(bpm, …) — very similar to loop, but evaluates arguments only once per beat
You might be confused with the difference, so let’s see some examples.
sin(hz(seq(240, 0, 5, 9, 5))) — plays arpeggio from the major scale at a tempo of 240 beats per minute.
Now let’s play nested sequences, for example playing two arpeggios changing each other. If you write sin(hz(seq(30, seq(240, 0, 5, 9, 5), seq(240, 1, 6, 10, 6)))) you won’t hear the expected result. That’s because seq function evaluates the arguments only once and stores it for the rest of the beat. Changing the outermost seq to loop fixes it.
As a rule of thumb — use seq for sequencing plain notes, use loop for scheduling inner sequences.
Inside seq you’re safe to use r() function because it will be evaluated once and will not produce the white noise. Instead it will return a single random number for each beat. Using loop and seq also produces an interesting side effect when the inner sequence is longer than the time granted by the outer loop. In this case the inner sequence will be rolled on each call. You may experiment with irregular tempo ratios to get interesting rhythmic patterns.
Shape of sound
Now we can play tunes but our oscillators have constant volume. You can provide an envelope to your signal to make it sound like a real instrument — with attack (rapid volume rise), release (volume fade out) and other parameters. There is a special variadic function env(signal, (dt, val), (dt, val)…) that wraps signal into some envelope shape. Without parameters env(sin(440)) plays an unaltered sine wave.
It’s quite common to use so-called percussive envelopes (the ones with attack and release only). In Glitch env(sin(440), (0.5, 0)) becomes a fading out beep of 500 milliseconds, env(sin(440), (0.5, 1), (0.1, 0)) has attack time of 0.5 second and release time of 0.1 second which sounds like fade-in and cutoff. You can give a more complex envelope, for example env(sin(440), (0.01, 1), (0.1, 0.7), (0.1, 0.3), (0.1, 0)) which results in quick attack (10ms), at 110ms the volume will be 70%, at 210ms it will be 30% and at 310ms it will be back to zero.
Now, you may have noticed that an envelope is played only once. How to reset it? All functions in Glitch take numeric parameters and return numbers. However there is a special “not-a-number” value that has very specific meaning, namely — to reset envelops, effects and oscillators. Not-a-number value is returned by the sequencers automatically when the beat changes and this value is propagated from one function to another so the envelope function will definitely receive it.
To keep Glitch simple there is only one sound effect available at the moment — a low pass filter. The function lpf(signal, [freq]) cuts off all the frequencies below the given one (which defaults to 200 Hz) and returns a modified signal. What about high-pass filters? Since lpf returns a signal without high pitch frequencies — you can build high-pass filters by subtracting the lpf value from the main signal. You can nest low pass filters and apply them multiple times. You can also vary the cutoff frequency value with some low-frequency oscillator and get some interesting vibrato effect.
In your music you are likely to have a number of instruments and parts. If you just add their signals, you can also divide the sum of signals to normalize them, but there is a special helper function mix(…) that does it for you. Depending on the number of inputs, mix adds them and normalizes. It also softly clips the output signal to avoid the unexpected overdrive effect.
Here’s a full list of all Glitch functions available. We’ve added 15 functions which are easy to remember and to use:
- Oscillators: sin(freq), tri(freq), saw(freq), sqr(freq, [width]), fm(freq, …)
- Sequencers: loop(bpm, …), seq(bpm, …), slide(bpm, …)
- Utilities: a(index, …), scale(index, [mode]), r([max]), hz(note), lpf(signal, [freq]), env(signal, …), mix(…)
To sum up my theoretical story — here’s a quick demo song I’ve recently made with Glitch:
Glitch is a minimal open source algorithmic music composer that uses simple mathematical formulas. It’s easy to learn (it’s all just arithmetic expressions), easy to use (runs in your browser), easy to share (just send a link).