commit 3f50d7bbf70a1d59ede8f492ec27a6e9f955fdb9 Author: Rachel Fae Fox (foxiepaws) Date: Sun Mar 8 14:33:48 2020 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9ff718 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.DS_STORE +*.o +fmtest +*.w16 +*.wav +.#a +build/ +*.dSYM +a.out diff --git a/FM/Engine.h b/FM/Engine.h new file mode 100644 index 0000000..89a21b6 --- /dev/null +++ b/FM/Engine.h @@ -0,0 +1,109 @@ +/* + * Filename: Engine.h + * + * Description: + * + * + * Version: + * Created: Sun Mar 8 08:04:38 2020 + * Revision: None + * Author: Rachel Fae Fox (foxiepaws),fox@foxiepa.ws + * + */ + +#pragma once +#include "Envelope.h" +#include "Operator.h" +#include "Matrix.h" +#include + +namespace FM { + template + class Engine { + Matrix gains; + std::array pans; // panning on master channels. + Matrix mutes; + std::array operators; + std::pair out; + + public: + double freq; + bool gate; + + Operator* getOperator(int n) { return operators[n]; } + void setMuteMatrix(int from, int to, float v) { *mutes(from,to) = v; } + void setGainMatrix(int from, int to, float v) { *gains(from,to) = v; } + Engine() { + for (auto iter = operators.begin(); iter != operators.end(); ++iter) { + *iter = new Operator(&freq, &gate, 44100.0); + } + } + void doFeedback(int n){ + Operator* o = operators[n]; + double fbg = (!*mutes(n,n)) ? *gains(n,n) : 0.0; + o->setFeedbackLevel(fbg); + } + + std::pair calcPan(float p) { + double l,r; + if (p > 0) { + l = p; + r = 1.0-p; + }else{ + l = abs(p); + r = 1.0-abs(p); + } + } + + void process(){ + out = 0.0; + for (int o = 0; o < T; o++) { + // do matrix + doFeedback(o); + Operator *op = operators[o]; + double m = 0.0; + //for (int from = 0; from < o; from++) { + for (int from = 0; from < T; from++) { // experiment with last. + if (!*mutes(from,o)) { + auto v = operators[from]->out(); + //printf("op %d to %d = %f\n",from,o,v); + m += *gains(from,o) * v; + } + } + + + auto preg = (*op)(m); + auto postg = *gains(o,T) * preg; + //printf("op %d in %f toSelf %f toOut %f\n",o,m,preg,postg); + + out += postg; + } + this->out = out; + } + + double sampleMono() { + process(); + return out.first + out.second; + } + std::pair sampleStereo() { + process(); + return out; + } + double getMono() { + return out.first + out.second; + } + std::pair getStereo() { + return out; + } + std::pair operator()(){ + process(); + return out; + } + }; +} + + +/* Local Variables: */ +/* mode: c++ */ +/* End: */ +/* vim: ft=cpp: */ diff --git a/FM/Envelope.cpp b/FM/Envelope.cpp new file mode 100644 index 0000000..5c04860 --- /dev/null +++ b/FM/Envelope.cpp @@ -0,0 +1,102 @@ +/* + * Filename: Envelope.cpp + * + * Description: + * + * + * Version: + * Created: Sun Mar 8 08:41:17 2020 + * Revision: None + * Author: Rachel Fae Fox (foxiepaws),fox@foxiepa.ws + * + */ + + +#include "Envelope.h" +namespace FM { + float Envelope::process () { + switch (envstate) { + case _e_off: + if (*gate) { + t = 0; + envstate = _e_attack; + return process(); + } + break; + case _e_attack: + if (*gate) { + if ( t < attack ) { + float stepsize = (max_level - min_level) / attack; + t++; + return t * stepsize; + } else { + t = 0; + envstate = _e_decay; + return max_level; + } + } else { + envstate = _e_attackrelease; + return process(); + } + break; + case _e_decay: + if (*gate) { + if (t < decay) { + float stepsize = (max_level - sustain_level) / decay; + t++; + return (max_level - (t * stepsize)); + + } else { + envstate = _e_sustain; + return sustain_level; + } + } else { + envstate = _e_attackrelease; + return process(); + } + break; + case _e_sustain: + if (!sustain) { + envstate = _e_release; + t = 0; + return process(); + } + if (!*gate) { + t = 0; + envstate = _e_release; + return sustain_level; + } + return sustain_level; + break; + case _e_release: + if (*gate) { + // reset envelope. + envstate=_e_off; + return process(); + } + if (modMode) { + return sustain_level; + } + if (t < release) { + float stepsize = (sustain_level - min_level) / release; + t++; + return sustain_level - (stepsize * t); + } else { + envstate = _e_off; + return min_level; + } + + break; + case _e_attackrelease: + if (*gate) { + //reset envelope + envstate=_e_off; + return process(); + } + return 0.0f; + break; + + } + return 0.0f; // if this is reached, something went wrong or an unhandled function was used. + } +} diff --git a/FM/Envelope.h b/FM/Envelope.h new file mode 100644 index 0000000..b8934fb --- /dev/null +++ b/FM/Envelope.h @@ -0,0 +1,80 @@ +/* + * Filename: Envelope.h + * + * Description: + * + * + * Version: + * Created: Sun Mar 8 07:48:47 2020 + * Revision: None + * Author: Rachel Fae Fox (foxiepaws),fox@foxiepa.ws + * + */ +#pragma once +#include +#include +namespace FM { + class Envelope { + enum EnvState {_e_off,_e_attack, _e_attackrelease, _e_decay, _e_sustain,_e_release, _e_finished}; + // Time Params + unsigned int attack = 0; // attack length in samples + unsigned int decay = 0; // decay length in samples + unsigned int release = 0; // release time in samples + bool sustain = true; // boolean if sustain is enabled. + bool modMode = false; // boolean if we just use SL instead of minlevel till reset. + + // Level Params + float max_level = 1.0; + float sustain_level = 1.0; // level that decay phase drops to. + float min_level = 0.0; + bool* gate; // pointer to gate + + // Internal use + unsigned long int t = 0; // samples; + unsigned int smpRate; // sample rate + EnvState envstate = _e_off; // envelope state. + public: + // getters + unsigned int getAttackSamples() { return attack; } + unsigned int getDecaySamples() { return decay; } + unsigned int getReleaseSamples() { return release; } + float getAttackTime() { return (double) attack / (double) smpRate; } + float getDecayTime() { return (double) decay / (double) smpRate; } + float getReleaseTime() { return (double) release / (double) smpRate; } + bool getSustain() { return sustain; } + float getMaxLevel() { return max_level; } + float getMinLevel() { return min_level; } + float getSustainLevel() { return sustain_level; } + bool getModMode() { return modMode; } + bool* getGate() { return gate; } + // setters + void setAttackSamples(unsigned int a) { attack = a; } + void setAttackTime(float a) { attack = floor(smpRate * a); } + void setDecaySamples(unsigned int d) { decay = d; } + void setDecayTime(float d) { decay = floor(smpRate * d); } + void setReleaseSamples(unsigned int r) { release = r; } + void setReleaseTime(float r) { release = floor(smpRate * r); } + void setSustain(bool s) { sustain = s; } + void setModMode(bool m) { modMode = m; } + void setMaxLevel(float ml) { max_level = ml; } + void setMinLevel(float ml) { min_level = ml; } + void setSustainLevel(float sl) { sustain_level = sl; } + void setGate(bool* g) { gate = g; } + void setRate(unsigned int sr) { smpRate = sr; } + // helpers + // note : if you make use of these functions YOU must manage their memory. + void makeGate() { gate = (bool*) malloc(sizeof(bool)); } + void freeGate() { free(gate); } + // constructors + Envelope(){} + Envelope(unsigned int sr, unsigned int a, unsigned int d, bool s, unsigned int r, float sl, bool* g); + // methods + float process(); + float operator()() { return process(); } + }; +} + +/* Local Variables: */ +/* mode: c++ */ +/* End: */ +/* vim: ft=cpp: */ diff --git a/FM/Matrix.h b/FM/Matrix.h new file mode 100644 index 0000000..95541d8 --- /dev/null +++ b/FM/Matrix.h @@ -0,0 +1,34 @@ +/* + * Filename: Matrix.h + * + * Description: + * + * + * Version: + * Created: Sun Mar 8 08:11:11 2020 + * Revision: None + * Author: Rachel Fae Fox (foxiepaws),fox@foxiepa.ws + * + */ + +#pragma once +#include + +namespace FM { + template + class Matrix { + public: + std::array, N> v; // gain + std::array *operator[] (int row){ + return &v[row]; + } + T *operator()(int row, int col) { + return &v[row][col]; + } + }; +} + +/* Local Variables: */ +/* mode: c++ */ +/* End: */ +/* vim: ft=cpp: */ diff --git a/FM/Operator.cpp b/FM/Operator.cpp new file mode 100644 index 0000000..cca3148 --- /dev/null +++ b/FM/Operator.cpp @@ -0,0 +1,61 @@ +/* + * Filename: Operator.cpp + * + * Description: + * + * + * Version: + * Created: Sun Mar 8 08:36:21 2020 + * Revision: None + * Author: Rachel Fae Fox (foxiepaws),fox@foxiepa.ws + * + */ + +#include "Operator.h" +namespace FM { + void Operator::process(){ + phase += smpTime * (((*freq * mul) + detune)); + _out=last=sin((2*M_PI*phase) + _in + (fbgain*last)); + } + double Operator::operator()(){ + process(); + elast = e(); + return elast * _out; + } + double Operator::operator()(double in){ + _in = in; + process(); + elast = e(); + return elast * _out; + } + void Operator::setAll(double mul, double detune, double fbgain) { + this->mul = mul; + this->detune = detune; + this->fbgain = fbgain; + } + Operator::Operator(){ + __m = true; + freq = (double*) malloc(sizeof(double)); + gate = (bool*) malloc(sizeof(bool)); + e.setGate(gate); + } + Operator::Operator(double* f, bool* g) { + gate = g; + freq = f; + e.setGate(gate); + } + Operator::Operator(double* f, bool* g, unsigned int sr) { + gate = g; + freq = f; + smpRate = sr; + smpTime = 1.0/sr; + e.setRate(sr); + e.setGate(gate); + } + Operator::~Operator(){ + if (__m) { + free(freq); + free(gate); + } + } +} diff --git a/FM/Operator.h b/FM/Operator.h new file mode 100644 index 0000000..170a078 --- /dev/null +++ b/FM/Operator.h @@ -0,0 +1,51 @@ +/* + * Filename: Operator.h + * + * Description: + * + * + * Version: + * Created: Sun Mar 8 07:57:26 2020 + * Revision: None + * Author: Rachel Fae Fox (foxiepaws),fox@foxiepa.ws + * + */ + +#pragma once +#include "Envelope.h" +namespace FM { + class Operator { + double mul = 1.0; + double detune = 0.0; + double fbgain = 0.0; + bool __m; + double *freq; // frequency + bool *gate; + double _in = 0.0; // fm input + double _out = 0.0; + double last = 0.0; // for feedback + double phase = 0.0; + Envelope e; + double elast = 0.0; + double smpTime; + double smpRate; + public: + void process(); + double operator()(); + double operator()(double in); + double out() { return elast * _out; } + Envelope* getEnvelope() { return &e; } + void setAll(double mul, double detune, double fbgain); + void setMul(double mul) { this->mul = mul; } + void setDetune(double mul) { this->mul = mul; } + void setFeedbackLevel(double fbgain) { this->fbgain = fbgain; }; + Operator(); + Operator(double* f, bool* g); + Operator(double* f, bool* g, unsigned int sr); + ~Operator(); + }; +} + +/* Local Variables: */ +/* mode: c++ */ +/* End: */ diff --git a/FM/fmtest.cpp b/FM/fmtest.cpp new file mode 100644 index 0000000..5a50527 --- /dev/null +++ b/FM/fmtest.cpp @@ -0,0 +1,96 @@ +/* + * Filename: fmtest.cpp + * + * Description: + * + * + * Version: + * Created: Sun Mar 8 08:12:43 2020 + * Revision: None + * Author: Rachel Fae Fox (foxiepaws),fox@foxiepa.ws + * + */ + +#include "Engine.h" +#include +#include +#include + +int16_t toint16(double i) { + double temp = i *= 32767; + return floor(temp); +} +void writeBytes(FILE* file, char* bytes, unsigned int count) { + for (int i = 0; i < count ; i++) + putc(bytes[i],file); +} +int main(int argc, char** argv){ + FM::Engine<2> *f = new FM::Engine<2>(); + f->getOperator(0)->setAll(2.0,0.0,0.0); + f->getOperator(0)->getEnvelope()->setAttackTime(1.0); + f->getOperator(0)->getEnvelope()->setDecayTime(0.5); + f->getOperator(0)->getEnvelope()->setMaxLevel(1.0); + f->getOperator(0)->getEnvelope()->setModMode(true); // play until reset. + f->getOperator(1)->getEnvelope()->setReleaseTime(2.5); + f->getOperator(1)->setAll(1.0,0.0,0.0); + f->setGainMatrix(0,0,0.0); // op1 fbl = 0 + f->setGainMatrix(0,1,1.0); // op1 -> op2 = 1 + f->setGainMatrix(1,0,0.0); // op2 -> op1 = 0 + f->setGainMatrix(1,2,1.0); // op2 -> out = 1 + f->freq = 440; + f->gate = true; + FM::Engine<2> *g = new FM::Engine<2>(); + g->getOperator(0)->setAll(2.0,0.0,0.0); + g->getOperator(0)->getEnvelope()->setAttackTime(1.0); + g->getOperator(0)->getEnvelope()->setDecayTime(0.5); + g->getOperator(0)->getEnvelope()->setMaxLevel(1.0); + g->getOperator(0)->getEnvelope()->setModMode(true); // play until reset. + g->getOperator(1)->getEnvelope()->setReleaseTime(2.5); + g->getOperator(1)->setAll(1.0,0.0,0.0); + g->setGainMatrix(0,0,0.0); // op1 fbl = 0 + g->setGainMatrix(0,1,1.0); // op1 -> op2 = 1 + g->setGainMatrix(1,0,0.0); // op2 -> op1 = 0 + g->setGainMatrix(1,2,1.0); // op2 -> out = 1 + g->freq = 256; + g->gate = true; + FILE* w = fopen("testwav.wav", "w"); + fprintf(w,"RIFF"); + double seconds = 20; + uint32_t srate = 44100; + unsigned int samples= floor(seconds*srate); + uint16_t bps = 16; + uint16_t channels = 2; + uint32_t datasize = ((bps/8)*channels*samples); + uint32_t fmtsize = 16; + uint16_t type = 1; + uint32_t riffsize = 20 + fmtsize + datasize; + uint32_t byterate = srate * 1 * (bps/8); + uint16_t blockalign = channels * (bps/8); + char* l; + writeBytes(w,(char*) &riffsize,4); + fprintf(w,"WAVEfmt "); + // size + writeBytes(w,(char*) &fmtsize,4); + // type + writeBytes(w,(char*) &type,2); + writeBytes(w,(char*) &channels,2); + writeBytes(w,(char*) &srate,4); + writeBytes(w,(char*) &byterate,4); + writeBytes(w,(char*) &blockalign,2); + writeBytes(w,(char*) &bps, 2); + fprintf(w,"data"); + writeBytes(w,(char*) &datasize, 4); + + for (int s = 0; s < samples; s++) { + double ld = (*f)(); + double rd = (*g)(); + int16_t l = toint16(ld/4.0); + int16_t r = toint16(rd/4.0); + writeBytes(w,(char*) &l,2); + writeBytes(w,(char*) &r,2); + } + fclose(w); + return 0; +} + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2d97d26 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright 2020 Rachel Fae Fox + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9ccd604 --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +CXX ?= clang++ +SRCDIR ?= FM +INCDIR += -I${SRCDIR} +OBJDIR ?= FM +BINDIR ?= bin +INSTALLDIR ?= /usr/local/bin + +TARGET ?= synththing + +CFLAGS += -std=c++11 +#CFLAGS += `pkg-config --cflags portaudio-2.0` +#LDFLAGS += `pkg-config --libs portaudio-2.0` + +UNAME=$(shell uname -s) +ifeq (${UNAME},FreeBSD) +INCDIR += -I/sys/ +endif + +SHELL=/bin/sh +SRC = $(wildcard ${SRCDIR}/*.cpp ${SRCDIR}/*/*.cpp) +OBJ = ${SRC:${SRCDIR}/%.cpp=${OBJDIR}/%.o} + + +all: fmtest + +${OBJ}: $(OBJDIR)/%.o : $(SRCDIR)/%.cpp + @echo CXX $< + ${CXX} -o $@ ${INCDIR} -c ${CFLAGS} ${DEFINES} $< + +fmtest: ${OBJ} + @echo CXX -o ${BINDIR}/fmtest + @mkdir -p ${BINDIR} + ${CXX} ${INCDIR} -o ${BINDIR}/fmtest ${OBJ} ${LDFLAGS} ${CFLAGS} ${DEFINES} + +clean: + @echo cleaning + @rm -f ${OBJ} + +remove: clean + @echo deleting binary + @rm -f ${BINDIR}/${TARGET} + @rm -r ${BINDIR} + +install: fmtest + mkdir -p ${INSTALLDIR} + cp ${BINDIR}/${TARGET} ${INSTALLDIR}/${TARGET} + chmod 4555 ${INSTALLDIR}/${TARGET} + +