Created
February 5, 2023 17:37
-
-
Save greed9/a349cc1abfee9fd5341850ce53f7016d to your computer and use it in GitHub Desktop.
Implementation of simple WAV file read/write
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
/** | |
* Copyright (c) 2015-2022, Martin Roth ([email protected]) | |
* | |
* Permission to use, copy, modify, and/or distribute this software for any | |
* purpose with or without fee is hereby granted, provided that the above | |
* copyright notice and this permission notice appear in all copies. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | |
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | |
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | |
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | |
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | |
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | |
* PERFORMANCE OF THIS SOFTWARE. | |
*/ | |
// Heavily based on and slightly modified from: https://github.com/mhroth/tinywav | |
#include <assert.h> | |
#include <string.h> | |
#if _WIN32 | |
#include <winsock.h> | |
#include <malloc.h> | |
#pragma comment(lib, "Ws2_32.lib") | |
#else | |
#include <alloca.h> | |
#include <netinet/in.h> | |
#endif | |
#include "tinywav.h" | |
int tinywav_open_write(TinyWav *tw, | |
int16_t numChannels, int32_t samplerate, | |
TinyWavSampleFormat sampFmt, TinyWavChannelFormat chanFmt, | |
const char *path) | |
{ | |
#if _WIN32 | |
errno_t err = fopen_s(&tw->f, path, "w"); | |
assert(err == 0); | |
#else | |
tw->f = fopen(path, "w"); | |
#endif | |
assert(tw->f != NULL); | |
tw->numChannels = numChannels; | |
tw->numFramesInHeader = -1; // not used for writer | |
tw->totalFramesReadWritten = 0; | |
tw->sampFmt = sampFmt; | |
tw->chanFmt = chanFmt; | |
// prepare WAV header | |
TinyWavHeader h; | |
h.ChunkID = htonl(0x52494646); // "RIFF" | |
h.ChunkSize = 0; // fill this in on file-close | |
h.Format = htonl(0x57415645); // "WAVE" | |
h.Subchunk1ID = htonl(0x666d7420); // "fmt " | |
h.Subchunk1Size = 16; // PCM | |
h.AudioFormat = (tw->sampFmt - 1); // 1 PCM, 3 IEEE float | |
h.NumChannels = numChannels; | |
h.SampleRate = samplerate; | |
h.ByteRate = samplerate * numChannels * tw->sampFmt; | |
h.BlockAlign = numChannels * tw->sampFmt; | |
h.BitsPerSample = 8 * tw->sampFmt; | |
h.Subchunk2ID = htonl(0x64617461); // "data" | |
h.Subchunk2Size = 0; // fill this in on file-close | |
// write WAV header | |
fwrite(&h, sizeof(TinyWavHeader), 1, tw->f); | |
return 0; | |
} | |
uint32_t findDataChunk(uint8_t *start, uint8_t tag[4], TinyWav* tw) | |
{ | |
int tooMany = 0; | |
// tag we are seeking | |
fwrite(tag, sizeof(char), 4, stdout); // print the tag | |
printf("\n"); | |
// current tag | |
fwrite(start, sizeof(char), 4, stdout); // print the tag | |
printf("\n"); | |
// search for the tag using TLV algo | |
uint32_t skip = 0; | |
start += 4; | |
skip = (unsigned int)*start; | |
fseek(tw->f, skip, SEEK_CUR); // skip those bytes | |
fread(tag, 4, 1, tw->f); // read the next tag | |
fwrite(tag, sizeof(char), 4, stdout); // print the tag | |
printf("\n"); | |
uint32_t dataSize = 0 ; | |
//fseek(fp, 4, SEEK_CUR) ; | |
fread( &dataSize, 4, 1, tw->f ) ; | |
return dataSize; | |
} | |
int tinywav_open_read(TinyWav *tw, const char *path, TinyWavChannelFormat chanFmt) | |
{ | |
uint8_t tag_sought[] = { | |
'd', | |
'a', | |
't', | |
'a'}; | |
tw->f = fopen(path, "rb"); | |
assert(tw->f != NULL); | |
size_t ret = fread(&tw->h, sizeof(TinyWavHeader), 1, tw->f); | |
assert(ret > 0); | |
assert(tw->h.ChunkID == htonl(0x52494646)); // "RIFF" | |
assert(tw->h.Format == htonl(0x57415645)); // "WAVE" | |
assert(tw->h.Subchunk1ID == htonl(0x666d7420)); // "fmt " | |
tw->h.Subchunk2Size = findDataChunk((void *)&tw->h.Subchunk2ID, tag_sought, tw); | |
tw->numChannels = tw->h.NumChannels; | |
tw->chanFmt = chanFmt; | |
if (tw->h.BitsPerSample == 32 && tw->h.AudioFormat == 3) | |
{ | |
tw->sampFmt = TW_FLOAT32; // file has 32-bit IEEE float samples | |
} | |
else if (tw->h.BitsPerSample == 16 && tw->h.AudioFormat == 1) | |
{ | |
tw->sampFmt = TW_INT16; // file has 16-bit int samples | |
} | |
else | |
{ | |
tw->sampFmt = TW_FLOAT32; | |
printf("Warning: wav file has %d bits per sample (int), which is not natively supported yet. Treating them as float; you may want to convert them manually after reading.\n", tw->h.BitsPerSample); | |
} | |
tw->numFramesInHeader = tw->h.Subchunk2Size / (tw->numChannels * tw->sampFmt); | |
tw->totalFramesReadWritten = 0; | |
return 0; | |
} | |
int tinywav_read_f(TinyWav *tw, void *data, int len) | |
{ | |
switch (tw->sampFmt) | |
{ | |
case TW_INT16: | |
{ | |
int16_t *interleaved_data = (int16_t *)alloca(tw->numChannels * len * sizeof(int16_t)); | |
size_t samples_read = fread(interleaved_data, sizeof(int16_t), tw->numChannels * len, tw->f); | |
int valid_len = (int)samples_read / tw->numChannels; | |
switch (tw->chanFmt) | |
{ | |
case TW_INTERLEAVED: | |
{ // channel buffer is interlyeaved e.g. [LRLRLRLR] | |
for (int pos = 0; pos < tw->numChannels * valid_len; pos++) | |
{ | |
((float *)data)[pos] = (float)interleaved_data[pos] / INT16_MAX; | |
} | |
return valid_len; | |
} | |
case TW_INLINE: | |
{ // channel buffer is inlined e.g. [LLLLRRRR] | |
for (int i = 0, pos = 0; i < tw->numChannels; i++) | |
{ | |
for (int j = i; j < valid_len * tw->numChannels; j += tw->numChannels, ++pos) | |
{ | |
((float *)data)[pos] = (float)interleaved_data[j] / INT16_MAX; | |
} | |
} | |
return valid_len; | |
} | |
case TW_SPLIT: | |
{ // channel buffer is split e.g. [[LLLL],[RRRR]] | |
for (int i = 0, pos = 0; i < tw->numChannels; i++) | |
{ | |
for (int j = 0; j < valid_len; j++, ++pos) | |
{ | |
((float **)data)[i][j] = (float)interleaved_data[j * tw->numChannels + i] / INT16_MAX; | |
} | |
} | |
return valid_len; | |
} | |
default: | |
return 0; | |
} | |
} | |
case TW_FLOAT32: | |
{ | |
float *interleaved_data = (float *)alloca(tw->numChannels * len * sizeof(float)); | |
size_t samples_read = fread(interleaved_data, sizeof(float), tw->numChannels * len, tw->f); | |
int valid_len = (int)samples_read / tw->numChannels; | |
switch (tw->chanFmt) | |
{ | |
case TW_INTERLEAVED: | |
{ // channel buffer is interleaved e.g. [LRLRLRLR] | |
memcpy(data, interleaved_data, tw->numChannels * valid_len * sizeof(float)); | |
return valid_len; | |
} | |
case TW_INLINE: | |
{ // channel buffer is inlined e.g. [LLLLRRRR] | |
for (int i = 0, pos = 0; i < tw->numChannels; i++) | |
{ | |
for (int j = i; j < valid_len * tw->numChannels; j += tw->numChannels, ++pos) | |
{ | |
((float *)data)[pos] = interleaved_data[j]; | |
} | |
} | |
return valid_len; | |
} | |
case TW_SPLIT: | |
{ // channel buffer is split e.g. [[LLLL],[RRRR]] | |
for (int i = 0, pos = 0; i < tw->numChannels; i++) | |
{ | |
for (int j = 0; j < valid_len; j++, ++pos) | |
{ | |
((float **)data)[i][j] = interleaved_data[j * tw->numChannels + i]; | |
} | |
} | |
return valid_len; | |
} | |
default: | |
return 0; | |
} | |
} | |
default: | |
return 0; | |
} | |
return len; | |
} | |
void tinywav_close_read(TinyWav *tw) | |
{ | |
fclose(tw->f); | |
tw->f = NULL; | |
} | |
int tinywav_write_f(TinyWav *tw, void *f, int len) | |
{ | |
switch (tw->sampFmt) | |
{ | |
case TW_INT16: | |
{ | |
int16_t *z = (int16_t *)alloca(tw->numChannels * len * sizeof(int16_t)); | |
switch (tw->chanFmt) | |
{ | |
case TW_INTERLEAVED: | |
{ | |
const float *const x = (const float *const)f; | |
for (int i = 0; i < tw->numChannels * len; ++i) | |
{ | |
z[i] = (int16_t)(x[i] * (float)INT16_MAX); | |
} | |
break; | |
} | |
case TW_INLINE: | |
{ | |
const float *const x = (const float *const)f; | |
for (int i = 0, k = 0; i < len; ++i) | |
{ | |
for (int j = 0; j < tw->numChannels; ++j) | |
{ | |
z[k++] = (int16_t)(x[j * len + i] * (float)INT16_MAX); | |
} | |
} | |
break; | |
} | |
case TW_SPLIT: | |
{ | |
const float **const x = (const float **const)f; | |
for (int i = 0, k = 0; i < len; ++i) | |
{ | |
for (int j = 0; j < tw->numChannels; ++j) | |
{ | |
z[k++] = (int16_t)(x[j][i] * (float)INT16_MAX); | |
} | |
} | |
break; | |
} | |
default: | |
return 0; | |
} | |
tw->totalFramesReadWritten += len; | |
size_t samples_written = fwrite(z, sizeof(int16_t), tw->numChannels * len, tw->f); | |
return (int)samples_written / tw->numChannels; | |
} | |
case TW_FLOAT32: | |
{ | |
float *z = (float *)alloca(tw->numChannels * len * sizeof(float)); | |
switch (tw->chanFmt) | |
{ | |
case TW_INTERLEAVED: | |
{ | |
tw->totalFramesReadWritten += len; | |
return (int)fwrite(f, sizeof(float), tw->numChannels * len, tw->f); | |
} | |
case TW_INLINE: | |
{ | |
const float *const x = (const float *const)f; | |
for (int i = 0, k = 0; i < len; ++i) | |
{ | |
for (int j = 0; j < tw->numChannels; ++j) | |
{ | |
z[k++] = x[j * len + i]; | |
} | |
} | |
break; | |
} | |
case TW_SPLIT: | |
{ | |
const float **const x = (const float **const)f; | |
for (int i = 0, k = 0; i < len; ++i) | |
{ | |
for (int j = 0; j < tw->numChannels; ++j) | |
{ | |
z[k++] = x[j][i]; | |
} | |
} | |
break; | |
} | |
default: | |
return 0; | |
} | |
tw->totalFramesReadWritten += len; | |
size_t samples_written = fwrite(z, sizeof(float), tw->numChannels * len, tw->f); | |
return (int)samples_written / tw->numChannels; | |
} | |
default: | |
return 0; | |
} | |
} | |
void tinywav_close_write(TinyWav *tw) | |
{ | |
uint32_t data_len = tw->totalFramesReadWritten * tw->numChannels * tw->sampFmt; | |
// set length of data | |
fseek(tw->f, 4, SEEK_SET); | |
uint32_t chunkSize_len = 36 + data_len; | |
fwrite(&chunkSize_len, sizeof(uint32_t), 1, tw->f); | |
fseek(tw->f, 40, SEEK_SET); | |
fwrite(&data_len, sizeof(uint32_t), 1, tw->f); | |
fclose(tw->f); | |
tw->f = NULL; | |
} | |
bool tinywav_isOpen(TinyWav *tw) | |
{ | |
return (tw->f != NULL); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment