diff options
author | Richard <q@1bpm.net> | 2025-03-08 14:53:52 +0000 |
---|---|---|
committer | Richard <q@1bpm.net> | 2025-03-08 14:53:52 +0000 |
commit | d8baa01ff91521e113260ef5d5cae272e02162e2 (patch) | |
tree | 6b118c71c308d29e517bda60bfbd69f7c4f39cbb | |
download | libguttersynth-d8baa01ff91521e113260ef5d5cae272e02162e2.tar.gz libguttersynth-d8baa01ff91521e113260ef5d5cae272e02162e2.tar.bz2 libguttersynth-d8baa01ff91521e113260ef5d5cae272e02162e2.zip |
initial
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 65 | ||||
-rw-r--r-- | README.md | 45 | ||||
-rw-r--r-- | cmake/Modules/FindPortaudio.cmake | 106 | ||||
-rwxr-xr-x | examples/build | 5 | ||||
-rw-r--r-- | examples/example1.c | 226 | ||||
-rw-r--r-- | examples/example2.cpp | 120 | ||||
-rw-r--r-- | examples/example3.c | 218 | ||||
-rw-r--r-- | include/guttersynth.h | 139 | ||||
-rw-r--r-- | include/guttersynth.hpp | 140 | ||||
-rw-r--r-- | src/guttersynth.c | 476 |
11 files changed, 1542 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0d451f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +nbproject/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..98a9baf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 2.8.12) +project(guttersynth) + + +# User options +option(USE_FLOAT "Set to use single-precision floating point." OFF) +option(CREATE_STATIC "Set to create the static library in addition to shared library." ON) +option(BUILD_EXAMPLES "Set to build the example programs." ON) + + +set(BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") + +if(USE_FLOAT) + message(STATUS "Building with 32-bit floats") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_FLOAT") +else() + message(STATUS "Building with 64-bit floats") +endif() + +include_directories("include") + +set(CSOURCES "src/guttersynth.c") + +set(PUBLIC_HEADERS "include/guttersynth.h" "include/guttersynth.hpp") +add_library(guttersynth SHARED ${CSOURCES}) +target_link_libraries(guttersynth m) +set_target_properties(guttersynth PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") + +install(TARGETS guttersynth + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin + PUBLIC_HEADER DESTINATION include) + +if (CREATE_STATIC) + add_library(guttersynthstatic STATIC ${CSOURCES}) + target_link_libraries(guttersynthstatic m) + install(TARGETS guttersynthstatic + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) +endif() + +if (BUILD_EXAMPLES) + add_executable(guttersynthexample1 "examples/example1.c") + target_link_libraries(guttersynthexample1 LINK_PUBLIC guttersynth) + + add_executable(guttersynthexample2 "examples/example2.cpp") + target_link_libraries(guttersynthexample2 LINK_PUBLIC guttersynth) + + find_package(Portaudio) + if (PORTAUDIO_FOUND) + message(STATUS "Portaudio found, example 3 building") + add_executable(guttersynthexample3 "examples/example3.c") + target_include_directories(guttersynthexample3 PUBLIC ${PORTAUDIO_INCLUDE_DIRS}) + target_link_libraries(guttersynthexample3 LINK_PUBLIC ${PORTAUDIO_LIBRARIES}) + target_link_libraries(guttersynthexample3 LINK_PUBLIC guttersynth) + else() + message(STATUS "Portaudio not found, example 3 not building") + endif() +endif() + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..7db0fdf --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# libguttersynth +This is a C port of Tom Mudd's Gutter Synth originally for Max/MSP. + + +## Requirements +* Cmake >= 2.8.12 +* Portaudio >= 2.0 for realtime example (example 3) + +Tested on Linux with Portaudio 2.0 version 19. + +## Installation +Create a build directory at the top of the source tree, execute *cmake ..*, +*make* and optionally *make install* as root. If the latter is not +used/possible then the resulting library can be used from the build directory. +eg: + + mkdir build && cd build + cmake .. + make && sudo make install + +The following options can be provided to cmake. + +* USE_FLOAT : use single-precision floats instead of double precision. Default=OFF +* CREATE_STATIC : create a static library in addition to shared. Default=ON +* BUILD_EXAMPLES : build the examples in addition to library. Default=ON + +These are set via the -D flag and should be ON or OFF. For example to build +with floats instead of doubles, cmake would be called with + + cmake -DUSE_FLOAT=ON + + +## Examples + +### Example 1 +Interpolates randomised parameters over 20 seconds to produce a wave file +which has different content upon each run. + +### Example 2 +Demonstrates the CPP API to create a 30 second wave file in which the filters +are randomised every second. + +### Example 3 +Utilises Portaudio (if available) to demonstrate randomised output in a +similar fashion to example 1, but with realtime audio output. diff --git a/cmake/Modules/FindPortaudio.cmake b/cmake/Modules/FindPortaudio.cmake new file mode 100644 index 0000000..fbc46e2 --- /dev/null +++ b/cmake/Modules/FindPortaudio.cmake @@ -0,0 +1,106 @@ +# - Try to find Portaudio +# Once done this will define +# +# PORTAUDIO_FOUND - system has Portaudio +# PORTAUDIO_INCLUDE_DIRS - the Portaudio include directory +# PORTAUDIO_LIBRARIES - Link these to use Portaudio +# PORTAUDIO_DEFINITIONS - Compiler switches required for using Portaudio +# PORTAUDIO_VERSION - Portaudio version +# +# Copyright (c) 2006 Andreas Schneider <[email protected]> +# +# Redistribution and use is allowed according to the terms of the New BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +if (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) + # in cache already + set(PORTAUDIO_FOUND TRUE) +else (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS) + if (NOT WIN32) + include(FindPkgConfig) + pkg_check_modules(PORTAUDIO2 portaudio-2.0) + endif (NOT WIN32) + + if (PORTAUDIO2_FOUND) + set(PORTAUDIO_INCLUDE_DIRS + ${PORTAUDIO2_INCLUDE_DIRS} + ) + if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(PORTAUDIO_LIBRARIES "${PORTAUDIO2_LIBRARY_DIRS}/lib${PORTAUDIO2_LIBRARIES}.dylib") + else (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(PORTAUDIO_LIBRARIES + ${PORTAUDIO2_LIBRARIES} + ) + endif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(PORTAUDIO_VERSION + 19 + ) + set(PORTAUDIO_FOUND TRUE) + else (PORTAUDIO2_FOUND) + find_path(PORTAUDIO_INCLUDE_DIR + NAMES + portaudio.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + + find_library(PORTAUDIO_LIBRARY + NAMES + portaudio + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + + find_path(PORTAUDIO_LIBRARY_DIR + NAMES + portaudio + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + + set(PORTAUDIO_INCLUDE_DIRS + ${PORTAUDIO_INCLUDE_DIR} + ) + set(PORTAUDIO_LIBRARIES + ${PORTAUDIO_LIBRARY} + ) + + set(PORTAUDIO_LIBRARY_DIRS + ${PORTAUDIO_LIBRARY_DIR} + ) + + set(PORTAUDIO_VERSION + 18 + ) + + if (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES) + set(PORTAUDIO_FOUND TRUE) + endif (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES) + + if (PORTAUDIO_FOUND) + if (NOT Portaudio_FIND_QUIETLY) + message(STATUS "Found Portaudio: ${PORTAUDIO_LIBRARIES}") + endif (NOT Portaudio_FIND_QUIETLY) + else (PORTAUDIO_FOUND) + if (Portaudio_FIND_REQUIRED) + message(FATAL_ERROR "Could not find Portaudio") + endif (Portaudio_FIND_REQUIRED) + endif (PORTAUDIO_FOUND) + endif (PORTAUDIO2_FOUND) + + + # show the PORTAUDIO_INCLUDE_DIRS and PORTAUDIO_LIBRARIES variables only in the advanced view + mark_as_advanced(PORTAUDIO_INCLUDE_DIRS PORTAUDIO_LIBRARIES) + +endif (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS)
\ No newline at end of file diff --git a/examples/build b/examples/build new file mode 100755 index 0000000..d5d5ece --- /dev/null +++ b/examples/build @@ -0,0 +1,5 @@ +# these will be built automatically by cmake, so usually no need to run this + +gcc -lguttersynth example1.c -oexample1 +g++ -lguttersynth example2.cpp -oexample2 +gcc -lguttersynth -lportaudio example3.c -oexample3 diff --git a/examples/example1.c b/examples/example1.c new file mode 100644 index 0000000..f827ca7 --- /dev/null +++ b/examples/example1.c @@ -0,0 +1,226 @@ +#include <stdio.h> +#include <stdlib.h> +#include <float.h> +#include <time.h> +#include <limits.h> +#include <string.h> +#include <math.h> +#include "guttersynth.h" + +int samples_per_second = 44100; +int bits_per_sample = 16; +char* output_name = "guttersynth-example1.wav"; + +int written_samples = 0; + +struct wavfile_header { + char riff_tag[4]; + int riff_length; + char wave_tag[4]; + char fmt_tag[4]; + int fmt_length; + short audio_format; + short num_channels; + int sample_rate; + int byte_rate; + short block_align; + short bits_per_sample; + char data_tag[4]; + int data_length; +}; + + +FILE* target; + +/* + * open wavfile and set the riff header + */ +FILE* wavfile_open() +{ + // populate the header + struct wavfile_header header; + strncpy(header.riff_tag, "RIFF",4); + strncpy(header.wave_tag, "WAVE",4); + strncpy(header.fmt_tag, "fmt ",4); + strncpy(header.data_tag, "data",4); + header.riff_length = 0; + header.fmt_length = 16; + header.audio_format = 1; + header.num_channels = 1; + header.sample_rate = samples_per_second; + header.byte_rate = samples_per_second * (bits_per_sample / 8); + header.block_align = bits_per_sample/8; + header.bits_per_sample = bits_per_sample; + header.data_length = 0; + + FILE *file = fopen(output_name, "w+"); + if(!file) return 0; + fwrite(&header, sizeof(header), 1, file); + fflush(file); + + return file; +} + + +/* + * write to wavfile + */ +void wavfile_write(double* data, int length) +{ + short* shortdata = (short*) malloc(sizeof(short) * length); + int i; + double sample; + for (i = 0; i < length; i++) { + sample = data[i] * 0.5; /// (DBL_MAX*0.01); + shortdata[i] = (short) (sample * SHRT_MAX * 0.5); + } + fwrite(shortdata, sizeof(short), length, target); + fflush(target); + written_samples += length; + free(shortdata); +} + + +/* + * close the open wavfile + */ +void wavfile_close() +{ + int file_length = ftell(target); + int data_length = file_length - sizeof(struct wavfile_header); + fseek(target, sizeof(struct wavfile_header) - sizeof(int), SEEK_SET); + fwrite(&data_length, sizeof(data_length), 1, target); + int riff_length = file_length - 8; + fseek(target, 4, SEEK_SET); + fwrite(&riff_length, sizeof(riff_length), 1, target); + fclose(target); +} + +/* + * Returns a random float within a range + * + * min: bottom end of range + * max: top end of range + * + * returns: random float + */ +double rangerandom(double min, double max) +{ + double random = ((double) rand()) / (double) RAND_MAX; + double diff = max - min; + double r = random * diff; + return min + r; +} + +int boolrandom() +{ + return (rand() > (RAND_MAX / 2)); +} + +double interpolate(double items[], int points, int point) +{ + return (((items[1] - items[0]) / points) * point) + items[0]; +} + +void create() +{ + target = wavfile_open(); + int seconds = 20; + int total_samples = samples_per_second * seconds; + int sample = 0; + int buffer_pos = 0; + int buffer_size = 4410; + double *buffer = (double*) malloc(sizeof(double) * buffer_size); + + gutter_state *gs = gutter_init(4, 24, samples_per_second); + //gutter_randomisefilters(gs); + + double* filterAutomation = malloc(sizeof(double) * gs->bankCount * gs->filterCount * 3 * 2); + + int b, f, i, ai; + double val; + for (b = 0, ai=0; b < gs->bankCount; b++) { + for (f = 0; f < gs->filterCount; f++) { + i = b * gs->bankCount + f; + val = rangerandom(100, 2000); + filterAutomation[ai++] = val; + filterAutomation[ai++] = (boolrandom()) ? val : rangerandom(100, 2000); + + val = rangerandom(0.3, 1); + filterAutomation[ai++] = val; + filterAutomation[ai++] = (boolrandom()) ? val : rangerandom(0.3, 1); + + val = rangerandom(20, 200); + filterAutomation[ai++] = val; + filterAutomation[ai++] = (boolrandom()) ? val : rangerandom(20, 200); + } + } + + + + double gamma[2] = { rangerandom(2, 10), rangerandom(2, 10) }; + double omega[2] = { rangerandom(0.2, 1), rangerandom(0.2, 1) }; + double c[2] = { rangerandom(0.2, 2), rangerandom(0.2, 2) }; + double dt[2] = { rangerandom(100, 10000), rangerandom(100, 10000) }; + double singleGain[2] = { rangerandom(0.2, 0.7), rangerandom(0.2, 0.7) }; + + + gs->filtersOn = 1; + gs->smoothing = 1; + gs->distortionMethod = 2; + + gs->omega = rangerandom(0.3, 0.9); + gs->c = rangerandom(0.5, 0.9); + gs->dt = rangerandom(100, 500); + + gutter_randomisefilters(gs); + + for (sample = 0; sample <= total_samples; sample ++) { + gs->gamma = interpolate(gamma, total_samples, sample); + //gs->omega = 0.8; //interpolate(omega, total_samples, sample); + //gs->c = 0.8; //interpolate(c, total_samples, sample); + //gs->dt = 300; //interpolate(dt, total_samples, sample); + gs->singleGain = 0.8; //interpolate(singleGain, total_samples, sample); + + + for (b = 0, ai = 0; b < gs->bankCount; b++) { + for (f = 0; f < gs->filterCount; f++, ai=ai+6) { + i = b * gs->bankCount + f; + gs->filterFreqs[i] = interpolate(filterAutomation + ai, total_samples, sample); + gs->gains[i] = interpolate(filterAutomation + ai + 2, total_samples, sample); + gs->Q[i] = interpolate(filterAutomation + ai + 4, total_samples, sample); + } + } + gutter_calccoeffs(gs); + + buffer[buffer_pos] = gutter_process(gs); + if (buffer_pos == buffer_size - 1) { + buffer_pos = 0; + wavfile_write(buffer, buffer_size); + } else { + buffer_pos ++; + } + + // randomise filters every second + if (sample % samples_per_second == 0) { + //gutter_randomisefilters(gs); + } + } + + free(buffer); + free(filterAutomation); + gutter_cleanup(gs); + wavfile_close(); +} + + +int main() +{ + // random seed from time + srand((unsigned int) time(NULL)); + + // generate the file + create(); + + return 0; +} diff --git a/examples/example2.cpp b/examples/example2.cpp new file mode 100644 index 0000000..415c1ff --- /dev/null +++ b/examples/example2.cpp @@ -0,0 +1,120 @@ +#include <cstdlib> +#include <fstream> +#include <iostream> +#include "guttersynth.hpp" + +using namespace std; + + +class Wavefile { +private: + std::ofstream output; + size_t data_chunk_pos; + + template <typename Word> + void write_word(Word value, unsigned size=sizeof(Word)) { + for (; size; --size, value >>= 8) + output.put( static_cast <char> (value & 0xFF) ); + } + +public: + Wavefile(string path, int samplerate) : output(path.c_str(), ios::binary) { + + int bitdepth = 16; + // Write the file headers + output << "RIFF----WAVEfmt "; // (chunk size to be filled in later) + write_word(16, 4); // no extension data + write_word(1, 2); // PCM - integer samples + write_word(1, 2); // one channel (mono file) + write_word(samplerate, 4); // samples per second (Hz) + write_word(samplerate * (bitdepth / 8), 4); // byte rate + write_word(2, 2); // data block size ; was bitdepth / 8 + write_word(bitdepth, 2); // number of bits per sample (use a multiple of 8) + + data_chunk_pos = output.tellp(); + output << "data----"; // (chunk size to be filled in later) + } + + ~Wavefile() { + std::cout << "closing\n"; + size_t file_length = output.tellp(); + + // Fix the data chunk header to contain the data size + output.seekp(data_chunk_pos + 4); + write_word(file_length - (data_chunk_pos + 8), 4); + + // Fix the file header to contain the proper RIFF chunk size, which is (file size - 8) bytes + output.seekp(0 + 4); + write_word(file_length - 8, 4); + output.close(); + } + + void write(double value) { + //https://www.cplusplus.com/forum/beginner/166954/ + write_word((short) (value * SHRT_MAX * 0.5), 2); + } + + +}; + + +class Example2 { +private: + GutterSynth* gs; + Wavefile* wav; + int samplerate; + +public: + + Example2(std::string path, int samplerate) { + this->samplerate = samplerate; + gs = new GutterSynth(2, 24, samplerate); + wav = new Wavefile(path, samplerate); + } + + double rangerandom(double min, double max) { + double random = ((double) rand()) / (double) RAND_MAX; + double diff = max - min; + double r = random * diff; + return min + r; + } + + void run() { + int total_samples = 30 * samplerate; + int sample = 0; + short data; + gs->randomiseFilters(); + gs->filtersOn(true); + gs->smoothing(true); + gs->distortionMethod(2); + + for (int sample = 0; sample < total_samples; sample ++) { + data = gs->process(); + if (data > 0) { + std::cout << data; + } + wav->write(data); + + if (sample % samplerate == 0) { // randomise every second + gs->randomiseFilters(); + gs->gamma(rangerandom(2, 10)); + gs->omega(rangerandom(0.3, 0.9)); + gs->c(rangerandom(0.5, 0.9)); + gs->dt(rangerandom(100, 500)); + gs->singleGain(rangerandom(0.2, 0.7)); + } + } + delete wav; + } +}; + + +/* + * + */ +int main(int argc, char** argv) { + Example2* e = new Example2("guttersynth-example2.wav", 44100); + e->run(); + return 0; +} + diff --git a/examples/example3.c b/examples/example3.c new file mode 100644 index 0000000..ce2d116 --- /dev/null +++ b/examples/example3.c @@ -0,0 +1,218 @@ +/* + * Example 3: randomised realtime output using Portaudio + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <limits.h> +#include <string.h> +#include <math.h> +#include "guttersynth.h" +#include "portaudio.h" + +#define NUM_SECONDS 60 +#define SAMPLE_RATE 44100 +#define FRAMES_PER_BUFFER 128 + + + +typedef struct { + gutter_state *gs; + long sample; + long total_samples; + double gamma[2]; + double omega[2]; + double c[2]; + double dt[2]; + double singleGain[2]; + double* filterAutomation; +} playstate; + + + +/* + * Returns a random float within a range + * + * min: bottom end of range + * max: top end of range + * + * returns: random float + */ +double rangerandom(double min, double max) +{ + double random = ((double) rand()) / (double) RAND_MAX; + double diff = max - min; + double r = random * diff; + return min + r; +} + + + + +double interpolate(double* items, int points, int point) +{ + return (((items[1] - items[0]) / points) * point) + items[0]; +} + + + +void rerandom(playstate* ps) { + ps->gamma[0] = rangerandom(0.1, 10); + ps->gamma[1] = rangerandom(0.1, 10); + ps->omega[0] = rangerandom(0.01, 2); + ps->omega[1] = rangerandom(0.01, 2); + ps->c[0] = rangerandom(0.2, 2); + ps->c[1] = rangerandom(0.2, 2); + ps->dt[0] = rangerandom(400, 4000); + ps->dt[1] = rangerandom(400, 4000); + ps->singleGain[0] = rangerandom(0.2, 0.3); + ps->singleGain[1] = rangerandom(0.2, 0.3); +} + +static int audiocallback(const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + + unsigned long i, sample; + float audioSample; + float *out = (float*)outputBuffer; + playstate *ps = (playstate*) userData; + + gutter_state *gs = ps->gs; + + for (i = 0; i < framesPerBuffer; i++, ps->sample++) { + if (ps->sample % SAMPLE_RATE == 0) { + //gutter_randomisefilters(gs); + //rerandom(ps); + } + + int b, f, ix, ai; + for (b = 0, ai = 0; b < gs->bankCount; b++) { + for (f = 0; f < gs->filterCount; f++, ai=ai+6) { + ix = b * gs->bankCount + f; + gs->filterFreqs[ix] = interpolate(ps->filterAutomation + ai, ps->total_samples, ps->sample); + gs->gains[ix] = interpolate(ps->filterAutomation + ai + 2, ps->total_samples, ps->sample); + gs->Q[ix] = interpolate(ps->filterAutomation + ai + 4, ps->total_samples, ps->sample); + } + } + gutter_calccoeffs(gs); + + gs->gamma = 7; //interpolate(ps->gamma, ps->total_samples, ps->sample); + gs->omega = 1.2; //interpolate(ps->omega, ps->total_samples, ps->sample); + gs->c = 0.5; //interpolate(ps->c, ps->total_samples, ps->sample); + gs->dt = 500; //interpolate(ps->dt, ps->total_samples, ps->sample); + gs->singleGain = 1; //interpolate(ps->singleGain, ps->total_samples, ps->sample); + + audioSample = (float) gutter_process(gs); + *out++ = audioSample * 0.2; + *out++ = audioSample * 0.2; + } + //ps->sample = ps->sample + framesPerBuffer; + (void) timeInfo; /* Prevent unused variable warnings. */ + (void) statusFlags; + (void) inputBuffer; + + return paContinue; +} + + + + +int main() +{ + // random seed from time + srand((unsigned int) time(NULL)); + + + playstate ps; + ps.sample = 0; + ps.total_samples = SAMPLE_RATE * NUM_SECONDS; + ps.gs = gutter_init(2, 32, SAMPLE_RATE); + ps.gs->filtersOn = 1; + ps.gs->smoothing = 1; + ps.gs->distortionMethod = 2; + + rerandom(&ps); + + + ps.filterAutomation = malloc(sizeof(double) * ps.gs->bankCount * ps.gs->filterCount * 3 * 2); + int b, f, i, ai; + for (b = 0, ai=0; b < ps.gs->bankCount; b++) { + for (f = 0; f < ps.gs->filterCount; f++) { + i = b * ps.gs->bankCount + f; + ps.filterAutomation[ai++] = rangerandom(100, 2000); + ps.filterAutomation[ai++] = rangerandom(100, 2000); + ps.filterAutomation[ai++] = rangerandom(0.6, 1); + ps.filterAutomation[ai++] = rangerandom(0.6, 1); + ps.filterAutomation[ai++] = rangerandom(20, 200); + ps.filterAutomation[ai++] = rangerandom(20, 200); + } + } + + gutter_randomisefilters(ps.gs); + + PaStreamParameters outputParameters; + PaStream *stream; + PaError err; + + err = Pa_Initialize(); + if (err != paNoError) { + goto error; + } + + outputParameters.device = Pa_GetDefaultOutputDevice(); + if (outputParameters.device == paNoDevice) { + fprintf(stderr,"Error: No default output device.\n"); + goto error; + } + + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + audiocallback, + &ps); + + if (err != paNoError) goto error; + + //err = Pa_SetStreamFinishedCallback(stream, &StreamFinished); + //if(err != paNoError) goto error; + + err = Pa_StartStream(stream); + if(err != paNoError) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS); + Pa_Sleep(NUM_SECONDS * 1000); + + err = Pa_StopStream( stream ); + if(err != paNoError) goto error; + + err = Pa_CloseStream(stream); + if(err != paNoError) goto error; + + Pa_Terminate(); + printf("Example finished.\n"); + + free(ps.filterAutomation); + + return err; + +error: + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/include/guttersynth.h b/include/guttersynth.h new file mode 100644 index 0000000..88d874a --- /dev/null +++ b/include/guttersynth.h @@ -0,0 +1,139 @@ +/* + * guttersynth.h + * Part of libguttersynth + * + * Copyright Tom Mudd 2019, Richard Knight 2021, 2022 + * + * Ported to C by Richard Knight + * from https://github.com/tommmmudd/guttersynthesis by Tom Mudd + * + */ + +#ifndef GUTTER_H +#define GUTTER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <stdlib.h> + +// support for either single or double precision +#ifndef FLT + +#ifdef USE_FLOAT +#define FLT float +#else +#define FLT double +#endif + +#endif + + +// internal/private dcblock state variables +typedef struct dcblock_session_ { + FLT xm1; + FLT ym1; + FLT r; +} dcblock_session; + +// internal/private state variables +typedef struct gutter_internal_ { + void (*dealloc)(void*); + int samplerate; + FLT finalY; + FLT duffX; + FLT duffY; + FLT dx; + FLT dy; + FLT t; + FLT* prevX1; + FLT* prevX2; + FLT* prevY1; + FLT* prevY2; + FLT* V; + FLT* K; + FLT* norm; + FLT* a0; + FLT* a1; + FLT* a2; + FLT* b1; + FLT* b2; + FLT* y; + dcblock_session dcblock; +} gutter_internal; + +// state with public variables +typedef struct gutter_state_ { + int bankCount; + int filterCount; + + FLT gamma; + FLT omega; + FLT c; + FLT dt; + FLT singleGain; + FLT* gains; + FLT* filterFreqs; + FLT* Q; + int enableAudioInput; + int filtersOn; + int smoothing; + int distortionMethod; + gutter_internal gi; +} gutter_state; + + +// synthesise single sample with input +FLT gutter_process_input(gutter_state* s, FLT audioInput); + +// synthesise single sample +FLT gutter_process(gutter_state* s); + +// synthesise specified number of samples with input +void gutter_process_input_samples(gutter_state* s, FLT* audioInput, FLT* audioOutput, int nsamps); + +// synthesise specified number of samples +void gutter_process_samples(gutter_state* s, FLT* audioOutput, int nsamps); + +// reset state +void gutter_reset(gutter_state* s); + +// clean up memory etc +void gutter_cleanup(gutter_state* s); + +// calculate filter coefficients +void gutter_calccoeffs(gutter_state* s); + +// initialise with custom memory allocator and deallocator +gutter_state* gutter_init_ca(int bankCount, int filterCount, int samplerate, + void* (*allocator)(size_t), void (*deallocator)(void*)); + +// initialise with standard malloc and free +gutter_state* gutter_init(int bankCount, int filterCount, int samplerate); + +// randomise filters +void gutter_randomisefilters(gutter_state* s); + +// set distortion method +int gutter_setdistortionmethod(gutter_state* s, int method); + +// set frequency of a filter in a bank +int gutter_setfreq(gutter_state*, int bank, int filter, FLT value); + +// set Q of a filter in a bank +int gutter_setq(gutter_state* s, int bank, int filter, FLT value); + +// set gain of a filter in a bank +int gutter_setgain(gutter_state* s, int bank, int filter, FLT value); + +// set Q of all filters +void gutter_setqall(gutter_state* s, FLT value); + +#ifdef __cplusplus +} +#endif + +#endif /* GUTTER_H */ + diff --git a/include/guttersynth.hpp b/include/guttersynth.hpp new file mode 100644 index 0000000..7b961f1 --- /dev/null +++ b/include/guttersynth.hpp @@ -0,0 +1,140 @@ +/* + * Thin C++ wrapper around the C library + */ +#ifndef GUTTERSYNTH_HPP +#define GUTTERSYNTH_HPP + +#include "guttersynth.h" +#include <stdexcept> +#include <limits.h> + +class GutterSynth { +protected: + gutter_state* s; +public: + + GutterSynth(int bankCount, int filterCount, int samplerate) { + s = gutter_init(bankCount, filterCount, samplerate); + } + + GutterSynth(int bankCount, int filterCount, int samplerate, + void* (*allocator)(size_t), void (*deallocator)(void*) + ) { + s = gutter_init_ca(bankCount, filterCount, samplerate, + allocator, deallocator); + } + + ~GutterSynth() { + gutter_cleanup(s); + } + + // simple accessors + FLT gamma() { return s->gamma; } + void gamma(FLT value) { s->gamma = value; } + + FLT omega() { return s->omega; } + void omega(FLT value) { s->omega = value; } + + FLT c() { return s->c; } + void c(FLT value) { s->c = value; } + + FLT dt() { return s->dt; } + void dt(FLT value) { s->dt = value; } + + FLT singleGain() { return s->singleGain; } + void singleGain(FLT value) { s->singleGain = value; } + + int filterCount() { return s->filterCount; } + int bankCount() { return s->bankCount; } + + bool enableAudioInput() { return bool(s->enableAudioInput); } + void enableAudioInput(bool value) { s->enableAudioInput = int(value); } + + bool filtersOn() { return bool(s->filtersOn); } + void filtersOn(bool value) { s->filtersOn = int(value); } + + int smoothing() { return s->smoothing; } + void smoothing(int smoothing) { s->smoothing = smoothing; } + + int distortionMethod() { return s->distortionMethod; } + void distortionMethod(int method) { + int result = gutter_setdistortionmethod(s, method); + if (result != 0) { + throw std::invalid_argument("Filter out of range"); + } + } + + void randomiseFilters() { + gutter_randomisefilters(s); + } + + void calcCoeffs() { + gutter_calccoeffs(s); + } + + void setFreq(int bank, int filter, FLT value, bool recalculate=true) { + int result = gutter_setfreq(s, bank, filter, value); + if (result != 0) { + throw std::invalid_argument("Filter out of range"); + } + + if (recalculate) { + calcCoeffs(); + } + } + + void setQ(int bank, int filter, FLT value, bool recalculate=true) { + int result = gutter_setq(s, bank, filter, value); + if (result != 0) { + throw std::invalid_argument("Filter out of range"); + } + + if (recalculate) { + calcCoeffs(); + } + } + + void setQ(FLT value, bool recalculate=true) { + gutter_setqall(s, value); + + if (recalculate) { + calcCoeffs(); + } + } + + void setGain(int bank, int filter, FLT value, bool recalculate=true) { + int result = gutter_setgain(s, bank, filter, value); + if (result != 0) { + throw std::invalid_argument("Filter out of range"); + } + + if (recalculate) { + calcCoeffs(); + } + } + + void reset() { + gutter_reset(s); + } + + // process audio members + FLT process() { + return gutter_process(s); + } + + FLT process(FLT audioInput) { + return gutter_process_input(s, audioInput); + } + + void process(FLT* audioOutput, int nsamps) { + gutter_process_samples(s, audioOutput, nsamps); + } + + void process(FLT* audioInput, FLT* audioOutput, int nsamps) { + gutter_process_input_samples(s, audioInput, audioOutput, nsamps); + } +}; + + +#endif /* GUTTERSYNTH_HPP */ + diff --git a/src/guttersynth.c b/src/guttersynth.c new file mode 100644 index 0000000..7e1607a --- /dev/null +++ b/src/guttersynth.c @@ -0,0 +1,476 @@ +/* + * guttersynth.c + * Part of libguttersynth + * + * Copyright Tom Mudd 2019, Richard Knight 2021, 2022 + * + * Ported to C by Richard Knight + * from https://github.com/tommmmudd/guttersynthesis by Tom Mudd + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <math.h> +#include <sys/param.h> +#include "guttersynth.h" + + +// TODO: freqs temp and Q temp, remove, not needed.. +// + +/* + * Returns a random float within a range + * + * min: bottom end of range + * max: top end of range + * + * returns: random float + */ +static FLT rangerandom(FLT min, FLT max) +{ + FLT random = ((FLT) rand()) / (FLT) RAND_MAX; + FLT diff = max - min; + FLT r = random * diff; + return min + r; +} + + +/* + * Calculate filter coefficients + * + * s: gutter_state struct to apply operation to + */ +void gutter_calccoeffs(gutter_state* s) +{ + int i, b, f; + for (b = 0; b < s->bankCount; b++) { + for (f = 0; f < s->filterCount; f++) { + i = b * s->bankCount + f; + s->gi.V[i] = (FLT) pow(10.0, 0.05); + s->gi.K[i] = (FLT) tan(((FLT)M_PI * s->filterFreqs[i]) / s->gi.samplerate); + s->gi.norm[i] = (1.0 / (1.0 + s->gi.K[i] / s->Q[i] + s->gi.K[i] * s->gi.K[i])); + s->gi.a0[i] = s->gi.K[i] / s->Q[i] * s->gi.norm[i]; + s->gi.a1[i] = 0.0; + s->gi.a2[i] = - s->gi.a0[i]; + s->gi.b1[i] = 2.0 * (s->gi.K[i] * s->gi.K[i] - 1.0) * s->gi.norm[i]; + s->gi.b2[i] = (1.0 - s->gi.K[i] / s->Q[i] + s->gi.K[i] * s->gi.K[i]) *s->gi.norm[i]; + } + } +} + + +/* + * Apply distortion + */ +static void gutter_distortion(gutter_state* s, FLT* audio_ptr) +{ + FLT input = *audio_ptr; + FLT output; + switch (s->distortionMethod) { + case 0: + output = MAX(MIN(input, 1), -1); + break; + case 1: + if (s->gi.finalY <= -1) { + output = -0.666666667; + } else if (input <= 1) { + output = input - input * input * input / 3.0; + } else { + output = 0.666666667; + } + break; + case 2: + output = (FLT) atan(input); + break; + case 3: + output = 0.75 * (((FLT)sqrt(input * 1.3 * (input * 1.3) + 1.0)) * 1.65 - 1.65) / input; + break; + case 4: + output = (0.1076 * input * input * input + 3.029 * input) / (input * input + 3.124); + break; + case 5: + output = 2.0 / (1.0 + ((FLT)exp(-1.0 * input))); + break; + default: + output = input; + } + *audio_ptr = output; +} + + +/* + * Randomise filter bank frequencies + * + * s: gutter_state struct to apply operation to + */ +void gutter_randomisefilters(gutter_state* s) +{ + int i, b, f; + for (b = 0; b < s->bankCount; b++) { + for (f = 0; f < s->filterCount; f++) { + i = b * s->bankCount + f; + s->filterFreqs[i] = rangerandom(100, 4000); + s->gains[i] = rangerandom(0, 1); + s->Q[i] = rangerandom(20, 200); + } + } + gutter_calccoeffs(s); +} + + +int gutter_setdistortionmethod(gutter_state* s, int method) +{ + if (method < 0 || method > 5) { + return -1; + } + s->distortionMethod = method; + return 0; +} + +/* + * Set frequency of a filter in a bank + * + * s: gutter_state struct to apply operation to + * bank: bank index + * filter: filter index + * value: frequency to set + * + * returns: 0 on success, -1 if bank or filter is out of range + */ +int gutter_setfreq(gutter_state* s, int bank, int filter, FLT value) +{ + if (bank >= s->bankCount || filter >= s->filterCount || bank < 0 || filter < 0) { + return -1; + } + s->filterFreqs[bank*s->bankCount+filter] = value; + return 0; +} + + +/* + * Set Q of a filter in a bank + * + * s: gutter_state struct to apply operation to + * bank: bank index + * filter: filter index + * value: Q to set + * + * returns: 0 on success, -1 if bank or filter is out of range + */ +int gutter_setq(gutter_state* s, int bank, int filter, FLT value) +{ + if (bank >= s->bankCount || filter >= s->filterCount || bank < 0 || filter < 0) { + return -1; + } + s->Q[bank*s->bankCount+filter] = value; + return 0; +} + + +/* + * Set gain of a filter in a bank + * + * s: gutter_state struct to apply operation to + * bank: bank index + * filter: filter index + * value: gain to set + * + * returns: 0 on success, -1 if bank or filter is out of range + */ +int gutter_setgain(gutter_state* s, int bank, int filter, FLT value) +{ + if (bank >= s->bankCount || filter >= s->filterCount || bank < 0 || filter < 0) { + return -1; + } + s->gains[bank*s->bankCount+filter] = value; + return 0; +} + + +/* + * Set the Q of all filters in all banks + * + * s: gutter_state struct to apply operation to + * value: Q to set + */ +void gutter_setqall(gutter_state* s, FLT value) +{ + int i; + for (i = 0; i < s->bankCount * s->filterCount; i++) { + s->Q[i] = value; + } +} + +int gutter_setqbank(gutter_state* s, int bank, FLT value) +{ + if (bank < 0 || bank <= s->bankCount) { + return -1; + } + int f; + for (f = 0; f < s->filterCount; f++) { + s->Q[bank * s->bankCount + f] = value; + } +} + +/* + * Initialise with custom memory allocator and deallocator + * + * bankCount: number of banks (typically 1 or 2) + * filterCount: number of filters (typically up to 24) + * samplerate: samplerate to calculate audio at + * allocator: memory allocator function taking a size_t and returning void* + * deallocator: memory deallocator function taking a void* + * + * returns: gutter_state struct to be used in the synthesis session + */ +gutter_state* gutter_init_ca(int bankCount, int filterCount, int samplerate, + void* (*allocator)(size_t), void (*deallocator)(void*)) +{ + int size2d, i, f, b; + + srand(time(0)); + + gutter_state* s = (gutter_state*) allocator(sizeof(gutter_state)); + + s->gi.samplerate = samplerate; + s->bankCount = bankCount; + s->filterCount = filterCount; + s->gi.dealloc = deallocator; + + + s->gi.dcblock.xm1 = 0; + s->gi.dcblock.ym1 = 0; + s->gi.dcblock.r = 0.995; + + + size2d = sizeof(FLT) * bankCount * filterCount; + s->gains = (FLT*) allocator(size2d); + s->Q = (FLT*) allocator(size2d); + s->filterFreqs = (FLT*) allocator(size2d); + s->gi.prevX1 = (FLT*) allocator(size2d); + s->gi.prevX2 = (FLT*) allocator(size2d); + s->gi.prevY1 = (FLT*) allocator(size2d); + s->gi.prevY2 = (FLT*) allocator(size2d); + s->gi.V = (FLT*) allocator(size2d); + s->gi.K = (FLT*) allocator(size2d); + s->gi.norm = (FLT*) allocator(size2d); + s->gi.a0 = (FLT*) allocator(size2d); + s->gi.a1 = (FLT*) allocator(size2d); + s->gi.a2 = (FLT*) allocator(size2d); + s->gi.b1 = (FLT*) allocator(size2d); + s->gi.b2 = (FLT*) allocator(size2d); + s->gi.y = (FLT*) allocator(size2d); + + s->filtersOn = 1; + s->smoothing = 1; + s->distortionMethod = 2; + s->enableAudioInput = 0; + + for (b = 0; b < bankCount; b++) { + for (f = 0; f < filterCount; f++) { + i = b * bankCount + f; + FLT freq = ((FLT)(f + 1)) / 2.0 * 20.0 * (b + 1) * 1.2 + 80; + s->filterFreqs[i] = freq; + s->gi.y[i] = 0; + s->gi.prevX1[i] = 0; + s->gi.prevX2[i] = 0; + s->gi.prevY1[i] = 0; + s->gi.prevY2[i] = 0; + s->gains[i] = 1; + s->Q[i] = 1; + } + } + + gutter_randomisefilters(s); + + return s; +} + + +/* + * Initialise with default memory allocator and deallocator (malloc and free) + * + * bankCount: number of banks (typically 1 or 2) + * filterCount: number of filters (typically up to 24) + * samplerate: samplerate to calculate audio at + * + * returns: gutter_state struct to be used in the synthesis session + */ +gutter_state* gutter_init(int bankCount, int filterCount, int samplerate) +{ + return gutter_init_ca(bankCount, filterCount, samplerate, &malloc, &free); +} + + +/* + * Clean up state: free memory + * + * s: gutter_state struct to apply operation to + */ +void gutter_cleanup(gutter_state* s) +{ + void (*da)(void*) = s->gi.dealloc; + + da(s->gains); + da(s->filterFreqs); + da(s->gi.prevX1); + da(s->gi.prevX2); + da(s->gi.prevY1); + da(s->gi.prevY2); + da(s->gi.V); + da(s->gi.K); + da(s->gi.norm); + da(s->gi.a0); + da(s->gi.a1); + da(s->gi.a2); + da(s->gi.b1); + da(s->gi.b2); + da(s->gi.y); + da(s->Q); + da(s); +} + + +/* + * Reset oscillator + * + * s: gutter_state struct to apply operation to + */ +void gutter_reset(gutter_state* s) +{ + s->gi.duffX = 0; + s->gi.duffY = 0; + s->gi.dx = 0; + s->gi.dy = 0; + s->gi.t = 0; +} + + +/* + * Apply DC blocking to audio referencing current state + * + * s: gutter_state struct to apply operation to + * input: audio to apply DC block to + * + * returns: DC blocked audio + */ +static FLT dcblock(gutter_state* s, FLT input) { + FLT y = input - s->gi.dcblock.xm1 + s->gi.dcblock.r * s->gi.dcblock.ym1; + s->gi.dcblock.xm1 = input; + s->gi.dcblock.ym1 = y; + return y; +} + +/* + * Synthesise one sample with audio input + * + * s: gutter_state struct to apply operation to + * audioInput: audio sample to feed into synthesis + * + * returns: synthesised audio sample + */ +FLT gutter_process_input(gutter_state* s, FLT audioInput) +{ + if (s->filtersOn) { + int i, b, f; + for (b = 0; b < s->bankCount; b++) { + for (f = 0; f < s->filterCount; f++) { + i = b * s->bankCount + f; + s->gi.y[i] = s->gi.a0[i] * s->gi.duffX + + s->gi.a1[i] * s->gi.prevX1[i] + + s->gi.a2[i] * s->gi.prevX2[i] + - s->gi.b1[i] * s->gi.prevY1[i] + - s->gi.b2[i] * s->gi.prevY2[i]; + s->gi.prevX2[i] = s->gi.prevX1[i]; + s->gi.prevX1[i] = s->gi.duffX; + s->gi.prevY2[i] = s->gi.prevY1[i]; + s->gi.prevY1[i] = s->gi.y[i]; + s->gi.finalY += s->gi.y[i] * s->gains[i] * s->singleGain; + } + } + } else { + s->gi.finalY = s->gi.duffX; + } + + if (s->enableAudioInput) { + s->gi.dy = (s->gi.finalY - s->gi.finalY * s->gi.finalY * s->gi.finalY + - s->c * s->gi.duffY + s->gamma * audioInput); + } else { + s->gi.dy = (s->gi.finalY - s->gi.finalY * s->gi.finalY * s->gi.finalY + - s->c * s->gi.duffY + s->gamma * ((FLT)sin(s->omega * s->gi.t))); + + } + + s->gi.duffY += s->gi.dy; + s->gi.dx = s->gi.duffY; + + FLT out; + s->gi.duffX = ((s->gi.finalY + s->gi.dx) - s->gi.duffX) / s->smoothing; // low pass filter + //s->gi.finalY = dcblock(s, s->gi.finalY); // RK: experimental + if (s->filtersOn) { + gutter_distortion(s, &(s->gi.duffX)); + out = s->gi.finalY * 0.125; + } else { + s->gi.duffX = MAX(MIN(s->gi.duffX, 100), -100); + if (abs(s->gi.duffX) > 99) { + gutter_reset(s); + } + out = MAX(MIN(s->gi.duffX * s->singleGain, 1.0), -1.0); + } + + s->gi.t += s->dt; + + if (isnan(s->gi.duffX)) { + gutter_reset(s); + } + + //return out; + return dcblock(s, out); +} + + +/* + * Synthesise specified number of samples with audio input + * + * s: gutter_state struct to apply operation to + * audioInput: audio sample pointer to feed into synthesis, must be at least nsamps size + * audioOutput: audio output pointer, must be at least nsamps size + * nsamps: number of samples to synthesise + */ +void gutter_process_input_samples(gutter_state* s, FLT* audioInput, FLT* audioOutput, int nsamps) +{ + int i; + for (i = 0; i < nsamps; i++) { + audioOutput[i] = gutter_process_input(s, audioInput[i]); + } +} + + +/* + * Synthesise one sample + * + * s: gutter_state struct to apply operation to + * + * returns: synthesised audio sample + */ +FLT gutter_process(gutter_state* s) +{ + return gutter_process_input(s, 0); +} + + +/* + * Synthesise specified number of samples + * + * s: gutter_state struct to apply operation to + * audioOutput: audio output pointer, must be at least nsamps size + * nsamps: number of samples to synthesise + */ +void gutter_process_samples(gutter_state* s, FLT* audioOutput, int nsamps) +{ + int i; + for (i = 0; i < nsamps; i++) { + audioOutput[i] = gutter_process_input(s, 0); + } +}
\ No newline at end of file |