Skip to content

Instantly share code, notes, and snippets.

@marvhus
Last active March 14, 2026 14:05
Show Gist options
  • Select an option

  • Save marvhus/699dc7b74d6e5d413f7d220a2dc4fd8d to your computer and use it in GitHub Desktop.

Select an option

Save marvhus/699dc7b74d6e5d413f7d220a2dc4fd8d to your computer and use it in GitHub Desktop.
WAV Audio
// gcc sample.c -o sample -lm && ./samples && mpv --loop=inf sample.mpv
#include <assert.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include "WAV.c"
#define PI 3.14159265f
#define TAU (PI * 2.0f)
#define FREQUENCY 48000
#define PITCH_STANDARD 440.0f
typedef struct {
float semitones;
float amplitude;
float beats;
float speed; // attack/release speed
} Note;
float freq_from_semitone(float semitones) {
float a = 1.05946309435929526456f; // a = 2^(1/12)
return PITCH_STANDARD * powf(a, semitones); // f(n) = f(0) * a^n
}
#define SECTION_1 \
(Note){.semitones = 0.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 0.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 0.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 0.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 0.0f, .beats = 0.50f, .amplitude = 0.25f, .speed = 60.0f}, \
\
(Note){.semitones = 0.0f, .beats = 0.50f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 0.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 0.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 0.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 0.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 0.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 0.0f, .beats = 0.50f, .amplitude = 0.25f, .speed = 60.0f}
#define SECTION_2 \
(Note){.semitones = 5.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 5.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 5.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 5.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 5.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 5.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 5.0f, .beats = 0.50f, .amplitude = 0.25f, .speed = 60.0f}, \
\
(Note){.semitones = 3.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 3.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 3.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 3.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 3.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 3.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}, \
(Note){.semitones = 3.0f, .beats = 0.50f, .amplitude = 0.25f, .speed = 60.0f}
#define SECTION_3 \
(Note){.semitones = -2.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = -2.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}
#define SECTION_4 \
(Note){.semitones = 5.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 60.0f}, \
(Note){.semitones = 5.0f, .beats = 0.25f, .amplitude = 0.25f, .speed = 50.0f}
int main(void)
{
Note notes[] = {
SECTION_1,
SECTION_2,
SECTION_3,
SECTION_1,
SECTION_4,
SECTION_1,
SECTION_4,
};
float bpm = 120.0f;
float beat_duration = 60.0f / bpm;
// NOTE(mvh): Measures the total length
uint32_t samples_count = 0;
for (size_t index = 0; index < sizeof notes / sizeof *notes; ++index) {
Note note = notes[index];
samples_count += (uint32_t) roundf(FREQUENCY * beat_duration * note.beats);
}
float samples[samples_count];
uint32_t samples_cursor = 0;
// NOTE(mvh): Generates the samples
for (size_t index = 0; index < sizeof notes / sizeof *notes; ++index) {
Note note = notes[index];
float note_freq = freq_from_semitone(note.semitones);
uint32_t duration = (uint32_t) roundf(FREQUENCY * beat_duration * note.beats);
for (uint32_t offset = 0; offset < duration; ++offset) {
uint32_t total_offset = samples_cursor + offset;
float time = (float) total_offset / FREQUENCY;
float sample = sinf(time * note_freq * TAU);
// NOTE(mvh): Simple envelope
float note_time = (float) offset / FREQUENCY;
float attack = fminf(1.0f, note.speed * 1.0f * note_time);
float release = fminf(1.0f, note.speed * 1.0f * (beat_duration * note.beats - note_time));
float amplitude = note.amplitude * attack * release;
samples[total_offset] = sample * amplitude;
}
samples_cursor += duration;
}
{ // NOTE(mvh): Saves the samples to disk as a WAV
size_t wav_file_size = 0;
void* wav_file_data = build_wav_file(1, FREQUENCY, WAV_AUDIO_FORMAT_IEEE_754_FLOAT, 32,
samples, samples_count * sizeof *samples, &wav_file_size);
assert(wav_file_data != NULL);
assert(wav_file_size > 0);
FILE* file = fopen("sample.wav", "wb");
assert(file != NULL);
fwrite(wav_file_data, 1, wav_file_size, file);
fclose(file);
}
}
typedef struct {
uint32_t block_id; // "RIFF"
uint32_t file_size; // total size - 8
uint32_t format_id; // "WAVE"
} WAV_RIFF_Header;
typedef struct {
uint32_t block_id; // "fmt "
uint32_t block_size; // sizeof(WAV_Format_Header) - 8
uint16_t audio_format;
uint16_t channels;
uint32_t frequency;
uint32_t bytes_per_second; // frequency * bytes_per_block
uint16_t bytes_per_block; // channels * bits_per_sample / 8
uint16_t bits_per_sample;
} WAV_Format_Header;
typedef struct {
uint32_t block_id; // "data"
uint32_t data_size;
} WAV_Data_Header;
#define WAV_FORMAT_ID "WAVE"
#define WAV_RIFF_BLOCK_ID "RIFF"
#define WAV_FORMAT_BLOCK_ID "fmt "
#define WAV_DATA_BLOCK_ID "data"
#define WAV_AUDIO_FORMAT_PCM_INTEGER (1)
#define WAV_AUDIO_FORMAT_IEEE_754_FLOAT (3)
void* build_wav_file(uint16_t channels, uint32_t frequency,
uint16_t audio_format, uint16_t bits_per_sample,
void* audio_data, uint32_t audio_size_in_bytes,
size_t* out_wav_file_size)
{
uint32_t total_file_size = sizeof (WAV_RIFF_Header)
+ sizeof (WAV_Format_Header)
+ sizeof (WAV_Data_Header)
+ audio_size_in_bytes;
void* data = malloc(total_file_size);
if (data == NULL) return NULL;
if (out_wav_file_size != NULL) *out_wav_file_size = total_file_size;
uint8_t* block_base = data;
{
WAV_RIFF_Header* header = (WAV_RIFF_Header*) block_base;
block_base += sizeof *header;
header->block_id = *(uint32_t*) WAV_RIFF_BLOCK_ID;
header->format_id = *(uint32_t*) WAV_FORMAT_ID;
header->file_size = total_file_size - 8;
}
{
WAV_Format_Header* header = (WAV_Format_Header*) block_base;
block_base += sizeof *header;
header->block_id = *(uint32_t*) WAV_FORMAT_BLOCK_ID;
header->block_size = (sizeof *header) - 8;
header->channels = channels;
header->frequency = frequency;
header->audio_format = audio_format;
header->bits_per_sample = bits_per_sample;
header->bytes_per_block = frequency * bits_per_sample / 8;
header->bytes_per_second = channels * header->bytes_per_block;
}
{
WAV_Data_Header* header = (WAV_Data_Header*) block_base;
block_base += sizeof *header;
header->block_id = *(uint32_t*) WAV_DATA_BLOCK_ID;
header->data_size = audio_size_in_bytes;
uint8_t* current_audio_byte = (uint8_t*) audio_data;
while (audio_size_in_bytes-- > 0) *block_base++ = *current_audio_byte++;
}
return data;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment