diff --git a/Makefile b/Makefile index 37ec369..c463872 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,37 @@ +# NOTE: C implies LADSPA, C++ implies VST +# it's hackish but it'll do for now + DISTNAME = crap VERSION = git +FULLNAME = ${DISTNAME}-${VERSION} + +BOTH = crap_eq_const +LADSPA_ONLY = crap_eq crap_noise +VST_ONLY = +LADSPA = ${BOTH:=-ladspa} ${LADSPA_ONLY:=-ladspa} +VST = ${BOTH:=-vst} ${VST_ONLY:=-vst} +PLUGINS = ${LADSPA} ${VST} EXE = design -SHOBJ = crap_eq.so crap_eq_const.so crap_noise.so -MID = -HEADERS = crap_util.h crap_util_def.h ladspa.h +HEADERS = crap_util.h crap_util_def.h +SHOBJ = ${PLUGINS:=.so} +OBJ = ${PLUGINS:=.o} vstsdk.o + +# only for dist target right now +SRC = ${BOTH:=.h} ${LADSPA_ONLY:=-ladspa.c} ${VST_ONLY:=-vst.cpp} ${EXE:=.c} BENCH = bench.o -AGAINST = ./crap_eq_const.so +AGAINST = ./crap_eq_const-ladspa.so -OBJ = ${SHOBJ:.so=.o} ${EXE:=.o} ${MID} -SRC = ${OBJ:.o=.c} +VST_SDK_DIR ?= . +VST_CPP = audioeffect.cpp audioeffectx.cpp vstplugmain.cpp +VST_SRC = ${VST_CPP:%=${VST_SDK_DIR}/public.sdk/source/vst2.x/%} +# temp objects before combining to vstsdk.o +VST_OBJ = ${VST_CPP:.cpp=.o} +VST_DEF = ${VST_SDK_DIR}/public.sdk/samples/vst2.x/win/vstplug.def -WARNING_FLAGS = -Wall -Wno-unused-function -ALL_CFLAGS = -std=gnu99 -fpic ${WARNING_FLAGS} ${CFLAGS} +ALL_CFLAGS = -Wall -Wno-unused-function ${CFLAGS} -std=gnu99 +ALL_CXXFLAGS = -Wno-write-strings ${CXXFLAGS} -I ${VST_SDK_DIR} -DBUILDING_DLL=1 ALL_LDFLAGS = -lm ${LDFLAGS} PREFIX ?= /usr/local @@ -21,55 +39,94 @@ EXEC_PREFIX ?= ${PREFIX} LIBDIR ?= ${EXEC_PREFIX}/lib LADSPADIR ?= ${LIBDIR}/ladspa -FULLNAME = ${DISTNAME}-${VERSION} -ALL = ${OBJ} ${SHOBJ} ${EXE} LADSPADEST = ${DESTDIR}${LADSPADIR} -all: options ${ALL} +ALL = ${SHOBJ} ${OBJ} ${EXE} +MISC_CLEAN = bench ${BENCH} +MISC_DIST = LICENSE README.md Makefile +MISC_DIST += benchtime ${BENCH:.o=.c} +MISC_DIST += generate-ladspa generate-vst common.sh +MISC_DIST += template-vst.cpp template-ladspa.c ladspa.h -bench: all ${BENCH} - @echo LD ${BENCH} ${MID} -o $@ +all: options ladspa vst ${EXE} + +.PHONY: options +options: + @echo "CPPFLAGS = ${CPPFLAGS}" + @echo "ALL_CFLAGS = ${ALL_CFLAGS}" + @echo "ALL_CXXFLAGS = ${ALL_CXXFLAGS}" + @echo "ALL_LDFLAGS = ${ALL_LDFLAGS}" + @echo "CC = ${CC}" + @echo "CXX = ${CXX}" + @echo "LD = ${LD}" + @echo + +ladspa: ${LADSPA:=.so} + +vst: ${VST:=.so} + +bench: ${AGAINST} ${BENCH} + @echo CC ${BENCH} -o $@ @${CC} ${ALL_CFLAGS} ${BENCH} -o $@ ${ALL_LDFLAGS} -rdynamic -ldl .PHONY: benchmark benchmark: bench ./benchtime ./bench ${AGAINST} -.PHONY: options -options: - @echo "ALL_CFLAGS = ${ALL_CFLAGS}" - @echo "CPPFLAGS = ${CPPFLAGS}" - @echo "ALL_LDFLAGS = ${ALL_LDFLAGS}" - @echo "CC = ${CC}" +vstsdk.o: ${VST_SRC} + @echo CXX -c $^ + @${CXX} -c ${ALL_CXXFLAGS} ${CPPFLAGS} $^ + @echo LD -r ${VST_OBJ} -o $@ + @${LD} -r ${VST_OBJ} -o $@ + rm ${VST_OBJ} -%.so: %.o ${MID} - @echo LD $< ${MID} -o $@ - @${CC} ${ALL_CFLAGS} -shared $< ${MID} -o $@ ${ALL_LDFLAGS} +%-ladspa.so: %-ladspa.o + @echo CC $^ -o $@ + @${CC} ${ALL_CFLAGS} -shared $^ -o $@ ${ALL_LDFLAGS} -%: %.o ${MID} - @echo LD $< ${MID} -o $@ - @${CC} ${ALL_CFLAGS} $< ${MID} -o $@ ${ALL_LDFLAGS} +%-vst.so: %-vst.o vstsdk.o + @echo CXX $^ -o $@ + @${CXX} ${ALL_CXXFLAGS} -shared $^ -o $@ ${ALL_LDFLAGS} -%.o: %.c ${HEADERS} +%-ladspa.o: %-ladspa.c ${HEADERS} ladspa.h + @echo CC -c $< -o $@ + @${CC} -c ${ALL_CFLAGS} ${CPPFLAGS} $< -o $@ + +%-vst.o: %-vst.cpp ${HEADERS} + @echo CXX -c $< -o $@ + @${CXX} -c ${ALL_CXXFLAGS} ${CPPFLAGS} $< -o $@ + +%-ladspa.c: %.h template-ladspa.c generate-ladspa common.sh + ./generate-ladspa $< $@ + +%-vst.cpp: %.h template-vst.cpp generate-vst common.sh + ./generate-vst $< $@ + +.SUFFIXES: + +%: %.o @echo CC $< -o $@ + @${CC} ${ALL_CFLAGS} $< -o $@ ${ALL_LDFLAGS} + +%.o: %.c + @echo CC -c $< -o $@ @${CC} -c ${ALL_CFLAGS} ${CPPFLAGS} $< -o $@ install: all mkdir -p ${LADSPADEST} install -d ${LADSPADEST} - install -m 644 ${SHOBJ} ${LADSPADEST} + install -m 644 ${LADSPA:=.so} ${LADSPADEST} .PHONY: clean clean: - -rm -f ${ALL} bench ${BENCH} + -rm -f ${ALL} ${MISC_CLEAN} .PHONY: dist dist: -rm -f ${FULLNAME}.tar.gz - mkdir -p ${FULLNAME} - cp LICENSE README.md Makefile ${HEADERS} ${SRC} ${FULLNAME} - cp bench.sh ${BENCH:.o=.c} ${FULLNAME} + mkdir ${FULLNAME} + cp ${MISC_DIST} ${HEADERS} ${SRC} ${FULLNAME} tar -cf ${FULLNAME}.tar ${FULLNAME} gzip ${FULLNAME}.tar - rm -rf ${FULLNAME} + rm -r ${FULLNAME} diff --git a/README.md b/README.md index 608e718..77bbdb2 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,27 @@ # crap -connor's rancid audio plugins. LADSPA. +connor's rancid audio plugins. LADSPA and VST. -alternatively, chocolate recipes are powerful. +alternatively, configuration realizes any personality ## plugs -### crap_eq +### crap Parametric Equalizer + +__crap_eq (0x000CAFED)__ multiband parametric EQ. try redefining BANDS. -### crap_eq_const +### crap const Equalizer -simplified code for a static response. -one day, this will be faster with compile-time constants. +__crap_eq_const (0x0DEFACED)__ -### crap_noise +simpler code with a static response. +edit code as needed. + +### crap noises generator + +__crap_noise (0xEC57A71C)__ white noise generator. loud, full-range, 0dBFS. don't say i didn't warn you. @@ -23,10 +29,10 @@ white noise generator. loud, full-range, 0dBFS. don't say i didn't warn you. `make` it. optional `benchmark` target which doesn't build on Windows. -if you're using gcc, try `CFLAGS='-O3 -ffast-math'`. -`-march=native` actually seems to degrade performance slightly, but YMMV. +with gcc, try `CFLAGS='-O3 -ffast-math'`. +`-march=native` seems to degrade performance slightly, but YMMV. -if you're using clang, (at the time of writing this) -you can't tune performance that much so just use `CFLAGS='-O2'`. +with clang, (at the time of writing this) +optimizations are not as tuneable so try `CFLAGS='-O2'`. can be cross-compiled for Windows for use in Audacity or a LADSPA wrapper. diff --git a/common.sh b/common.sh new file mode 100644 index 0000000..0b1a22c --- /dev/null +++ b/common.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e +set -u + +p="$1" +out="$2" + +while read -r; do + : +done < "$p" + +pp_include="#include \"$p\"" diff --git a/crap_eq.c b/crap_eq-ladspa.c similarity index 100% rename from crap_eq.c rename to crap_eq-ladspa.c diff --git a/crap_eq_const.c b/crap_eq_const.c deleted file mode 100755 index 2700e10..0000000 --- a/crap_eq_const.c +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include - -#include "ladspa.h" -#include "crap_util.h" - -typedef unsigned long ulong; - -#define REALBANDS 4 -#define EQ_INPUT 0 -#define EQ_OUTPUT 1 -#define PCOUNT 2 - -const LADSPA_PortDescriptor p_discs[PCOUNT] = { - LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, - LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO -}; -const LADSPA_PortRangeHint p_hints[PCOUNT] = { - {.HintDescriptor = 0}, - {.HintDescriptor = 0} -}; -const char *p_names[PCOUNT] = {"Input", "Output"}; - -typedef struct { - LADSPA_Data *input; - LADSPA_Data *output; - - biquad filters[REALBANDS]; - LADSPA_Data fs; -} eq_t; - -static void -activate_eq(LADSPA_Handle instance) { - eq_t *eq = (eq_t *)instance; - biquad *filters = eq->filters; - - for (int i = 0; i < REALBANDS; i++) - biquad_init(&filters[i]); -} - -static void -cleanup_eq(LADSPA_Handle instance) { - free(instance); -} - -static void -connect_port_eq(LADSPA_Handle instance, ulong port, LADSPA_Data *data) { - eq_t *eq = (eq_t *)instance; - if (port == EQ_INPUT) - eq->input = data; - else if (port == EQ_OUTPUT) - eq->output = data; -} - -static LADSPA_Handle -instantiate_eq(const LADSPA_Descriptor *descriptor, ulong s_rate) { - eq_t *eq = (eq_t *) calloc(1, sizeof(eq_t)); - biquad *filters = eq->filters; - LADSPA_Data fs = s_rate; - - eq->fs = fs; - - filters[0] = biquad_gen(4, 10, 0.0, 1.00, fs); - filters[1] = biquad_gen(0, 36, 4.3, 1.25, fs); - filters[2] = biquad_gen(2, 1400, -7.0, 1.20, fs); - filters[3] = biquad_gen(0, 7000, 3.3, 1.25, fs); - - return (LADSPA_Handle) eq; -} - -static void -run_eq(LADSPA_Handle instance, ulong sample_count) { - eq_t *eq = (eq_t *) instance; - biquad *filters = eq->filters; - - const LADSPA_Data *input = eq->input; - LADSPA_Data *output = eq->output; - - for (ulong pos = 0; pos < sample_count; pos++) { - LADSPA_Data samp = input[pos]; - for (int i = 0; i < REALBANDS; i++) - samp = biquad_run(&filters[i], samp); - output[pos] = samp; - } -} - -static const LADSPA_Descriptor eqDescriptor = { - .UniqueID = 0xDEFACED, - .Label = "crap_eq_const", - .Properties = 0, - .Name = "crap const Equalizer", - .Maker = "Connor Olding", - .Copyright = "MIT", - .PortCount = PCOUNT, - .PortDescriptors = p_discs, - .PortRangeHints = p_hints, - .PortNames = p_names, - - .activate = activate_eq, - .cleanup = cleanup_eq, - .connect_port = connect_port_eq, - .deactivate = NULL, - .instantiate = instantiate_eq, - .run = run_eq, - .run_adding = NULL, - .set_run_adding_gain = NULL -}; - -const LADSPA_Descriptor * -ladspa_descriptor(ulong index) { - if (index != 0) - return NULL; - return &eqDescriptor; -} diff --git a/crap_eq_const.h b/crap_eq_const.h new file mode 100644 index 0000000..7a9c7f6 --- /dev/null +++ b/crap_eq_const.h @@ -0,0 +1,76 @@ +#include + +#define BIQUAD_DOUBLE +#include "crap_util.h" + +#define ID 0x0DEFACED +#define LABEL "crap_eq_const" +#define NAME "crap Constant Equalizer" +#define AUTHOR "Connor Olding" +#define COPYRIGHT "MIT" +#define PARAMETERS 0 + +#define BANDS 4 +typedef struct { + biquad filters[2][BANDS]; +} personal; + +static bq_t +process_one(biquad *filters, bq_t samp) +{ + for (int i = 0; i < BANDS; i++) + samp = biquad_run(&filters[i], samp); + return samp*1.2023; +} + +static void +process(personal *data, + float *in_L, float *in_R, + float *out_L, float *out_R, + unsigned long count) { + for (unsigned long pos = 0; pos < count; pos++) { + out_L[pos] = process_one(data->filters[0], in_L[pos]); + out_R[pos] = process_one(data->filters[1], in_R[pos]); + } +} + +static void +process_double(personal *data, + double *in_L, double *in_R, + double *out_L, double *out_R, + unsigned long count) { + // TODO: test which hosts use this + for (unsigned long pos = 0; pos < count; pos++) { + out_L[pos] = process_one(data->filters[0], in_L[pos]); + out_R[pos] = process_one(data->filters[1], in_R[pos]); + } +} + +static void +construct(personal *data) { +} + +static void +destruct(personal *data) { +} + +static void +resume(personal *data) { + biquad *filters = data->filters[0]; + for (int i = 0; i < BANDS; i++) + biquad_init(&filters[i]); + memcpy(data->filters[1], filters, BANDS*sizeof(biquad)); +} + +static void +pause(personal *data) { +} + +static void +adjust(personal *data, unsigned long fs) { + biquad *filters = data->filters[0]; + filters[0] = biquad_gen(0, 34.34, +4.6, 1.21, fs); + filters[1] = biquad_gen(0, 85.74, -1.2, 1.31, fs); + filters[2] = biquad_gen(2, 862.2, -5.5, 1.00, fs); + filters[3] = biquad_gen(0, 7496., +3.3, 1.10, fs); +} diff --git a/crap_noise.c b/crap_noise-ladspa.c similarity index 100% rename from crap_noise.c rename to crap_noise-ladspa.c diff --git a/generate-ladspa b/generate-ladspa new file mode 100755 index 0000000..de63e75 --- /dev/null +++ b/generate-ladspa @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +. common.sh + +(cat template-ladspa.c | while read -r; do + case "$REPLY" in + + '//#INCLUDE') echo "$pp_include" ;; + + # unimplemented + '//#PORT_DESCRIPTIONS') :;; + '//#PORT_HINTS') :;; + '//#PORT_NAMES') :;; + '//#PORT_DATA') :;; + '//#PORT_CONNECT') :;; + + *) echo -E "$REPLY" ;; + esac +done) > "$out" diff --git a/generate-vst b/generate-vst new file mode 100755 index 0000000..3630414 --- /dev/null +++ b/generate-vst @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +. common.sh + +(cat template-vst.cpp | while read -r; do + case "$REPLY" in + + '//#INCLUDE') echo "$pp_include" ;; + + # unimplemented + '//#VST_PARAMS') :;; + + *) echo -E "$REPLY" ;; + esac +done) > "$out" diff --git a/template-ladspa.c b/template-ladspa.c new file mode 100644 index 0000000..45179e9 --- /dev/null +++ b/template-ladspa.c @@ -0,0 +1,132 @@ +#include +#include "ladspa.h" + +//#INCLUDE + +#define PLUG_INPUT_L 0 +#define PLUG_INPUT_R 1 +#define PLUG_OUTPUT_L 2 +#define PLUG_OUTPUT_R 3 +#define PCOUNT (PARAMETERS + 4) + +const LADSPA_PortDescriptor p_discs[PCOUNT] = { + LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO + , + LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO + , + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO + , + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO +//#PORT_DESCRIPTIONS +}; + +const LADSPA_PortRangeHint p_hints[PCOUNT] = { + {.HintDescriptor = 0} + , + {.HintDescriptor = 0} + , + {.HintDescriptor = 0} + , + {.HintDescriptor = 0} +//#PORT_HINTS +}; + +const char *p_names[PCOUNT] = { + "L input" + , + "R input" + , + "L output" + , + "R output" +//#PORT_NAMES +}; + +typedef struct { + LADSPA_Data *input_L; + LADSPA_Data *input_R; + LADSPA_Data *output_L; + LADSPA_Data *output_R; + +//#PORT_DATA + personal data; +} plug_t; + +static void +plug_connect(LADSPA_Handle instance, unsigned long port, LADSPA_Data *data) { + plug_t *plug = (plug_t *)instance; + if (port == PLUG_INPUT_L) + plug->input_L = data; + else if (port == PLUG_INPUT_R) + plug->input_R = data; + else if (port == PLUG_OUTPUT_L) + plug->output_L = data; + else if (port == PLUG_OUTPUT_R) + plug->output_R = data; +//#PORT_CONNECT +} + +static void +plug_resume(LADSPA_Handle instance) { + plug_t *plug = (plug_t *)instance; + resume(&plug->data); +} + +static void +plug_pause(LADSPA_Handle instance) { + plug_t *plug = (plug_t *)instance; + pause(&plug->data); +} + +static LADSPA_Handle +plug_construct(const LADSPA_Descriptor *descriptor, unsigned long fs) { + plug_t *plug = (plug_t *) calloc(1, sizeof(plug_t)); + construct(&plug->data); + adjust(&plug->data, fs); + return (LADSPA_Handle) plug; +} + +static void +plug_destruct(LADSPA_Handle instance) { + plug_t *plug = (plug_t *)instance; + destruct(&plug->data); + free(plug); +} + +static void +plug_process(LADSPA_Handle instance, unsigned long count) { + plug_t *plug = (plug_t *)instance; + process(&plug->data, + plug->input_L, plug->input_R, + plug->output_L, plug->output_R, + count); +} + +static const LADSPA_Descriptor plug_desc = { + .UniqueID = ID, + .Label = LABEL, + .Properties = 0, + .Name = NAME, + .Maker = AUTHOR, + .Copyright = COPYRIGHT, + .PortCount = PCOUNT, + .PortDescriptors = p_discs, + .PortRangeHints = p_hints, + .PortNames = p_names, + + .instantiate = plug_construct, + .cleanup = plug_destruct, + .activate = plug_resume, + .deactivate = plug_pause, + .connect_port = plug_connect, + .run = plug_process, + .run_adding = NULL, + .set_run_adding_gain = NULL +}; + +const LADSPA_Descriptor * +ladspa_descriptor(unsigned long index) { + if (index != 0) + return NULL; + return &plug_desc; +} diff --git a/template-vst.cpp b/template-vst.cpp new file mode 100644 index 0000000..c7845e7 --- /dev/null +++ b/template-vst.cpp @@ -0,0 +1,127 @@ +#include "public.sdk/source/vst2.x/audioeffectx.h" +//#include "vstparam.h" + +//#INCLUDE + +class plugin : public AudioEffectX +{ +public: + plugin(audioMasterCallback audioMaster); + ~plugin(); + + void resume(); + void suspend(); + void processReplacing(float **, float **, VstInt32); + void processDoubleReplacing(double **, double **, VstInt32); + + bool setProcessPrecision(VstInt32 precision); + + /* + void setParameter(VstInt32, float); + float getParameter(VstInt32); + void getParameterLabel(VstInt32, char *); + void getParameterDisplay(VstInt32, char *); + void getParameterName(VstInt32, char *); + */ + + void setSampleRate(float); + bool getEffectName(char *); + bool getVendorString(char *); + bool getProductString(char *); + //VstInt32 getVendorVersion(); + +private: + #if (PARAMETERS > 0) + VstParam *m_params[PARAMETERS]; + #endif + + personal data; +}; + +AudioEffect * +createEffectInstance(audioMasterCallback audioMaster) { + return new plugin(audioMaster); +} + +plugin::plugin(audioMasterCallback audioMaster) +: AudioEffectX(audioMaster, 1, PARAMETERS) +{ +//#VST_PARAMS + //m_params[n] = new VstParam("Input", "dB", -12.0, 12.0, 0.0, NULL, NULL, 1, NULL); + setNumInputs(2); + setNumOutputs(2); + setUniqueID(ID); + canProcessReplacing(); + ::construct(&data); +} + +plugin::~plugin() +{ + ::destruct(&data); + //for (int i = 0; i < PARAMETERS; i++) + // delete m_params[i]; +} + +void +plugin::resume() { + ::resume(&data); + AudioEffectX::resume(); +} + +void +plugin::suspend() { + AudioEffectX::suspend(); + ::pause(&data); +} + +void +plugin::processReplacing(float **inputs, float **outputs, VstInt32 count) { + ::process(&data, + inputs[0], inputs[1], + outputs[0], outputs[1], + count); +} + +void +plugin::processDoubleReplacing(double **inputs, double **outputs, VstInt32 count) { + ::process_double(&data, + inputs[0], inputs[1], + outputs[0], outputs[1], + count); +} + +bool +plugin::setProcessPrecision(VstInt32 precision) { + return true; +} + +/* +parameter funcs go here + if (index >= PARAMETERS) return; +*/ + +void +plugin::setSampleRate(float fs) { + AudioEffectX::setSampleRate(fs); + ::adjust(&data, (unsigned long) fs); +} + +bool +plugin::getEffectName(char *name) { + vst_strncpy(name, LABEL, kVstMaxEffectNameLen); + return true; +} + +bool +plugin::getProductString(char *text) +{ + vst_strncpy(text, NAME, kVstMaxProductStrLen); + return true; +} + +bool +plugin::getVendorString(char *text) +{ + vst_strncpy(text, AUTHOR, kVstMaxVendorStrLen); + return true; +}