From 12ef243815d409e5f4039502f378d43ecdf7abaa Mon Sep 17 00:00:00 2001 From: gramanas Date: Thu, 14 Sep 2023 13:57:53 +0300 Subject: Add archive code --- src/archive/tinywav.c | 271 ++++++++++++++++++++++++++++++++++++++++++++++++ src/archive/tinywav.h | 131 +++++++++++++++++++++++ src/archive/wavetable.c | 182 ++++++++++++++++++++++++++++++++ src/archive/wavetable.h | 24 +++++ 4 files changed, 608 insertions(+) create mode 100644 src/archive/tinywav.c create mode 100644 src/archive/tinywav.h create mode 100644 src/archive/wavetable.c create mode 100644 src/archive/wavetable.h (limited to 'src/archive') diff --git a/src/archive/tinywav.c b/src/archive/tinywav.c new file mode 100644 index 0000000..7d7f43c --- /dev/null +++ b/src/archive/tinywav.c @@ -0,0 +1,271 @@ +/** + * Copyright (c) 2015-2022, Martin Roth (mhroth@gmail.com) + * + * 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. + */ + + + +#include +#include +#if _WIN32 +#include +#include +#pragma comment(lib, "Ws2_32.lib") +#else +#include +#include +#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; +} + +int tinywav_open_read(TinyWav *tw, const char *path, TinyWavChannelFormat chanFmt) { + 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 " + + // skip over any other chunks before the "data" chunk + bool additionalHeaderDataPresent = false; + while (tw->h.Subchunk2ID != htonl(0x64617461)) { // "data" + fseek(tw->f, 4, SEEK_CUR); + fread(&tw->h.Subchunk2ID, 4, 1, tw->f); + additionalHeaderDataPresent = true; + } + assert(tw->h.Subchunk2ID == htonl(0x64617461)); // "data" + if (additionalHeaderDataPresent) { + // read the value of Subchunk2Size, the one populated when reading 'TinyWavHeader' structure is wrong + fread(&tw->h.Subchunk2Size, 4, 1, tw->f); + } + + 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 interleaved 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); +} diff --git a/src/archive/tinywav.h b/src/archive/tinywav.h new file mode 100644 index 0000000..3abbd65 --- /dev/null +++ b/src/archive/tinywav.h @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2015-2022, Martin Roth (mhroth@gmail.com) + * + * 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. + */ + + + +#ifndef _TINY_WAV_ +#define _TINY_WAV_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// http://soundfile.sapp.org/doc/WaveFormat/ + +typedef struct TinyWavHeader { + uint32_t ChunkID; + uint32_t ChunkSize; + uint32_t Format; + uint32_t Subchunk1ID; + uint32_t Subchunk1Size; + uint16_t AudioFormat; + uint16_t NumChannels; + uint32_t SampleRate; + uint32_t ByteRate; + uint16_t BlockAlign; + uint16_t BitsPerSample; + uint32_t Subchunk2ID; + uint32_t Subchunk2Size; +} TinyWavHeader; + +typedef enum TinyWavChannelFormat { + TW_INTERLEAVED, // channel buffer is interleaved e.g. [LRLRLRLR] + TW_INLINE, // channel buffer is inlined e.g. [LLLLRRRR] + TW_SPLIT // channel buffer is split e.g. [[LLLL],[RRRR]] +} TinyWavChannelFormat; + +typedef enum TinyWavSampleFormat { + TW_INT16 = 2, // two byte signed integer + TW_FLOAT32 = 4 // four byte IEEE float +} TinyWavSampleFormat; + +typedef struct TinyWav { + FILE *f; + TinyWavHeader h; + int16_t numChannels; + int32_t numFramesInHeader; ///< number of samples per channel declared in wav header (only populated when reading) + uint32_t totalFramesReadWritten; ///< total numSamples per channel which have been read or written + TinyWavChannelFormat chanFmt; + TinyWavSampleFormat sampFmt; +} TinyWav; + +/** + * Open a file for writing. + * + * @param numChannels The number of channels to write. + * @param samplerate The sample rate of the audio. + * @param sampFmt The sample format (e.g. 16-bit integer or 32-bit float) to be used in the file. + * @param chanFmt The channel format (how the channel data is layed out in memory) + * @param path The path of the file to write to. The file will be overwritten. + * + * @return The error code. Zero if no error. + */ +int tinywav_open_write(TinyWav *tw, + int16_t numChannels, int32_t samplerate, + TinyWavSampleFormat sampFmt, TinyWavChannelFormat chanFmt, + const char *path); + +/** + * Open a file for reading. + * + * @param path The path of the file to read. + * @param chanFmt The channel format (how the channel data is layed out in memory) when read. + * + * @return The error code. Zero if no error. + */ +int tinywav_open_read(TinyWav *tw, const char *path, TinyWavChannelFormat chanFmt); + +/** + * Read sample data from the file. + * + * @param tw The TinyWav structure which has already been prepared. + * @param data A pointer to the data structure to read to. This data is expected to have the + * correct memory layout to match the specifications given in tinywav_open_read(). + * @param len The number of frames to read. + * + * @return The number of frames (samples per channel) read from file. + */ +int tinywav_read_f(TinyWav *tw, void *data, int len); + +/** Stop reading the file. The Tinywav struct is now invalid. */ +void tinywav_close_read(TinyWav *tw); + +/** + * Write sample data to file. + * + * @param tw The TinyWav structure which has already been prepared. + * @param f A pointer to the sample data to write. + * @param len The number of frames to write. + * + * @return The number of frames (samples per channel) written to file. + */ +int tinywav_write_f(TinyWav *tw, void *f, int len); + +/** Stop writing to the file. The Tinywav struct is now invalid. */ +void tinywav_close_write(TinyWav *tw); + +/** Returns true if the Tinywav struct is available to write or write. False otherwise. */ +bool tinywav_isOpen(TinyWav *tw); + +#ifdef __cplusplus +} +#endif + +#endif // _TINY_WAV_ diff --git a/src/archive/wavetable.c b/src/archive/wavetable.c new file mode 100644 index 0000000..ab3308a --- /dev/null +++ b/src/archive/wavetable.c @@ -0,0 +1,182 @@ +#include "wavetable.h" +#include + +static wave_t wvt_sine_data; +static wave_t wvt_saw_data; +static wave_t wvt_digisaw_data; +static wave_t wvt_tri_data; +static wave_t wvt_sqr_data; +static wave_t wvt_sound_data; + + +void +wvt_sine_init(size_t size) +{ + wvt_sine_data.size = size; + wvt_sine_data.data = (float *) malloc(sizeof(float) * size); + for (int i = 0; i < size; i++) { + wvt_sine_data.data[i] = sinf(2 * M_PI * i / size); + } +} + +void +wvt_saw_init(size_t size) +{ + wvt_saw_data.size = size; + wvt_saw_data.data = (float *) malloc(sizeof(float) * size); + for (int i = 0; i < size; i++) { + float dOutput = 0.0; + for (float n = 1.0; n < 25; n++) + dOutput += sinf(n * 2.0 * M_PI * i / size) / n; + wvt_saw_data.data[i] = 0.6 * dOutput; + } +} + +void +wvt_digisaw_init(size_t size) +{ + wvt_digisaw_data.size = size; + wvt_digisaw_data.data = (float *) malloc(sizeof(float) * size); + for (int i = 0; i < size; i++) { + wvt_digisaw_data.data[i] = (float)i / size; + } +} + +void +wvt_sqr_init(size_t size) +{ + wvt_sqr_data.size = size; + wvt_sqr_data.data = (float *) malloc(sizeof(float) * size); + for (int i = 0; i < size; i++) { + if (i < size / 2) { + wvt_sqr_data.data[i] = -1.0f; + } else { + wvt_sqr_data.data[i] = 1.0f; + } + } +} + +void +wvt_sound_init(char * path) +{ + SNDFILE* file; + SF_INFO fileInfo; + int numSamplesRead; + + // Open the WAV file + file = sf_open(path, SFM_READ, &fileInfo); + if (!file) { + printf("Failed to open the WAV file: %s\n", sf_strerror(NULL)); + return; + } + printf("frames: %ld sr: %d chan: %d format: %d sections: %d seekable: %d\n", fileInfo.frames, fileInfo.samplerate, fileInfo.channels, fileInfo.format, fileInfo.sections, fileInfo.seekable); + // Ensure the WAV file has only one channel + if (fileInfo.channels != 1) { + printf("Only mono WAV files are supported.\n"); + sf_close(file); + return; + } + + wvt_sound_data.size = fileInfo.frames ; + wvt_sound_data.data = (float *) malloc(sizeof(float) * fileInfo.frames); + + // Read the WAV file into the buffer + numSamplesRead = sf_readf_float(file, wvt_sound_data.data, fileInfo.frames); + + float max = -1000; + float min = 1000; + for (int i = 0; i < numSamplesRead; i++) { + float s = wvt_sound_data.data[i]; + // printf("Sample %d: %f\n", i, s); + if (s < min) min = s; + if (s > max) max = s; + } + printf("Min: %f Max: %f\n", min, max); + + // Close the WAV file + sf_close(file); +} + +void +wvt_init() +{ + wvt_sine_init(44100); + wvt_saw_init(128); + wvt_digisaw_init(48000); + wvt_sqr_init(48000); + wvt_tri_data.size = 2; + wvt_tri_data.data = (float *) malloc(sizeof(float) * 2); + wvt_tri_data.data[0] = -1.0f; + wvt_tri_data.data[1] = 1.0f; + wvt_sound_init("/home/gramanas/code/synth-project/waves/test1.wav"); +} + + +float +wvt_gen(wave_t * wave, float * index, float freq, int sample_rate) +{ + if (*index >= wave->size) *index = 0; + + int current_index = (int)*index; + int next_index = current_index + 1 >= wave->size ? 0 : current_index + 1; + + // linear interpolation between current_index and next_index + float m = (wave->data[next_index] - wave->data[current_index]) / (1 - 0); + float b = wave->data[current_index] /* + m * 0 */; + + // the value to pick + float x = *index - current_index; + + float sample = m * x + b; + float step = freq * ((float)wave->size / sample_rate); + + *index += step; + if (*index >= wave->size) { + *index -= wave->size; + } + + return sample; +} + +float +wvt_next(float (* wvt_fun)(float *, float, int), float freq, int sample_rate, float * wvt_index) +{ + float sample = wvt_fun(wvt_index, freq, sample_rate); + return sample; +} + +float +wvt_saw(float * index, float freq, int sample_rate) +{ + return wvt_gen(&wvt_saw_data, index, freq, sample_rate); +} + +float +wvt_digisaw(float * index, float freq, int sample_rate) +{ + return wvt_gen(&wvt_digisaw_data, index, freq, sample_rate); +} + +float +wvt_sine(float * index, float freq, int sample_rate) +{ + return wvt_gen(&wvt_sine_data, index, freq, sample_rate); +} + +float +wvt_tri(float * index, float freq, int sample_rate) +{ + return wvt_gen(&wvt_tri_data, index, freq, sample_rate); +} + +float +wvt_sqr(float * index, float freq, int sample_rate) +{ + return wvt_gen(&wvt_sqr_data, index, freq, sample_rate); +} + +float +wvt_sound(float * index, float freq, int sample_rate) +{ + return wvt_gen(&wvt_sound_data, index, freq, sample_rate); +} diff --git a/src/archive/wavetable.h b/src/archive/wavetable.h new file mode 100644 index 0000000..46174b4 --- /dev/null +++ b/src/archive/wavetable.h @@ -0,0 +1,24 @@ +#ifndef WAVETABLE_H +#define WAVETABLE_H + +#include "synth_engine.h" + +#define WVT_MAX 44100 + +typedef struct wave_t { + float * data; + size_t size; +} wave_t; + +float wvt_sine(float *, float, int); +float wvt_saw(float *, float, int); +float wvt_digisaw(float *, float, int); +float wvt_tri(float *, float, int); +float wvt_sqr(float *, float, int); +float wvt_sound(float *, float, int); + +float wvt_next(float (* wvt_fun)(float *, float, int), float freq, int sample_rate, float * wvt_index); + +void wvt_init(); + +#endif /* WAVETABLE_H */ -- cgit v1.2.3