Last active
March 14, 2026 14:05
-
-
Save marvhus/699dc7b74d6e5d413f7d220a2dc4fd8d to your computer and use it in GitHub Desktop.
WAV Audio
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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