initial commit

master
Rachel Fae Fox (foxiepaws) 2020-03-08 14:33:48 -04:00
commit 3f50d7bbf7
10 changed files with 615 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.DS_STORE
*.o
fmtest
*.w16
*.wav
.#a
build/
*.dSYM
a.out

109
FM/Engine.h Normal file
View File

@ -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 <array>
namespace FM {
template <int T>
class Engine {
Matrix<double,T> gains;
std::array<float, T> pans; // panning on master channels.
Matrix<bool,T> mutes;
std::array<Operator*,T> operators;
std::pair<double,double> 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<double,double> 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<double,double> sampleStereo() {
process();
return out;
}
double getMono() {
return out.first + out.second;
}
std::pair<double,double> getStereo() {
return out;
}
std::pair<double,double> operator()(){
process();
return out;
}
};
}
/* Local Variables: */
/* mode: c++ */
/* End: */
/* vim: ft=cpp: */

102
FM/Envelope.cpp Normal file
View File

@ -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.
}
}

80
FM/Envelope.h Normal file
View File

@ -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 <stdlib.h>
#include <math.h>
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: */

34
FM/Matrix.h Normal file
View File

@ -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 <array>
namespace FM {
template <class T, int N>
class Matrix {
public:
std::array<std::array<T, N+1>, N> v; // gain
std::array<T,N+1> *operator[] (int row){
return &v[row];
}
T *operator()(int row, int col) {
return &v[row][col];
}
};
}
/* Local Variables: */
/* mode: c++ */
/* End: */
/* vim: ft=cpp: */

61
FM/Operator.cpp Normal file
View File

@ -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);
}
}
}

51
FM/Operator.h Normal file
View File

@ -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: */

96
FM/fmtest.cpp Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <math.h>
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;
}

24
LICENSE Normal file
View File

@ -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.

49
Makefile Normal file
View File

@ -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}