From f1d35f23e2cdfbcfcf36efcecd9d414951628c90 Mon Sep 17 00:00:00 2001 From: Andrew Aday Date: Tue, 5 Nov 2024 14:53:44 -0800 Subject: [PATCH] convrev build for webchuck, default fft blocksize to 512 on web. removes threading if compiled with emscripten --- ConvRev/.vscode/c_cpp_properties.json | 18 ++ ConvRev/.vscode/launch.json | 13 ++ ConvRev/.vscode/settings.json | 59 +++++++ ConvRev/ConvRev.cpp | 233 +++++++++++++++----------- ConvRev/makefile | 10 +- 5 files changed, 233 insertions(+), 100 deletions(-) create mode 100644 ConvRev/.vscode/c_cpp_properties.json create mode 100644 ConvRev/.vscode/launch.json create mode 100644 ConvRev/.vscode/settings.json diff --git a/ConvRev/.vscode/c_cpp_properties.json b/ConvRev/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..f377a1c4 --- /dev/null +++ b/ConvRev/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "macos-clang-arm64", + "includePath": [ + "${workspaceFolder}/**" + ], + "compilerPath": "/usr/bin/clang", + "cStandard": "${default}", + "cppStandard": "c++17", + "intelliSenseMode": "macos-clang-arm64", + "compilerArgs": [ + "" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/ConvRev/.vscode/launch.json b/ConvRev/.vscode/launch.json new file mode 100644 index 00000000..0ba6fb16 --- /dev/null +++ b/ConvRev/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "C/C++ Runner: Debug Session", + "type": "lldb", + "request": "launch", + "args": [], + "cwd": "/Users/Andrew/Google-Drive/Stanford/chugins/ConvRev", + "program": "/Users/Andrew/Google-Drive/Stanford/chugins/ConvRev/build/Debug/outDebug" + } + ] +} \ No newline at end of file diff --git a/ConvRev/.vscode/settings.json b/ConvRev/.vscode/settings.json new file mode 100644 index 00000000..9e8efa3a --- /dev/null +++ b/ConvRev/.vscode/settings.json @@ -0,0 +1,59 @@ +{ + "C_Cpp_Runner.cCompilerPath": "clang", + "C_Cpp_Runner.cppCompilerPath": "clang++", + "C_Cpp_Runner.debuggerPath": "lldb", + "C_Cpp_Runner.cStandard": "", + "C_Cpp_Runner.cppStandard": "c++17", + "C_Cpp_Runner.msvcBatchPath": "", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.compilerArgs": [], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": ["*", "**/*"], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": true, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false, + "files.associations": { + "cstring": "cpp", + "thread": "cpp" + } +} diff --git a/ConvRev/ConvRev.cpp b/ConvRev/ConvRev.cpp index 89b25481..f6c7d76a 100644 --- a/ConvRev/ConvRev.cpp +++ b/ConvRev/ConvRev.cpp @@ -23,10 +23,18 @@ #include #include #include + +#if __EMSCRIPTEN__ #include +#endif + #include -#define CONV_REV_BLOCKSIZE 128 // default FFT blocksize +#ifdef __EMSCRIPTEN__ +#define CONV_REV_BLOCKSIZE 512 // default FFT blocksize +#else +#define CONV_REV_BLOCKSIZE 128 // default FFT blocksize +#endif CK_DLL_CTOR(convrev_ctor); CK_DLL_DTOR(convrev_dtor); @@ -44,7 +52,7 @@ CK_DLL_MFUN(convrev_setCoeff); CK_DLL_MFUN(convrev_getCoeff); // initialize convolution engine -CK_DLL_MFUN(convrev_init); +CK_DLL_MFUN(convrev_init); // load entire buffer at once (see fluidsynth for how to take in array arg) // CK_DLL_MFUN(convrev_setIRBuffer); @@ -58,10 +66,10 @@ t_CKINT convrev_data_offset = 0; class ConvRev { -private: // internal data - t_CKFLOAT _SR; // sample rate - t_CKINT _blocksize; // FFT blocksize - t_CKINT _order; // filter order +private: // internal data + t_CKFLOAT _SR; // sample rate + t_CKINT _blocksize; // FFT blocksize + t_CKINT _order; // filter order // internal buffers std::vector _ir_buffer; @@ -72,75 +80,93 @@ class ConvRev bool _which_input_buffer; // output double buffer - fftconvolver::Sample* _output_buffer; - fftconvolver::Sample* _staging_out_buffer; + fftconvolver::Sample *_output_buffer; + fftconvolver::Sample *_staging_out_buffer; bool _which_output_buffer; - fftconvolver::FFTConvolver _convolver; // convolution engine + fftconvolver::FFTConvolver _convolver; // convolution engine - size_t _idx; // to track head of circular input buffer + size_t _idx; // to track head of circular input buffer +#ifndef __EMSCRIPTEN__ // threading std::thread _conv_thr; +#endif // scale factor to normalize output float _scale_factor; public: - ConvRev( t_CKFLOAT fs ) - : _SR(fs), _blocksize(CONV_REV_BLOCKSIZE), _order(0), _convolver(), - _idx(0), _scale_factor(1.0f), _which_input_buffer(false), _which_output_buffer(false), - _output_buffer(nullptr), _staging_out_buffer(nullptr) - {} - - ~ConvRev() { - if (_conv_thr.joinable()) _conv_thr.join(); - if (_output_buffer) delete[] _output_buffer; - if (_staging_out_buffer) delete[] _staging_out_buffer; + ConvRev(t_CKFLOAT fs) + : _SR(fs), _blocksize(CONV_REV_BLOCKSIZE), _order(0), _convolver(), + _idx(0), _scale_factor(1.0f), _which_input_buffer(false), _which_output_buffer(false), + _output_buffer(nullptr), _staging_out_buffer(nullptr) + { + } + + ~ConvRev() + { +#ifndef __EMSCRIPTEN__ + if (_conv_thr.joinable()) + _conv_thr.join(); +#endif + if (_output_buffer) + delete[] _output_buffer; + if (_staging_out_buffer) + delete[] _staging_out_buffer; } // double buffer getters - std::vector& getInputBuffer() { + std::vector &getInputBuffer() + { return _which_input_buffer ? _input_buffer : _staging_in_buffer; } - std::vector& getStagingInputBuffer() { + std::vector &getStagingInputBuffer() + { return _which_input_buffer ? _staging_in_buffer : _input_buffer; } - fftconvolver::Sample* getOutputBuffer() { + fftconvolver::Sample *getOutputBuffer() + { return _which_output_buffer ? _output_buffer : _staging_out_buffer; } - fftconvolver::Sample* getStagingOutputBuffer() { + fftconvolver::Sample *getStagingOutputBuffer() + { return _which_output_buffer ? _staging_out_buffer : _output_buffer; } // buffer swappers - void swapBuffers() { + void swapBuffers() + { _which_input_buffer = !_which_input_buffer; _which_output_buffer = !_which_output_buffer; } // for Chugins extending UGen - SAMPLE tick( SAMPLE in ) + SAMPLE tick(SAMPLE in) { #ifdef CONV_REV_PROFILE Timer timer("tick", _blocksize); #endif getInputBuffer()[_idx] = in; - SAMPLE output = _scale_factor * (getOutputBuffer()[_idx]); + SAMPLE output = _scale_factor * (getOutputBuffer()[_idx]); // increment circular buffer head _idx++; - if (_idx == _blocksize) { + if (_idx == _blocksize) + { #ifdef CONV_REV_PROFILE - Timer timer("----tick at blocksize"); + Timer timer("----tick at blocksize"); #endif _idx = 0; // reset circular buffer head - // wait for convolution engine to finish processing next block - if (_conv_thr.joinable()) _conv_thr.join(); +// wait for convolution engine to finish processing next block +#ifndef __EMSCRIPTEN__ + if (_conv_thr.joinable()) + _conv_thr.join(); +#endif // swap input and output buffers swapBuffers(); @@ -150,24 +176,29 @@ class ConvRev // shockingly low... but the more complex the ugen graph, the more // we should see a benefit from puting the convolution engine on a // separate thread - _conv_thr = std::thread([&]() { - _process(getStagingInputBuffer(), getStagingOutputBuffer()); - }); + +#ifdef __EMSCRIPTEN__ + _process(getStagingInputBuffer(), getStagingOutputBuffer()); +#else + _conv_thr = std::thread([&]() + { _process(getStagingInputBuffer(), getStagingOutputBuffer()); }); +#endif } return output; } - void _process(std::vector& input_buffer, fftconvolver::Sample* output_buffer) { + void _process(std::vector &input_buffer, fftconvolver::Sample *output_buffer) + { // TODO process does not work with std::vector for output buffer. why?? #ifdef CONV_REV_PROFILE Timer timer("--------convolver.process()"); #endif - _convolver.process(input_buffer.data(), output_buffer, _blocksize); + _convolver.process(input_buffer.data(), output_buffer, _blocksize); } // set parameter example - t_CKFLOAT setBlockSize( t_CKFLOAT p ) + t_CKFLOAT setBlockSize(t_CKFLOAT p) { _blocksize = p; return p; @@ -176,35 +207,40 @@ class ConvRev // get parameter example t_CKFLOAT getBlockSize() { return _blocksize; } - void setOrder( t_CKINT m ) + void setOrder(t_CKINT m) { _order = m; _ir_buffer.resize(_order, 0); } - t_CKINT getOrder() { + t_CKINT getOrder() + { return _order; } - void setCoeff(t_CKINT idx, t_CKFLOAT val) { + void setCoeff(t_CKINT idx, t_CKFLOAT val) + { _ir_buffer[idx] = val; } t_CKFLOAT getCoeff(t_CKINT idx) { return _ir_buffer[idx]; } - t_CKVOID init() { + t_CKVOID init() + { // resize buffers and zero buffers _input_buffer.resize(_blocksize, 0); _staging_in_buffer.resize(_blocksize, 0); // free old buffer - if (_output_buffer) delete[] _output_buffer; - if (_staging_out_buffer) delete[] _staging_out_buffer; + if (_output_buffer) + delete[] _output_buffer; + if (_staging_out_buffer) + delete[] _staging_out_buffer; // allocate new buffer _output_buffer = new fftconvolver::Sample[_blocksize]; _staging_out_buffer = new fftconvolver::Sample[_blocksize]; // zero out new buffer - memset( _output_buffer, 0, _blocksize * sizeof(fftconvolver::Sample)); + memset(_output_buffer, 0, _blocksize * sizeof(fftconvolver::Sample)); memset(_staging_out_buffer, 0, _blocksize * sizeof(fftconvolver::Sample)); // initialize convolution engine @@ -212,47 +248,47 @@ class ConvRev // set normalization scale factor _scale_factor = _SR / _order; - if (_scale_factor > 1) { _scale_factor = 1; } + if (_scale_factor > 1) + { + _scale_factor = 1; + } } }; - -CK_DLL_QUERY( ConvRev ) +CK_DLL_QUERY(ConvRev) { - QUERY->setname( QUERY, "ConvRev" ); - + QUERY->setname(QUERY, "ConvRev"); + // begin the class definition - QUERY->begin_class( QUERY, "ConvRev", "UGen" ); + QUERY->begin_class(QUERY, "ConvRev", "UGen"); QUERY->doc_class(QUERY, "Convolution Reverb Chugin"); QUERY->add_ex(QUERY, "effects/ConvRev.ck"); - QUERY->add_ctor( QUERY, convrev_ctor ); - QUERY->add_dtor( QUERY, convrev_dtor ); + QUERY->add_ctor(QUERY, convrev_ctor); + QUERY->add_dtor(QUERY, convrev_dtor); // for UGens only: add tick function - QUERY->add_ugen_func( QUERY, convrev_tick, NULL, 1, 1 ); + QUERY->add_ugen_func(QUERY, convrev_tick, NULL, 1, 1); // NOTE: if this is to be a UGen with more than 1 channel, // e.g., a multichannel UGen -- will need to use add_ugen_funcf() // and declare a tickf function using CK_DLL_TICKF QUERY->add_mfun(QUERY, convrev_setBlockSize, "float", "blocksize"); QUERY->add_arg(QUERY, "float", "arg"); - QUERY->doc_func(QUERY, - "Set the blocksize of the FFT convolution engine. " - "Larger blocksize means more efficient processing, but more latency. " - "Latency is equal to blocksize / sample rate." - "Defaults to 128 samples." - ); + QUERY->doc_func(QUERY, + "Set the blocksize of the FFT convolution engine. " + "Larger blocksize means more efficient processing, but more latency. " + "Latency is equal to blocksize / sample rate." + "Defaults to 128 samples."); QUERY->add_mfun(QUERY, convrev_getBlockSize, "float", "blocksize"); QUERY->doc_func(QUERY, "Get the blocksize of the FFT convolution engine."); QUERY->add_mfun(QUERY, convrev_setOrder, "int", "order"); QUERY->add_arg(QUERY, "int", "arg"); - QUERY->doc_func(QUERY, - "Set the order of the convolution filter. " - "This should be set to the length of the impulse response buffer in samples" - ); + QUERY->doc_func(QUERY, + "Set the order of the convolution filter. " + "This should be set to the length of the impulse response buffer in samples"); QUERY->add_mfun(QUERY, convrev_getOrder, "int", "order"); QUERY->doc_func(QUERY, "Get the order of the convolution filter."); @@ -260,21 +296,18 @@ CK_DLL_QUERY( ConvRev ) QUERY->add_mfun(QUERY, convrev_setCoeff, "float", "coeff"); QUERY->add_arg(QUERY, "int", "index"); QUERY->add_arg(QUERY, "float", "coefficient"); - QUERY->doc_func(QUERY, - "Set the coefficient of the convolution filter at position . " - ); + QUERY->doc_func(QUERY, + "Set the coefficient of the convolution filter at position . "); QUERY->add_mfun(QUERY, convrev_getCoeff, "float", "coeff"); QUERY->add_arg(QUERY, "int", "index"); - QUERY->doc_func(QUERY, - "Get the coefficient of the convolution filter at position . " - ); + QUERY->doc_func(QUERY, + "Get the coefficient of the convolution filter at position . "); QUERY->add_mfun(QUERY, convrev_init, "void", "init"); - QUERY->doc_func(QUERY, - "Initialize the convolution engine. Performs memory allocations, pre-computes the IR FFT etc." - "This should be called after setting the order and coefficients of the filter, and before using the UGen." - ); + QUERY->doc_func(QUERY, + "Initialize the convolution engine. Performs memory allocations, pre-computes the IR FFT etc." + "This should be called after setting the order and coefficients of the filter, and before using the UGen."); // this reserves a variable in the ChucK internal class to store // reference to the c++ class we defined above @@ -292,16 +325,16 @@ CK_DLL_CTOR(convrev_ctor) OBJ_MEMBER_INT(SELF, convrev_data_offset) = 0; // instantiate our internal c++ class representation - ConvRev * cr_obj = new ConvRev(API->vm->srate(VM)); + ConvRev *cr_obj = new ConvRev(API->vm->srate(VM)); // store the pointer in the ChucK object member - OBJ_MEMBER_INT(SELF, convrev_data_offset) = (t_CKINT) cr_obj; + OBJ_MEMBER_INT(SELF, convrev_data_offset) = (t_CKINT)cr_obj; } CK_DLL_DTOR(convrev_dtor) { // get our c++ class pointer - ConvRev * cr_obj = (ConvRev *) OBJ_MEMBER_INT(SELF, convrev_data_offset); + ConvRev *cr_obj = (ConvRev *)OBJ_MEMBER_INT(SELF, convrev_data_offset); // free it CK_SAFE_DELETE(cr_obj); // zero out memory @@ -311,40 +344,41 @@ CK_DLL_DTOR(convrev_dtor) CK_DLL_TICK(convrev_tick) { // get our c++ class pointer - ConvRev * cr_obj = (ConvRev *) OBJ_MEMBER_INT(SELF, convrev_data_offset); + ConvRev *cr_obj = (ConvRev *)OBJ_MEMBER_INT(SELF, convrev_data_offset); // invoke our tick function; store in the magical out variable - if(cr_obj) *out = cr_obj->tick(in); + if (cr_obj) + *out = cr_obj->tick(in); return TRUE; } CK_DLL_MFUN(convrev_setBlockSize) { - ConvRev * cr_obj = (ConvRev *) OBJ_MEMBER_INT(SELF, convrev_data_offset); + ConvRev *cr_obj = (ConvRev *)OBJ_MEMBER_INT(SELF, convrev_data_offset); RETURN->v_float = cr_obj->setBlockSize(GET_NEXT_FLOAT(ARGS)); } - CK_DLL_MFUN(convrev_getBlockSize) { - ConvRev * cr_obj = (ConvRev *) OBJ_MEMBER_INT(SELF, convrev_data_offset); + ConvRev *cr_obj = (ConvRev *)OBJ_MEMBER_INT(SELF, convrev_data_offset); RETURN->v_float = cr_obj->getBlockSize(); } CK_DLL_MFUN(convrev_setOrder) { - ConvRev * cr_obj = (ConvRev *) OBJ_MEMBER_INT(SELF, convrev_data_offset); + ConvRev *cr_obj = (ConvRev *)OBJ_MEMBER_INT(SELF, convrev_data_offset); t_CKINT order = GET_NEXT_INT(ARGS); - if (order < 0) { + if (order < 0) + { API->vm->throw_exception( "InvalidArgument", - (std::string("Trying to set convolution filter order to a negative value!\n") - + "order = " + std::to_string(order) + ".").c_str(), - SHRED - ); - } else { + (std::string("Trying to set convolution filter order to a negative value!\n") + "order = " + std::to_string(order) + ".").c_str(), + SHRED); + } + else + { cr_obj->setOrder(order); } @@ -353,25 +387,26 @@ CK_DLL_MFUN(convrev_setOrder) CK_DLL_MFUN(convrev_getOrder) { - ConvRev * cr_obj = (ConvRev *) OBJ_MEMBER_INT(SELF, convrev_data_offset); + ConvRev *cr_obj = (ConvRev *)OBJ_MEMBER_INT(SELF, convrev_data_offset); RETURN->v_int = cr_obj->getOrder(); } CK_DLL_MFUN(convrev_setCoeff) { - ConvRev * cr_obj = (ConvRev *) OBJ_MEMBER_INT(SELF, convrev_data_offset); + ConvRev *cr_obj = (ConvRev *)OBJ_MEMBER_INT(SELF, convrev_data_offset); t_CKINT idx = GET_NEXT_INT(ARGS); t_CKFLOAT val = GET_NEXT_FLOAT(ARGS); auto order = cr_obj->getOrder(); - if (idx >= order || idx < 0) { + if (idx >= order || idx < 0) + { API->vm->throw_exception( "IndexOutOfBounds", - (std::string("Illegal index out of bounds in setting convolver filter coefficient!\n") - + "idx = " + std::to_string(idx) + " on size " + std::to_string(order) + ".").c_str(), - SHRED - ); - } else { + (std::string("Illegal index out of bounds in setting convolver filter coefficient!\n") + "idx = " + std::to_string(idx) + " on size " + std::to_string(order) + ".").c_str(), + SHRED); + } + else + { cr_obj->setCoeff(idx, val); } @@ -380,12 +415,12 @@ CK_DLL_MFUN(convrev_setCoeff) CK_DLL_MFUN(convrev_getCoeff) { - ConvRev * cr_obj = (ConvRev *) OBJ_MEMBER_INT(SELF, convrev_data_offset); + ConvRev *cr_obj = (ConvRev *)OBJ_MEMBER_INT(SELF, convrev_data_offset); RETURN->v_int = cr_obj->getCoeff(GET_CK_INT(ARGS)); } CK_DLL_MFUN(convrev_init) { - ConvRev * cr_obj = (ConvRev *) OBJ_MEMBER_INT(SELF, convrev_data_offset); - cr_obj->init(); + ConvRev *cr_obj = (ConvRev *)OBJ_MEMBER_INT(SELF, convrev_data_offset); + cr_obj->init(); } diff --git a/ConvRev/makefile b/ConvRev/makefile index 975db852..312ee681 100644 --- a/ConvRev/makefile +++ b/ConvRev/makefile @@ -28,6 +28,8 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS:=$(.DEFAULT_GOAL) endif endif +# webchugin extension +WEBSUFFIX=.wasm .PHONY: mac osx linux linux-pulse linux-oss linux-jack linux-alsa win32 mac osx linux linux-pulse linux-oss linux-jack linux-alsa: all @@ -89,6 +91,7 @@ C_OBJECTS=$(addsuffix .o,$(basename $(C_MODULES))) CXX_OBJECTS=$(addsuffix .o,$(basename $(CXX_MODULES))) CHUG=$(addsuffix $(SUFFIX),$(CHUGIN_NAME)) +WEBCHUG=$(addsuffix $(WEBSUFFIX),$(CHUG)) all: $(CHUG) @@ -106,11 +109,16 @@ $(C_OBJECTS): %.o: %.c $(CXX_OBJECTS): %.o: %.cpp $(CK_SRC_PATH)/chugin.h $(CXX) $(FLAGS) -c -o $@ $< +# build as webchugin +web: + emcc -O3 -s SIDE_MODULE=1 -s DISABLE_EXCEPTION_CATCHING=0 -fPIC -Wformat=0 \ + -I ../chuck/include/ $(CXX_MODULES) $(C_MODULES) -o $(WEBCHUG) + install: $(CHUG) mkdir -p $(CHUGIN_PATH) cp $^ $(CHUGIN_PATH) chmod 755 $(CHUGIN_PATH)/$(CHUG) clean: - rm -rf $(C_OBJECTS) $(CXX_OBJECTS) $(CHUG) Release Debug + rm -rf $(C_OBJECTS) $(CXX_OBJECTS) $(CHUG) $(WEBCHUG) Release Debug