Skip to content

Instantly share code, notes, and snippets.

@PanagiotisPtr
Last active March 5, 2024 09:05
Show Gist options
  • Save PanagiotisPtr/bacbc4e4cbe9eabd32a540f6ef8ffae6 to your computer and use it in GitHub Desktop.
Save PanagiotisPtr/bacbc4e4cbe9eabd32a540f6ef8ffae6 to your computer and use it in GitHub Desktop.
WAV File Reader / Writer C++
#pragma once
#include "Audio.h"
Audio::Audio(std::string str) {
if (str.substr(str.size() - 4) != ".wav")
throw std::invalid_argument("Can only read WAV files!");
load_wav(str);
}
void Audio::load_wav(std::string str) {
FILE *fp;
errno_t err = fopen_s(&fp, str.c_str(), "rb");
if (err != 0)
throw std::runtime_error("Error opening file!");
// Chunk
fread(type, sizeof(char), 4, fp);
if (strcmp(type, "RIFF"))
throw std::runtime_error("Not a RIFF file!");
fread(&ChunkSize, sizeof(int), 1, fp);
fread(format, sizeof(char), 4, fp);
if (strcmp(format, "WAVE"))
throw std::runtime_error("Not a WAVE format!");
// 1st Subchunk
fread(Subchunk1ID, sizeof(char), 4, fp);
if (strcmp(Subchunk1ID, "fmt "))
throw std::runtime_error("Missing fmt header!");
fread(&Subchunk1Size, sizeof(int), 1, fp);
fread(&AudioFormat, sizeof(short), 1, fp);
fread(&NumChannels, sizeof(short), 1, fp);
fread(&SampleRate, sizeof(int), 1, fp);
fread(&ByteRate, sizeof(int), 1, fp);
fread(&BlockAlign, sizeof(short), 1, fp);
fread(&BitsPerSample, sizeof(short), 1, fp);
// 2nd Subchunk
fread(Subchunk2ID, sizeof(char), 4, fp);
if (strcmp(Subchunk2ID, "data")!=0)
throw std::runtime_error("Missing data header!");
fread(&Subchunk2Size, sizeof(int), 1, fp);
// Data
//Subchunk2Size = NumSamples * NumChannels * BitsPerSample/8
int NumSamples = Subchunk2Size / (NumChannels*(BitsPerSample / 8));
data = std::vector<std::pair<short, short> >(NumSamples);
for (int i = 0; i < NumSamples; i++) {
fread(&data[i].first, sizeof(short), 1, fp);
fread(&data[i].second, sizeof(short), 1, fp);
}
fclose(fp);
}
void Audio::write_wav(std::string str) {
FILE *fp;
fopen_s(&fp, str.c_str(), "wb");
fwrite(type, sizeof(char), 4, fp);
fwrite(&ChunkSize, sizeof(int), 1, fp);
fwrite(format, sizeof(char), 4, fp);
// 1st Subchunk
fwrite(Subchunk1ID, sizeof(char), 4, fp);
fwrite(&Subchunk1Size, sizeof(int), 1, fp);
fwrite(&AudioFormat, sizeof(short), 1, fp);
fwrite(&NumChannels, sizeof(short), 1, fp);
fwrite(&SampleRate, sizeof(int), 1, fp);
fwrite(&ByteRate, sizeof(int), 1, fp);
fwrite(&BlockAlign, sizeof(short), 1, fp);
fwrite(&BitsPerSample, sizeof(short), 1, fp);
// 2nd Subchunk
fwrite(Subchunk2ID, sizeof(char), 4, fp);
fwrite(&Subchunk2Size, sizeof(int), 1, fp);
// Ideally instead of the for loop there would be something like
// fwrite(&data[0], sizeof(short), data.size(), fp);
// But I have pairs and they have to be written interchangebly like right,left right,left right,left...
for (int i = 0; i < data.size(); i++) {
fwrite(&data[i].first, sizeof(short), 1, fp);
fwrite(&data[i].second, sizeof(short), 1, fp);
}
fclose(fp);
}
#pragma once
//#define _CRT_SECURE_NO_DEPRECATE
#include <vector>
#include <utility>
#include <string>
#include <stdexcept>
#include <iostream>
#include <stdio.h>
#include <cstring>
class Audio {
public:
// Constructors
Audio(std::string str);
Audio() {};
// Read / Write files
void load_wav(std::string str);
void write_wav(std::string str);
// Get functions
// Subchunk2Size = NumSamples * NumChannels * BitsPerSample / 8 <==> NumSamples = Subchunk2Size / (NumChannels*(BitsPerSample / 8))
unsigned get_size() { return data.size(); }
unsigned get_sample_rate() { return SampleRate; }
unsigned get_n_channels() { return NumChannels; }
// Set Functions
void set_sample_rate(int n) { SampleRate = n; }
void set_n_channels(int n) { if (n != 1 && n != 2)throw std::invalid_argument("n can only be 1 (MONO) or 2 (STEREO)!"); NumChannels = n; }
// Overloaded Operators
std::pair<short, short> &operator[](unsigned i) { return data[i]; }
protected:
char type[5];
char format[5];
char Subchunk1ID[5];
char Subchunk2ID[5];
int ChunkSize;
int Subchunk1Size;
int SampleRate;
int ByteRate;
int Subchunk2Size;
short AudioFormat;
short NumChannels;
short BlockAlign;
short BitsPerSample;
// utility
unsigned NumSamples;
std::vector<std::pair<short, short> > data;
};
@janhuang6
Copy link

In Audio.h file line 31:

void set_n_channels(int n) { if (n != 1 || n != 2)throw std::invalid_argument("n can only be 1 (MONO) or 2 (STEREO)!"); NumChannels = n; }

That logic statement (n != 1 || n != 2) is always true for any n. As a result, it always throws.
Change that to be (n != 1 && n != 2) should fix it.

Please apply the fix to this file in github.

@janhuang6
Copy link

Audio.cpp doesn't work. This problem was found when I let it read a 720KB wav file. It throws at this code:

	if (strcmp(Subchunk2ID, "data"))
		throw std::runtime_error("Missing data header!");

Debug found the read-in Subchunk2ID is "LIST", not "data" expected by the code.
I commented out above 2 lines and it could complete the 2 functions load_wav() and write_wav().
But the output audio file is only 68 bytes and got error when trying to play that audio file.
Original 720KB input audio file can be played with no problem.

@PanagiotisPtr
Copy link
Author

Thanks for reporting those issues. I have fixed the issue with set_n_channels. However, I don't plan to expand this gist any further. It's just some example code - it's not meant to be a library. For a more stable implementation, I would suggest using an audio library for C++.

@marriusco
Copy link

	memset(format,0,sizeof(format));
	memset(Subchunk1ID,0,sizeof(Subchunk1ID));
	memset(Subchunk2ID,0,sizeof(Subchunk2ID));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment