Last active
August 18, 2024 23:19
-
-
Save PanagiotisPtr/bacbc4e4cbe9eabd32a540f6ef8ffae6 to your computer and use it in GitHub Desktop.
WAV File Reader / Writer 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
#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); | |
} |
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 | |
//#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; | |
}; |
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.
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++.
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
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.