1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
#define SDL_MAIN_HANDLED
#include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>
#include <iostream>
#include <cstdint>
#include <cassert>
#include <cmath>
using u8 = unsigned char; // do not use std::uint8_t here
using i16 = std::int16_t;
using i32 = std::int32_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using i64 = std::int64_t;
using f32 = float;
int constexpr wav_header_size = 44;
int constexpr wav_sample_rate = 44100;
int constexpr wav_sample_size_bytes = 2;
// Returns the number of bytes required to store a WAVE file containing n samples.
[[nodiscard]] constexpr i64 wav_get_size_bytes(int f) noexcept
{ return wav_header_size + static_cast<i64>(f) * wav_sample_size_bytes; }
constexpr u8* store_u32_be(u8* p, u32 n) noexcept
{
p[0] = static_cast<u8>((n & 0xff'00'00'00u) >> 030);
p[1] = static_cast<u8>((n & 0x00'ff'00'00u) >> 020);
p[2] = static_cast<u8>((n & 0x00'00'ff'00u) >> 010);
p[3] = static_cast<u8>((n & 0x00'00'00'ffu) >> 000);
return p + 4;
}
constexpr u8* store_u32_le(u8* p, u32 n) noexcept
{
p[3] = static_cast<u8>((n & 0xff'00'00'00u) >> 030);
p[2] = static_cast<u8>((n & 0x00'ff'00'00u) >> 020);
p[1] = static_cast<u8>((n & 0x00'00'ff'00u) >> 010);
p[0] = static_cast<u8>((n & 0x00'00'00'ffu) >> 000);
return p + 4;
}
constexpr u8* store_u16_le(u8* p, u16 n) noexcept
{
p[1] = static_cast<u8>((n & 0xff'00u) >> 010);
p[0] = static_cast<u8>((n & 0x00'ffu) >> 000);
return p + 2;
}
// Writes a WAVE file header into the buffer pointed-to by p.
// The file header describes 1-channel, 44100Hz, signed 16-bit little-
// endian data consisting of n samples. Returns a pointer one-past-the-end
// of the header, where the sample data is expected to begin.
//
// See
// http://soundfile.sapp.org/doc/WaveFormat/
// for format information.
constexpr u8* wav_synthesize_header(u8* p, int n)
{
[[maybe_unused]] u8* const p_original = p;
// Size of data in bytes without metadata:
i64 const data_chunk_size = static_cast<i64>(n) * wav_sample_size_bytes;
assert(data_chunk_size > 0);
assert(data_chunk_size <= std::numeric_limits<u32>::max());
// Size of data & almost all metadata
i64 const riff_chunk_size = data_chunk_size - 8 + wav_header_size;
assert(riff_chunk_size > 0);
assert(riff_chunk_size <= std::numeric_limits<u32>::max());
// RIFF chunk
p = store_u32_be(p, 0x52494646u); // 4cc magic number "RIFF"
p = store_u32_le(p, static_cast<u32>(riff_chunk_size));
// RIFF->WAVE
p = store_u32_be(p, 0x57415645); // 4cc magic number "WAVE"
// RIFF->WAVE->fmt chunk
p = store_u32_be(p, 0x666d7420); // 4cc magic number "fmt "
p = store_u32_le(p, 16); // 16 bytes remaining in this chunk
p = store_u16_le(p, 1); // LPCM
p = store_u16_le(p, 1); // 1 channel
p = store_u32_le(p, wav_sample_rate);
p = store_u32_le(p, wav_sample_rate * wav_sample_size_bytes);
p = store_u16_le(p, wav_sample_size_bytes); // frame alignment in bytes
p = store_u16_le(p, 16); // 16 bits per sample
// RIFF->WAVE->data chunk
p = store_u32_be(p, 0x64617461); // 4cc magic number "data"
p = store_u32_le(p, static_cast<u32>(data_chunk_size));
assert(p - p_original == wav_header_size);
return p;
}
f32 constexpr two_pi = 6.283185307179586476925286766559f;
u32 constexpr wav_samples = wav_sample_rate * 10; // 10 seconds
i64 constexpr wav_buffer_size_bytes = wav_get_size_bytes(wav_samples);
static_assert(wav_buffer_size_bytes > 0);
static_assert(wav_buffer_size_bytes <= std::numeric_limits<u32>::max());
u8 wav_buffer[wav_buffer_size_bytes];
[[nodiscard]] constexpr f32 interpolate(f32 t, f32 a, f32 b) { return a + (b - a) * t; }
[[nodiscard]] constexpr i16 discretize(f32 t) { return static_cast<i16>(t * 32767.0f); }
[[noreturn]] void die() { std::abort(); }
int main()
{
// Write a WAVE file header into wav_buffer, and return a pointer
// just past-the-end of the header (where the samples should go).
u8* data = wav_synthesize_header(wav_buffer, wav_samples);
f32 theta = 0.f;
f32 omega = 0.f;
f32 volume = 0.05f; // keep the volume low
f32 constexpr frequency_start = 55.0f;
f32 constexpr frequency_finish = 880.0f;
for (int i = 0; i < wav_samples; ++i)
{
data = store_u16_le(data, discretize(volume * std::sin(theta)));
omega = interpolate(static_cast<f32>(i) / wav_samples,
two_pi * frequency_start / wav_sample_rate,
two_pi * frequency_finish / wav_sample_rate);
theta = std::fmod(theta + omega, two_pi);
}
SDL_SetMainReady();
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) die();
// Write the synthesized audio to disk:
SDL_RWops* file_stream = SDL_RWFromFile("output.wav", "w");
SDL_RWwrite(file_stream, wav_buffer, 1, wav_buffer_size_bytes);
SDL_RWclose(file_stream);
// Play the synthesized audio.
if (Mix_OpenAudio(wav_sample_rate, AUDIO_S16LSB, 1, 4096) < 0) die();
SDL_RWops* wav_buffer_stream = SDL_RWFromMem(wav_buffer, wav_buffer_size_bytes);
if (wav_buffer_stream == nullptr) die();
Mix_Chunk* chunk = Mix_LoadWAV_RW(wav_buffer_stream, 0);
if (chunk == nullptr) die();
Mix_PlayChannel(-1, chunk, 0);
// Wait for the sound to stop playing.
while (Mix_Playing(-1)) SDL_Delay(100);
// Free resources in reverse order of their acquisition
Mix_FreeChunk(chunk);
SDL_RWclose(wav_buffer_stream);
Mix_CloseAudio();
SDL_Quit();
return 0;
}
|