Last active
November 12, 2022 22:23
-
-
Save SteelPh0enix/c8e2434c5074755d9d60caae376de821 to your computer and use it in GitHub Desktop.
Generate simple WAV audio file in C++
This file contains 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
#include <algorithm> | |
#include <array> | |
#include <cinttypes> | |
#include <cmath> | |
#include <fstream> | |
#include <iostream> | |
#include <string_view> | |
#include <vector> | |
#include "wav_header.hpp" | |
namespace ranges = std::ranges; | |
constexpr unsigned DefaultSampleRate = 44100u; | |
std::vector<double> generate_sine(double const frequency, double const duration, | |
unsigned const samples_per_second) { | |
std::vector<double> samples{}; | |
std::size_t const samples_count = static_cast<std::size_t>(duration * samples_per_second); | |
samples.resize(samples_count); | |
double const phi_step = (2.0 * M_PI * frequency) / samples_per_second; | |
double phi = 0.0; | |
for (auto& sample : samples) { | |
sample = std::sin(phi); | |
phi += phi_step; | |
} | |
return samples; | |
} | |
void save_as_wav(std::string_view filename, std::vector<uint8_t> const& samples, | |
WavHeader const& header) { | |
std::fstream wav_file(filename.data(), | |
std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); | |
if (!wav_file.is_open()) { | |
std::cerr << "Could not open file '" << filename << "'!" << std::endl; | |
return; | |
} | |
auto const header_bytes = header.to_bytes(); | |
wav_file.write(reinterpret_cast<char const*>(header_bytes.data()), header_bytes.size()); | |
wav_file.write(reinterpret_cast<char const*>(samples.data()), samples.size()); | |
} | |
int main() { | |
auto samples = generate_sine(2137, 2.0, DefaultSampleRate); | |
std::vector<uint8_t> samples_bytes{}; | |
samples_bytes.reserve(samples.size()); | |
ranges::transform(samples, std::back_inserter(samples_bytes), | |
[](auto const sample) { return static_cast<uint8_t>((sample + 1.0) * 127.5); }); | |
WavHeader header{ | |
.amount_of_samples = static_cast<uint32_t>(samples_bytes.size()), | |
.amount_of_channels = 1, | |
.samples_per_second = DefaultSampleRate, | |
.bits_per_sample = 8, | |
}; | |
save_as_wav("output.wav", samples_bytes, header); | |
} |
This file contains 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
#pragma once | |
#include <array> | |
#include <cstdint> | |
template <typename Array> | |
constexpr void uint16_to_bytes_le(uint16_t value, Array& buffer, std::size_t offset) { | |
buffer[offset] = (value & 0xFFu); | |
buffer[offset + 1u] = (value & 0xFF00u) >> 8u; | |
} | |
template <typename Array> | |
constexpr void uint32_to_bytes_le(uint32_t value, Array& buffer, std::size_t offset) { | |
buffer[offset] = (value & 0xFFu); | |
buffer[offset + 1u] = (value & 0xFF00u) >> 8u; | |
buffer[offset + 2u] = (value & 0xFF0000u) >> 16u; | |
buffer[offset + 3u] = (value & 0xFF000000u) >> 24u; | |
} | |
struct WavHeader { | |
uint32_t amount_of_samples; | |
uint16_t amount_of_channels; | |
uint32_t samples_per_second; | |
uint16_t bits_per_sample; | |
auto to_bytes() const { | |
std::array<uint8_t, 44> header{}; | |
uint16_t const bytes_per_sample = bits_per_sample / 8u; | |
uint32_t const amount_of_bytes_in_data = | |
amount_of_samples * amount_of_channels * bytes_per_sample; | |
uint32_t const byte_rate = samples_per_second * amount_of_channels * bytes_per_sample; | |
uint16_t const block_align = amount_of_channels * bytes_per_sample; | |
uint32_t const sub_chunk_1_size = 16; | |
uint32_t const sub_chunk_2_size = amount_of_bytes_in_data; | |
uint32_t const chunk_size = 4u + 8u + sub_chunk_1_size + 8u + sub_chunk_2_size; | |
// ChunkID - RIFF | |
header[0] = 'R'; | |
header[1] = 'I'; | |
header[2] = 'F'; | |
header[3] = 'F'; | |
// ChunkSize | |
uint32_to_bytes_le(chunk_size, header, 4); | |
// Format - WAVE | |
header[8] = 'W'; | |
header[9] = 'A'; | |
header[10] = 'V'; | |
header[11] = 'E'; | |
// SubChunk1ID | |
header[12] = 'f'; | |
header[13] = 'm'; | |
header[14] = 't'; | |
header[15] = ' '; | |
// SubChunk1Size | |
uint32_to_bytes_le(sub_chunk_1_size, header, 16); | |
// AudioFormat - 1 for PCM | |
uint16_to_bytes_le(1, header, 20); | |
// NumChannels | |
uint16_to_bytes_le(amount_of_channels, header, 22); | |
// SampleRate | |
uint32_to_bytes_le(samples_per_second, header, 24); | |
// ByteRate | |
uint32_to_bytes_le(byte_rate, header, 28); | |
// BlockAlign | |
uint16_to_bytes_le(block_align, header, 32); | |
// BitsPerSample | |
uint16_to_bytes_le(bits_per_sample, header, 34); | |
// SubChunk2ID | |
header[36] = 'd'; | |
header[37] = 'a'; | |
header[38] = 't'; | |
header[39] = 'a'; | |
// SubChunk2Size | |
uint32_to_bytes_le(sub_chunk_2_size, header, 40); | |
return header; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment