NoiSQL (named after Robert Noyce) shows how to play sound and music with declarative SQL queries.
It contains oscillators for basic waves, envelopes, sequencers, arpeggiators, effects (distortion, delay), noise generators, AM and FM, and LFO, …
Sometimes it can generate something nice, but usually not.
Quick Start – Linux
Clone the Repository.
Download clickhouse-local:
curl https://clickhouse.com/ | sh
Install (Linux):
sudo ./clickhouse install
Check the installation:
clickhouse-local --version
Quick Start – MacOS
Before beginning, please ensure that you have homebrew installed.
Clone the Repository.
Change to the Repository folder (i.e. cd NoiSQL
)
Install ClickHouse (macOS):
mkdir -p bin
mv clickhouse bin/clickhouse-local
export PATH="$(pwd)/bin:$PATH"
NOTE: If you want this to live past a terminal restart add to your profile. That may look something like the below or .bash_profile
or .zshrc
depending on your terminal of choice.
echo 'export PATH="'$(pwd)'/bin:$PATH"' >> .profile
In order to playback audio from the terminal (via STDIN) we use an open source project (with a convenient brew recipe) called ‘sox’
Bleeps and Bloops – a demonstration
Now, ClickHouse Local is setup and it is time to make our first noises.
Demo (Linux):
./music2.sql.sh | aplay -f cd
Demo (macOS):
./music2.sql.sh | play -t raw -b 16 -e signed -c 2 -r 44100 -v .75 -
Live editing (Linux):
sudo apt install inotifytools
./play.sh music0.sql
You can edit the music0.sql
file, and the changes will automatically apply while playing. On macOS this is not possible due to the lack of inotifytools BUT the play script can be used to play any of the samples music*.sql files provided
Examples
If, you are unable to use it yourself. You can still hear the output as .mp4 here.
An SQL query selects from the system.numbers
table, containing a sequence of natural numbers, and transforms this sequence into a PCM audio signal in the CD format. This output signal is piped to the aplay -f cd
tool (on Linux) to play it.
The CD (Compact Disc Audio) format is a 44100 Hz 16-bit stereo PCM signal.
- 44100 Hz is the “sampling frequency”, meaning that the signal has a value 44 100 times every second;
- 16 bit is the precision of every value, and the value is represented by
Int16
(signed, two’s complement, little endian) integer; - stereo means that we have two values at every sample – for the left channel and for the right channel;
The signal is represented in binary, corresponding to the ClickHouse’s RowBinary
format. Therefore, the bit rate of the signal is 16 * 2 * 44100 = 1411 kBit.
Although we could also use the classic 8-bit mono 9 kHz format, the CD format gives more possibilities.
To get the idea, run this:
clickhouse-local --query "
SELECT
(number % 44100)::Int16 AS left,
((number + 22050) % 44100)::Int16 AS right
FROM system.numbers
FORMAT RowBinary" | aplay -f cd
It will give you an uninteresting clicky sound.
clickhouse-local --query "
WITH number * 2 * pi() / 44100 AS time
SELECT
(sin(time * 200) * 0x7FFF)::Int16 AS left,
(sin(time * 400) * 0x7FFF)::Int16 AS right
FROM system.numbers
FORMAT RowBinary" | aplay -f cd
It will give you uninteresting waves.
Making Something Interesting
Here is a query from music0.sql, that generates something at least listenable. Let’s walk through this SQL query.
The WITH clause in the SQL query allows defining expressions for further use.
We can define both constants and functions (in form of lambda expressions).
We use the allow_experimental_analyzer
setting to make this query possible.
Input
Let’s define the time
column to be a floating point value representing the number of seconds.
WITH
44100 AS sample_frequency
, number AS tick
, tick / sample_frequency AS time
Output Control
Let us work with the signal in the floating point format, with values in the [-1, 1]
range.
Here are the functions to convert it to the output PCM CD signal.
A knob for the volume:
If the signal exceeds the boundaries, it will be clipped:
Basic waves
We define oscillators as functions of time, with the period adjusted to be one second.
You can modify the frequency simply by multiplying the time argument.
For example, sine_wave(time * 400)
– a sine wave of 400 Hz frequency.
The sine wave gives the cleanest and most boring sound.
LFO means “Low-Frequency Oscillation,” and it is used to control a parameter of one signal with another low-frequency signal.
It can have multiple interesting effects.
For example, AM – amplitude modulation is modulating the volume of one signal with another signal, and it will give a trembling effect, making your sine waves sound more natural.
Another example, FM – frequency modulation, is making the frequency of one signal change with another frequency.
See the explanation.
We take the wave and map it to the [from, to]
interval:
Generating noise is easy. We just need random numbers.
But we want the noise to be deterministic (determined by the time) for further processing.
That’s why we use cityHash64
instead of a random and erf
instead of randNormal
.
All the following are variants of white noise. Although we can also generate brown noise with the help of runningAccumulate
.
Distortion alters the signal in various ways to make it sound less boring.
The harshest distortion – clipping – amplifies the signal, then clips what’s above the range.
It adds higher harmonics and makes sound more metallic, and makes sine waves more square.
pow
function, such as square root to the [-1, 1]
signal:
It adds some sort of noise, making it sound worn out.
, (time, wave, amount) -> (time - floor(time) < (1 - amount)) ? wave(time * (1 - amount)) : 0 AS thin
Skewing the waves in time making sine ways more similar to sawtooth waves:
, (time, wave, amount) -> wave(floor(time) + pow(time - floor(time), amount)) AS skew
Envelopes
The envelope is a way to make the signal sound like a note of a keyboard musical instrument, such as a piano.
It modulates the volume of the signal by:
- attack - time for the sound to appear;
- hold - time for the sound to play at maximum volume;
- release - time for the sound to decay to zero;
This is a simplification of what typical envelopes are, but it's good enough.
, (time, offset, attack, hold, release) ->
time < offset ? 0
: (time < offset + attack ? ((time - offset) / attack)
: (time < offset + attack + hold ? 1
: (time < offset + attack + hold + release ? (offset + attack + hold + release - time) / release
: 0))) AS envelope
We can make the musical note sound periodically to define a rhythm.
For convenience, we define "bpm" as "beats per minute" and make it sound once in every beat.
, (bpm, time, offset, attack, hold, release) ->
envelope(
time * (bpm / 60) - floor(time * (bpm / 60)),
offset,
attack,
hold,
release) AS running_envelope
Sequencers
To create a melody, we need a sequence of notes. A sequencer generates it.
In the first example, we simply take it from an array and make it repeat indefinitely:
, (sequence, time) -> sequence[1 + time::UInt64 % length(sequence)] AS sequencer
But the obvious way to generate a melody is to take a Sierpinski triangle.
Sierpinski triangles sound delicious:
, time -> bitAnd(time::UInt8, time::UInt8 * 8) AS sierpinski
Another way is to map the number of bits in a number to a musical note:


Sign Up to Our Newsletter
Be the first to know the latest updates
Whoops, you're not connected to Mailchimp. You need to enter a valid Mailchimp API key.