diff --git a/.gitignore b/.gitignore index 8fc783475..89df1bfaf 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,9 @@ MyProjects/ .cortex-debug.registers.state.json .cortex-debug.peripherals.state.json + + +**/build +**/.vscode + + diff --git a/optic/HachiKit/AhdEnv.cpp b/optic/HachiKit/AhdEnv.cpp new file mode 100644 index 000000000..10486d215 --- /dev/null +++ b/optic/HachiKit/AhdEnv.cpp @@ -0,0 +1,40 @@ +#include "AhdEnv.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + +void AhdEnv::Init(float sample_rate) { + ad.Init(sample_rate); + adsr.Init(sample_rate); + adsr.SetTime(ADSR_SEG_DECAY, 0.001); + adsr.SetSustainLevel(0.5); +} + +float AhdEnv::Process() { + // The AD envelope is used to count off the gate time of the ADSR, providing the hold time. + ad.Process(); + return adsr.Process(ad.IsRunning()); +} + +void AhdEnv::Trigger() { + ad.Trigger(); + adsr.Retrigger(true); +} + +void AhdEnv::SetAttack(float time) { + ad.SetTime(ADENV_SEG_ATTACK, time); + adsr.SetTime(ADSR_SEG_ATTACK, time); +} + +void AhdEnv::SetHold(float time) { + ad.SetTime(ADENV_SEG_DECAY, time); +} + +void AhdEnv::SetDecay(float time) { + adsr.SetTime(ADSR_SEG_RELEASE, time); +} + +bool AhdEnv::IsRunning() const { + return adsr.IsRunning(); +} diff --git a/optic/HachiKit/AhdEnv.h b/optic/HachiKit/AhdEnv.h new file mode 100644 index 000000000..7895cd60e --- /dev/null +++ b/optic/HachiKit/AhdEnv.h @@ -0,0 +1,29 @@ +#ifndef AHDENV_H +#define AHDENV_H + +#include "daisy_patch.h" +#include "daisysp.h" + +using namespace daisy; +using namespace daisysp; + +class AhdEnv { + + public: + void Init(float sample_rate); + float Process(); + void Trigger(); + void SetAttack(float time); + void SetHold(float time); + void SetDecay(float time); + void SetCurve(float scalar); + bool IsRunning() const; + + private: + AdEnv ad; + Adsr adsr; +}; + + + +#endif diff --git a/optic/HachiKit/Bd8.cpp b/optic/HachiKit/Bd8.cpp new file mode 100644 index 000000000..c24199de5 --- /dev/null +++ b/optic/HachiKit/Bd8.cpp @@ -0,0 +1,141 @@ +#include "Bd8.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + +void Bd8::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 78, 0.001, 4.0, 0.001, 0.1, 0.95); +} + +void Bd8::Init(std::string slot, float sample_rate, float frequency, float ampAttack, float ampDecay, float pitchAttack, float pitchDecay, float modAmount) { + + this->slot = slot; + + // oscillator settings + osc.Init(sample_rate); + SetParam(PARAM_FREQUENCY, frequency); + osc.SetWaveform(Oscillator::WAVE_SIN); + + // ampEnv settings + ampEnv.Init(sample_rate); + ampEnv.SetMax(1); + ampEnv.SetMin(0); + ampEnv.SetCurve(-100); + SetParam(PARAM_AMP_ATTACK, ampAttack); + SetParam(PARAM_AMP_DECAY, ampDecay); + + // pitchEnv settings + pitchEnv.Init(sample_rate); + pitchEnv.SetMax(1); + pitchEnv.SetMin(0); + pitchEnv.SetCurve(-100); + SetParam(PARAM_PITCH_ATTACK, pitchAttack); + SetParam(PARAM_PITCH_DECAY, pitchDecay); + SetParam(PARAM_MOD_AMT, modAmount); +} + +float Bd8::Process() { + float psig = pitchEnv.Process(); + osc.SetFreq(parameters[PARAM_FREQUENCY].GetScaledValue() + parameters[PARAM_MOD_AMT].GetScaledValue() * psig); + // osc.SetFreq(parameters[PARAM_FREQUENCY].GetScaledValue()); + return 2 * velocity * osc.Process() * ampEnv.Process(); +} + +void Bd8::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + osc.Reset(); + ampEnv.Trigger(); + pitchEnv.Trigger(); + } +} + +float Bd8::GetParam(uint8_t param) { + return param < PARAM_COUNT ? parameters[param].GetScaledValue() : 0.0f; +} + +std::string Bd8::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + case PARAM_MOD_AMT: + return std::to_string((int)GetParam(param));// + "hz"; + case PARAM_AMP_ATTACK: + case PARAM_AMP_DECAY: + case PARAM_PITCH_ATTACK: + case PARAM_PITCH_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + } + } + return ""; + } + +float Bd8::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 20, 5000, Parameter::EXPONENTIAL)); + break; + case PARAM_AMP_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + ampEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_AMP_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + ampEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_PITCH_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + pitchEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_PITCH_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + pitchEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_MOD_AMT: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0, 2000, Parameter::EXPONENTIAL)); + break; + } + } + + return scaled; +} + +void Bd8::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } +} + +void Bd8::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + parameters[param].SetScaledValue(scaled); + break; + case PARAM_AMP_ATTACK: + parameters[param].SetScaledValue(scaled); + ampEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_AMP_DECAY: + parameters[param].SetScaledValue(scaled); + ampEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_PITCH_ATTACK: + parameters[param].SetScaledValue(scaled); + pitchEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_PITCH_DECAY: + parameters[param].SetScaledValue(scaled); + pitchEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_MOD_AMT: + parameters[param].SetScaledValue(scaled); + break; + } + } + +} + diff --git a/optic/HachiKit/Bd8.h b/optic/HachiKit/Bd8.h new file mode 100644 index 000000000..a2c60c15b --- /dev/null +++ b/optic/HachiKit/Bd8.h @@ -0,0 +1,56 @@ +#ifndef BD8_H +#define BD8_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "Utility.h" +#include "Param.h" + +using namespace daisy; +using namespace daisysp; + +class Bd8: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 6; + // This is the order params will appear in the UI. + static const uint8_t PARAM_FREQUENCY = 0; + static const uint8_t PARAM_MOD_AMT = 1; + static const uint8_t PARAM_AMP_DECAY = 2; + static const uint8_t PARAM_PITCH_DECAY = 3; + static const uint8_t PARAM_AMP_ATTACK = 4; + static const uint8_t PARAM_PITCH_ATTACK = 5; + // TODO: add aCurve and pCurve + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float frequency, float ampAttack, float ampDecay, float pitchAttack, float pitchDecay, float modAmount); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + std::string GetParamString(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + + std::string Name() { return "Bd8"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Freq", "Mod", "aDcy", "pDcy", "aAtk", "pAtk" }; + std::string slot; + Param parameters[PARAM_COUNT]; + Oscillator osc; + AdEnv ampEnv; + AdEnv pitchEnv; + float velocity; + +}; + + + +#endif diff --git a/optic/HachiKit/BitArray.cpp b/optic/HachiKit/BitArray.cpp new file mode 100644 index 000000000..b25f67727 --- /dev/null +++ b/optic/HachiKit/BitArray.cpp @@ -0,0 +1,123 @@ +#include "BitArray.h" + +static const uint16_t mask = 1; +static const uint64_t nibMask = 15; + +/***** bit array functions ******************************/ + +bool BitArray16_Get(bit_array_16 array, u8 index) { + return (array & (mask << index)) != 0; +} + +bit_array_16 BitArray16_Set(bit_array_16 array, u8 index, bool value) { + if (value) { + return array | (mask << index); + } else { + return array & ~(mask << index); + } +} + +bit_array_16 BitArray16_Toggle(bit_array_16 array, u8 index) { + return array ^ (mask << index); +} + + +/***** nibble array functions ******************************/ + +u8 NibArray16_Get(nib_array_16& array, u8 index) { + if (index < 8) { + return (array.lo & (nibMask << (index*4))) >> (index*4); + } + index -= 8; + return (array.hi & (nibMask << (index*4))) >> (index*4); +} + +void NibArray16_Set(nib_array_16& array, u8 index, u8 value) { + if (index < 8) { + array.lo = (array.lo & ~(nibMask << (index*4))) | (value << (index*4)); + } + index -= 8; + array.hi = (array.hi & ~(nibMask << (index*4))) | (value << (index*4)); +} + +bool NibArray16_GetFlag(nib_array_16& array, u8 index) { + return (NibArray16_Get(array, index) & 8) != 0; +} + +u8 NibArray16_GetValue(nib_array_16& array, u8 index) { + return NibArray16_Get(array, index) & 7; +} +void NibArray16_SetFlag(nib_array_16& array, u8 index, bool flag) { + u8 value = NibArray16_GetValue(array, index); + NibArray16_Set(array, index, value | (flag<<3)); +} + +void NibArray16_ToggleFlag(nib_array_16& array, u8 index) { + bool toggleFlag = !NibArray16_GetFlag(array, index); + u8 value = NibArray16_GetValue(array, index); + NibArray16_Set(array, index, value | (toggleFlag<<3)); +} + +void NibArray16_SetValue(nib_array_16& array, u8 index, u8 value) { + bool flag = NibArray16_GetFlag(array, index); + return NibArray16_Set(array, index, value | (flag<<3)); +} + +// /***** nibble array functions ******************************/ + +// u8 NibArray16_Get(nib_array_16& array, u8 index) { +// // SERIALPRINTLN("NA16_Get: idx=" + String(index)); +// return (array & (nibMask << (index*4))) >> (index*4); +// } + +// void NibArray16_Set(nib_array_16& array, u8 index, u8 value) { +// // SERIALPRINTLN("NA16_Set: idx=" + String(index)); +// array = (array & ~(nibMask << (index*4))) | (value << (index*4)); +// } + +// // s8 SignedNib(u8 value) { +// // return value < 8 ? value : value - 16; +// // } + +// // u8 UnsignedNib(s8 value) { +// // return value >= 0 ? value : value + 16; +// // } + +// // s8 NibArray16_GetSigned(nib_array_16& array, u8 index) { +// // u8 value = NibArray16_Get(array, index); +// // s8 signedValue = SignedNib(value); +// // return signedValue; +// // } + +// // void NibArray16_SetSigned(nib_array_16& array, u8 index, s8 value) { +// // u8 unsignedValue = UnsignedNib(value); +// // NibArray16_Set(array, index, unsignedValue); +// // } + +// bool NibArray16_GetFlag(nib_array_16& array, u8 index) { +// // SERIALPRINTLN("NA16_GetFlag: idx=" + String(index) + ", nib=" + String(NibArray16_Get(array, index)) + ", flag=" + String((NibArray16_Get(array, index) & 8) != 0)); +// return (NibArray16_Get(array, index) & 8) != 0; +// } + +// u8 NibArray16_GetValue(nib_array_16& array, u8 index) { +// // SERIALPRINTLN("NA16_GetValue: idx=" + String(index) + ", nib=" + String(NibArray16_Get(array, index)) + ", flag=" + String(NibArray16_Get(array, index) & 7)); +// return NibArray16_Get(array, index) & 7; +// } +// void NibArray16_SetFlag(nib_array_16& array, u8 index, bool flag) { +// u8 value = NibArray16_GetValue(array, index); +// // SERIALPRINTLN("NA16_SetFlag: idx=" + String(index) + ", value=" + String(value) + ", flag=" + String(flag)); +// NibArray16_Set(array, index, value | (flag<<3)); +// } + +// void NibArray16_ToggleFlag(nib_array_16& array, u8 index) { +// bool toggleFlag = !NibArray16_GetFlag(array, index); +// u8 value = NibArray16_GetValue(array, index); +// // SERIALPRINTLN("NA16_ToggleFlag: idx=" + String(index) + ", value=" + String(value) + ", tflag=" + String(toggleFlag)); +// NibArray16_Set(array, index, value | (toggleFlag<<3)); +// } + +// void NibArray16_SetValue(nib_array_16& array, u8 index, u8 value) { +// bool flag = NibArray16_GetFlag(array, index); +// return NibArray16_Set(array, index, value | (flag<<3)); +// } + diff --git a/optic/HachiKit/BitArray.h b/optic/HachiKit/BitArray.h new file mode 100644 index 000000000..ab0948d79 --- /dev/null +++ b/optic/HachiKit/BitArray.h @@ -0,0 +1,44 @@ +#ifndef BITARRAY_H +#define BITARRAY_H + + +// typedef and convenient functions for treating a 16-bit uint as a bit array +// does not change the array in the func, but returns the modified form +typedef uint16_t bit_array_16; + +bool BitArray16_Get(bit_array_16 array, u8 index); +bit_array_16 BitArray16_Set(bit_array_16 array, u8 index, bool value); +bit_array_16 BitArray16_Toggle(bit_array_16 array, u8 index); + + +// typedef and convenient functions for a 4-bit (nibble) array +// nib array is treated as unsigned +// even though value is set/returned as a u8, only lower 4 bits are used +// these functions change the array in place +// +// typedef u64 nib_array_16; // <-- this did not work properly on arduino +struct nib_array_16 { + u32 lo = 0; + u32 hi = 0; +}; + +u8 NibArray16_Get(nib_array_16& array, u8 index); +void NibArray16_Set(nib_array_16& array, u8 index, u8 value); + +// these functions treat the nibble as a 4-bit signed 2s-complement value (i.e. where 1111 = -1) +// s8 SignedNib(u8 value); +// u8 UnsignedNib(s8 value); +// s8 NibArray16_GetSigned(nib_array_16& array, u8 index); +// void NibArray16_SetSigned(nib_array_16& array, u8 index, s8 value); + +// these functions treat the nibble as a 1-bit flag and a 3-bit unsigned value +bool NibArray16_GetFlag(nib_array_16& array, u8 index); +u8 NibArray16_GetValue(nib_array_16& array, u8 index); +void NibArray16_SetFlag(nib_array_16& array, u8 index, bool flag); +void NibArray16_ToggleFlag(nib_array_16& array, u8 index); +void NibArray16_SetValue(nib_array_16& array, u8 index, u8 value); + + + + +#endif diff --git a/optic/HachiKit/Blank.cpp b/optic/HachiKit/Blank.cpp new file mode 100644 index 000000000..7dc719ea4 --- /dev/null +++ b/optic/HachiKit/Blank.cpp @@ -0,0 +1,78 @@ +#include "Blank.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + +void Blank::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 220, 0.001, 0.5); +} + +void Blank::Init(std::string slot, float sample_rate, float frequency, float attack, float decay) { + + this->slot = slot; + + // initialize audio objects + SetParam(PARAM_FREQUENCY, frequency); + SetParam(PARAM_ATTACK, attack); + SetParam(PARAM_DECAY, decay); +} + +float Blank::Process() { + return 0.0f; +} + +void Blank::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + // trigger envelopes + } +} + +float Blank::GetParam(uint8_t param) { + return param < PARAM_COUNT ? parameters[param].GetScaledValue() : 0.0f; +} + +std::string Blank::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + return std::to_string((int)GetParam(param));// + "hz"; + case PARAM_ATTACK: + case PARAM_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + } + } + return ""; + } + +float Blank::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < Blank::PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 20, 5000, Parameter::EXPONENTIAL)); + break; + case PARAM_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + break; + case PARAM_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + break; + } + } + + return scaled; +} + +void Blank::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } +} + +void Blank::SetParam(uint8_t param, float value) { + if (param < PARAM_COUNT) { + parameters[param].SetScaledValue(value); + } +} diff --git a/optic/HachiKit/Blank.h b/optic/HachiKit/Blank.h new file mode 100644 index 000000000..fc1a7140d --- /dev/null +++ b/optic/HachiKit/Blank.h @@ -0,0 +1,50 @@ +#ifndef BLANK_H +#define BLANK_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "Utility.h" +#include "Param.h" + +using namespace daisy; +using namespace daisysp; + +class Blank: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 3; + // This is the order params will appear in the UI. + static const uint8_t PARAM_FREQUENCY = 0; + static const uint8_t PARAM_ATTACK = 1; + static const uint8_t PARAM_DECAY = 2; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float frequency, float attack, float decay); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + std::string GetParamString(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + + std::string Name() { return "Blank"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Freq", "Atk", "Dcy" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + // audio objects + +}; + + + +#endif diff --git a/optic/HachiKit/Ch.cpp b/optic/HachiKit/Ch.cpp new file mode 100644 index 000000000..4ec4b936f --- /dev/null +++ b/optic/HachiKit/Ch.cpp @@ -0,0 +1,136 @@ +#include "Ch.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + + +void Ch::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 0.001, 0.5, NULL, 0.5, 2000, 5000); +} + +void Ch::Init(std::string slot, float sample_rate, float attack, float decay, HhSource68 *source, float morph, float hpf, float lpf) { + + this->slot = slot; + + // env settings + env.Init(sample_rate); + env.SetMax(1); + env.SetMin(0); + env.SetCurve(-20); + SetParam(PARAM_ATTACK, attack); + SetParam(PARAM_DECAY, decay); + + // source settings + this->source = source; + SetParam(PARAM_MORPH, morph); + SetParam(PARAM_HPF, hpf); + SetParam(PARAM_LPF, lpf); + +} + +float Ch::Process() { + if (source == NULL) { + return 0.0f; + } + + float sig = source->Signal() * env.Process(); + return velocity * sig; +} + +void Ch::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + env.Trigger(); + } +} + +float Ch::GetParam(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + case PARAM_DECAY: + return parameters[param].GetScaledValue(); + case PARAM_MORPH: + return source->GetParam(HhSource68::PARAM_MORPH); + case PARAM_HPF: + return source->GetParam(HhSource68::PARAM_HPF); + case PARAM_LPF: + return source->GetParam(HhSource68::PARAM_LPF); + } + } + return 0.0f; +} + +std::string Ch::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + case PARAM_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + case PARAM_MORPH: + return std::to_string((int)(GetParam(param) * 100)); + case PARAM_HPF: + case PARAM_LPF: + return std::to_string((int)GetParam(param)); + } + } + return ""; + } + +float Ch::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < Ch::PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + env.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + env.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_MORPH: + source->UpdateParam(HhSource68::PARAM_MORPH, raw); + break; + case PARAM_HPF: + source->UpdateParam(HhSource68::PARAM_HPF, raw); + break; + case PARAM_LPF: + source->UpdateParam(HhSource68::PARAM_LPF, raw); + break; + } + } + + return scaled; +} + +void Ch::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } + source->ResetParams(); +} + +void Ch::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + parameters[param].SetScaledValue(scaled); + env.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_DECAY: + parameters[param].SetScaledValue(scaled); + env.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_MORPH: + source->SetParam(HhSource68::PARAM_MORPH, scaled); + break; + case PARAM_HPF: + source->SetParam(HhSource68::PARAM_HPF, scaled); + break; + case PARAM_LPF: + source->SetParam(HhSource68::PARAM_LPF, scaled); + break; + } + }} diff --git a/optic/HachiKit/Ch.h b/optic/HachiKit/Ch.h new file mode 100644 index 000000000..7001b25e9 --- /dev/null +++ b/optic/HachiKit/Ch.h @@ -0,0 +1,57 @@ +#ifndef CH_H +#define CH_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "ISource.h" +#include "Utility.h" +#include "Param.h" +#include "HhSource68.h" + +using namespace daisy; +using namespace daisysp; + +class Ch: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 5; + // This is the order params will appear in the UI. + static const uint8_t PARAM_ATTACK = 0; + static const uint8_t PARAM_DECAY = 1; + // These are pass-thru params that belong to the sound source and aren't tracked in Ch + static const uint8_t PARAM_MORPH = 2; + static const uint8_t PARAM_HPF = 3; + static const uint8_t PARAM_LPF = 4; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float attack, float decay, HhSource68 *source, float morph, float hpf, float lpf); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + std::string GetParamString(uint8_t param); + + std::string Name() { return "Blank"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Atk", "Dcy", "Mrph", "Hpf", "Lpf" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + // ISource *source = NULL; + HhSource68 *source = NULL; + AdEnv env; + +}; + + + +#endif diff --git a/optic/HachiKit/Clap.cpp b/optic/HachiKit/Clap.cpp new file mode 100644 index 000000000..72bc1f5dd --- /dev/null +++ b/optic/HachiKit/Clap.cpp @@ -0,0 +1,99 @@ +#include "Clap.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + +void Clap::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 0.012, 1.0); +} + +void Clap::Init(std::string slot, float sample_rate, float spread, float decay) { + + this->slot = slot; + + noise.Init(); + + env.Init(sample_rate); + env.SetMax(1); + env.SetMin(0); + env.SetCurve(-20); + env.SetTime(ADENV_SEG_ATTACK, 0.001); + SetParam(PARAM_DECAY, decay); + SetParam(PARAM_SPREAD, spread); +} + +float Clap::Process() { + if (!env.IsRunning()) { + repeat++; + if (repeat == REPEATS) { + env.SetTime(ADENV_SEG_DECAY, GetParam(PARAM_DECAY)); + } + if (repeat <= REPEATS) { + env.Trigger(); + } + } + return velocity * noise.Process() * env.Process(); +} + +void Clap::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + repeat = 0; + env.SetTime(ADENV_SEG_DECAY, GetParam(PARAM_SPREAD)); + env.Trigger(); + } +} + +float Clap::GetParam(uint8_t param) { + return param < PARAM_COUNT ? parameters[param].GetScaledValue() : 0.0f; +} + +std::string Clap::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_SPREAD: + case PARAM_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + } + } + return ""; + } + +float Clap::UpdateParam(uint8_t param, float raw) { + return SetParam(param, raw, true); +} + +void Clap::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } +} + +void Clap::SetParam(uint8_t param, float scaled) { + SetParam(param, scaled, false); +} + +float Clap::SetParam(uint8_t param, float value, bool isRaw) { + float scaled = value; + if (param < Clap::PARAM_COUNT) { + switch (param) { + case PARAM_SPREAD: + if (isRaw) { + scaled = parameters[param].Update(value, Utility::ScaleFloat(value, 0.001, 0.050, Parameter::LINEAR)); + } else { + parameters[param].SetScaledValue(value); + } + return scaled; + case PARAM_DECAY: + if (isRaw) { + scaled = parameters[param].Update(value, Utility::ScaleFloat(value, 0.001, 3, Parameter::EXPONENTIAL)); + } else { + parameters[param].SetScaledValue(value); + } + return scaled; + } + } + + return 0.0f; +} \ No newline at end of file diff --git a/optic/HachiKit/Clap.h b/optic/HachiKit/Clap.h new file mode 100644 index 000000000..6c19a3b4e --- /dev/null +++ b/optic/HachiKit/Clap.h @@ -0,0 +1,56 @@ +#ifndef CLAP_H +#define CLAP_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "Utility.h" +#include "Param.h" + +using namespace daisy; +using namespace daisysp; + +class Clap: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 2; + // This is the order params will appear in the UI. + static const uint8_t PARAM_SPREAD = 0; + static const uint8_t PARAM_DECAY = 1; + + static const uint8_t REPEATS = 3; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float spread, float decay); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + std::string GetParamString(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + + std::string Name() { return "Blank"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Sprd", "Dcy" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + float repeat; + // audio objects + WhiteNoise noise; + AdEnv env; + + float SetParam(uint8_t param, float value, bool isRaw); + +}; + + + +#endif diff --git a/optic/HachiKit/ClickSource.cpp b/optic/HachiKit/ClickSource.cpp new file mode 100644 index 000000000..4a3b6c022 --- /dev/null +++ b/optic/HachiKit/ClickSource.cpp @@ -0,0 +1,114 @@ +#include "ClickSource.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + + +void ClickSource::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 1500, 191, 116); +} + +void ClickSource::Init(std::string slot, float sample_rate, float hpfFreq, float lpfFreq, float mod) { + + this->slot = slot; + + noise.Init(); + + lpfEnv.Init(sample_rate); + lpfEnv.SetMax(1); + lpfEnv.SetMin(0); + lpfEnv.SetCurve(-20); + lpfEnv.SetTime(ADENV_SEG_ATTACK, 0.001); + lpfEnv.SetTime(ADENV_SEG_DECAY, 0.09); + + hpf.Init(sample_rate); + hpf.SetRes(0.2); + hpf.SetDrive(.002); + SetParam(PARAM_HPF, hpfFreq); + + lpf.Init(sample_rate); + lpf.SetRes(0.2); + lpf.SetDrive(.002); + SetParam(PARAM_LPF, lpfFreq); + SetParam(PARAM_LPF_MOD, mod); +} + +float ClickSource::Process() { + // noise goes through a hpf, then through an lpf modulated by the lpf env. + // lpf env also controls amp of noise + float lpfEnvSignal = lpfEnv.Process(); + lpf.SetFreq(1000 + parameters[PARAM_LPF_MOD].GetScaledValue() * lpfEnvSignal); + hpf.Process(noise.Process()); + lpf.Process(hpf.High()); + signal = lpf.Low() * lpfEnvSignal; + return signal; +} + +void ClickSource::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + lpfEnv.Trigger(); + } +} + +float ClickSource::GetParam(uint8_t param) { + return param < PARAM_COUNT ? parameters[param].GetScaledValue() : 0.0f; +} + +std::string ClickSource::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_LPF_MOD: + case PARAM_HPF: + case PARAM_LPF: + return std::to_string((int)GetParam(param)); + } + } + return ""; + } + +float ClickSource::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < ClickSource::PARAM_COUNT) { + switch (param) { + case PARAM_LPF_MOD: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0, 2000, Parameter::EXPONENTIAL)); + break; + case PARAM_HPF: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 20, 3000, Parameter::EXPONENTIAL)); + hpf.SetFreq(scaled); + break; + case PARAM_LPF: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 20, 3000, Parameter::EXPONENTIAL)); + lpf.SetFreq(scaled); + break; + } + } + + return scaled; +} + +void ClickSource::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } +} + +void ClickSource::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_LPF_MOD: + parameters[param].SetScaledValue(scaled); + break; + case PARAM_HPF: + parameters[param].SetScaledValue(scaled); + hpf.SetFreq(scaled); + break; + case PARAM_LPF: + parameters[param].SetScaledValue(scaled); + lpf.SetFreq(scaled); + break; + } + } +} diff --git a/optic/HachiKit/ClickSource.h b/optic/HachiKit/ClickSource.h new file mode 100644 index 000000000..5d3dd3686 --- /dev/null +++ b/optic/HachiKit/ClickSource.h @@ -0,0 +1,55 @@ +#ifndef CLICKSOURCE_H +#define CLICKSOURCE_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "ISource.h" +#include "Utility.h" +#include "Param.h" + +using namespace daisy; +using namespace daisysp; + +class ClickSource: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 3; + // This is the order params will appear in the UI. + static const uint8_t PARAM_HPF = 0; + static const uint8_t PARAM_LPF = 1; + static const uint8_t PARAM_LPF_MOD = 2; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float hpfFreq, float lpfFreq, float mod); + float Process(); + void Trigger(float velocity); + float Signal() { return signal; } + + float GetParam(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + std::string GetParamString(uint8_t param); + + std::string Name() { return "Tom"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Hpf", "Lpf", "fMod" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + float signal; + WhiteNoise noise; + Svf hpf, lpf; + AdEnv lpfEnv; + +}; + + + +#endif diff --git a/optic/HachiKit/Cow8.cpp b/optic/HachiKit/Cow8.cpp new file mode 100644 index 000000000..01b3925a4 --- /dev/null +++ b/optic/HachiKit/Cow8.cpp @@ -0,0 +1,142 @@ +#include "Cow8.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + + +const float Cow8::HPF_MAX = 12000; +const float Cow8::HPF_MIN = 100; +const float Cow8::LPF_MAX = 18000; +const float Cow8::LPF_MIN = 100; + + +void Cow8::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 0.001, 3.5, NULL, 1700, 2300); +} + +void Cow8::Init(std::string slot, float sample_rate, float attack, float decay, HhSource68 *source, float hpfCutoff, float lpfCutoff) { + + this->slot = slot; + + // env settings + env.Init(sample_rate); + env.SetMax(1); + env.SetMin(0); + env.SetCurve(-20); + SetParam(PARAM_ATTACK, attack); + SetParam(PARAM_DECAY, decay); + + hpf.Init(sample_rate); + hpf.SetRes(0.2); + hpf.SetDrive(.002); + hpf.SetFreq(hpfCutoff); + + lpf.Init(sample_rate); + lpf.SetRes(0.2); + lpf.SetDrive(.002); + lpf.SetFreq(lpfCutoff); + + this->source = source; + +} + +float Cow8::Process() { + if (source == NULL) { + return 0.0f; + } + + float sig = source->Cowbell(false) * env.Process(); + hpf.Process(sig); + lpf.Process(hpf.High()); + return velocity * lpf.Low();; +} + +void Cow8::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + env.Trigger(); + } +} + +float Cow8::GetParam(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + case PARAM_DECAY: + case PARAM_HPF: + case PARAM_LPF: + return parameters[param].GetScaledValue(); + } + } + return 0.0f; +} + +std::string Cow8::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + case PARAM_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + case PARAM_HPF: + case PARAM_LPF: + return std::to_string((int)GetParam(param)); + } + } + return ""; + } + +float Cow8::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < Cow8::PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + env.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 15, Parameter::EXPONENTIAL)); + env.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_HPF: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, HPF_MIN, HPF_MAX, Parameter::EXPONENTIAL)); + hpf.SetFreq(scaled); + break; + case PARAM_LPF: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, LPF_MIN, LPF_MAX, Parameter::EXPONENTIAL)); + lpf.SetFreq(scaled); + break; + } + } + + return scaled; +} + +void Cow8::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } + source->ResetParams(); +} + +void Cow8::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + parameters[param].SetScaledValue(scaled); + env.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_DECAY: + parameters[param].SetScaledValue(scaled); + env.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_HPF: + parameters[param].SetScaledValue(scaled); + hpf.SetFreq(scaled); + break; + case PARAM_LPF: + parameters[param].SetScaledValue(scaled); + lpf.SetFreq(scaled); + break; + } + }} diff --git a/optic/HachiKit/Cow8.h b/optic/HachiKit/Cow8.h new file mode 100644 index 000000000..cec8235d6 --- /dev/null +++ b/optic/HachiKit/Cow8.h @@ -0,0 +1,62 @@ +#ifndef COW8_H +#define COW8_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "ISource.h" +#include "Utility.h" +#include "Param.h" +#include "HhSource68.h" + +using namespace daisy; +using namespace daisysp; + +class Cow8: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 4; + // This is the order params will appear in the UI. + static const uint8_t PARAM_ATTACK = 0; + static const uint8_t PARAM_DECAY = 1; + // These are pass-thru params that belong to the sound source and aren't tracked in Ch + static const uint8_t PARAM_HPF = 2; + static const uint8_t PARAM_LPF = 3; + + static const float HPF_MAX; + static const float HPF_MIN; + static const float LPF_MAX; + static const float LPF_MIN; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float attack, float decay, HhSource68 *source, float hpfCutoff, float lpfCutoff); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + std::string GetParamString(uint8_t param); + + std::string Name() { return "Blank"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Atk", "Dcy", "Hpf", "Lpf" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + // ISource *source = NULL; + HhSource68 *source = NULL; + AdEnv env; + Svf hpf, lpf; + +}; + + + +#endif diff --git a/optic/HachiKit/Cy.cpp b/optic/HachiKit/Cy.cpp new file mode 100644 index 000000000..62740d89f --- /dev/null +++ b/optic/HachiKit/Cy.cpp @@ -0,0 +1,142 @@ +#include "Cy.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + + +const float Cy::HPF_MAX = 12000; +const float Cy::HPF_MIN = 100; +const float Cy::LPF_MAX = 18000; +const float Cy::LPF_MIN = 100; + + +void Cy::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 0.001, 3.5, NULL, 1700, 2300); +} + +void Cy::Init(std::string slot, float sample_rate, float attack, float decay, HhSource68 *source, float hpfCutoff, float lpfCutoff) { + + this->slot = slot; + + // env settings + env.Init(sample_rate); + env.SetMax(1); + env.SetMin(0); + env.SetCurve(-4); + SetParam(PARAM_ATTACK, attack); + SetParam(PARAM_DECAY, decay); + + hpf.Init(sample_rate); + hpf.SetRes(0.2); + hpf.SetDrive(.002); + SetParam(PARAM_HPF, hpfCutoff); + + lpf.Init(sample_rate); + lpf.SetRes(0.2); + lpf.SetDrive(.002); + SetParam(PARAM_LPF, lpfCutoff); + + this->source = source; + +} + +float Cy::Process() { + if (source == NULL) { + return 0.0f; + } + + float sig = source->Signal() * env.Process(); + hpf.Process(sig); + lpf.Process(hpf.High()); + return velocity * lpf.Low();; +} + +void Cy::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + env.Trigger(); + } +} + +float Cy::GetParam(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + case PARAM_DECAY: + case PARAM_HPF: + case PARAM_LPF: + return parameters[param].GetScaledValue(); + } + } + return 0.0f; +} + +std::string Cy::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + case PARAM_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + case PARAM_HPF: + case PARAM_LPF: + return std::to_string((int)GetParam(param)); + } + } + return ""; + } + +float Cy::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < Cy::PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + env.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 15, Parameter::EXPONENTIAL)); + env.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_HPF: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, HPF_MIN, HPF_MAX, Parameter::EXPONENTIAL)); + hpf.SetFreq(scaled); + break; + case PARAM_LPF: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, LPF_MIN, LPF_MAX, Parameter::EXPONENTIAL)); + lpf.SetFreq(scaled); + break; + } + } + + return scaled; +} + +void Cy::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } + source->ResetParams(); +} + +void Cy::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + parameters[param].SetScaledValue(scaled); + env.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_DECAY: + parameters[param].SetScaledValue(scaled); + env.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_HPF: + parameters[param].SetScaledValue(scaled); + hpf.SetFreq(scaled); + break; + case PARAM_LPF: + parameters[param].SetScaledValue(scaled); + lpf.SetFreq(scaled); + break; + } + }} diff --git a/optic/HachiKit/Cy.h b/optic/HachiKit/Cy.h new file mode 100644 index 000000000..429b5a852 --- /dev/null +++ b/optic/HachiKit/Cy.h @@ -0,0 +1,62 @@ +#ifndef CY_H +#define CY_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "ISource.h" +#include "Utility.h" +#include "Param.h" +#include "HhSource68.h" + +using namespace daisy; +using namespace daisysp; + +class Cy: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 4; + // This is the order params will appear in the UI. + static const uint8_t PARAM_ATTACK = 0; + static const uint8_t PARAM_DECAY = 1; + // These are pass-thru params that belong to the sound source and aren't tracked in Ch + static const uint8_t PARAM_HPF = 2; + static const uint8_t PARAM_LPF = 3; + + static const float HPF_MAX; + static const float HPF_MIN; + static const float LPF_MAX; + static const float LPF_MIN; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float attack, float decay, HhSource68 *source, float hpfCutoff, float lpfCutoff); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + std::string GetParamString(uint8_t param); + + std::string Name() { return "Blank"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Atk", "Dcy", "Hpf", "Lpf" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + // ISource *source = NULL; + HhSource68 *source = NULL; + AdEnv env; + Svf hpf, lpf; + +}; + + + +#endif diff --git a/optic/HachiKit/DigiClap.cpp b/optic/HachiKit/DigiClap.cpp new file mode 100644 index 000000000..55c2c718e --- /dev/null +++ b/optic/HachiKit/DigiClap.cpp @@ -0,0 +1,111 @@ +#include "DigiClap.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + +void DigiClap::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 0.012f, 1.0f, 3000.0f); +} + +void DigiClap::Init(std::string slot, float sample_rate, float spread, float decay, float frequency) { + + this->slot = slot; + + clockedNoise.Init(sample_rate); + SetParam(PARAM_FREQUENCY, frequency); + clockedNoise.SetFreq(frequency); + + env.Init(sample_rate); + env.SetMax(1); + env.SetMin(0); + env.SetCurve(-20); + env.SetTime(ADENV_SEG_ATTACK, 0.001); + SetParam(PARAM_DECAY, decay); + SetParam(PARAM_SPREAD, spread); +} + +float DigiClap::Process() { + if (!env.IsRunning()) { + repeat++; + if (repeat == REPEATS) { + env.SetTime(ADENV_SEG_DECAY, GetParam(PARAM_DECAY)); + } + if (repeat <= REPEATS) { + env.Trigger(); + } + } + return velocity * clockedNoise.Process() * env.Process(); +} + +void DigiClap::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + repeat = 0; + env.SetTime(ADENV_SEG_DECAY, GetParam(PARAM_SPREAD)); + env.Trigger(); + } +} + +float DigiClap::GetParam(uint8_t param) { + return param < PARAM_COUNT ? parameters[param].GetScaledValue() : 0.0f; +} + +std::string DigiClap::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_SPREAD: + case PARAM_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + case PARAM_FREQUENCY: + return std::to_string((int)(GetParam(param))); + } + } + return ""; + } + +float DigiClap::UpdateParam(uint8_t param, float raw) { + return SetParam(param, raw, true); +} + +void DigiClap::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } +} + +void DigiClap::SetParam(uint8_t param, float scaled) { + SetParam(param, scaled, false); +} + +float DigiClap::SetParam(uint8_t param, float value, bool isRaw) { + float scaled = value; + if (param < DigiClap::PARAM_COUNT) { + switch (param) { + case PARAM_SPREAD: + if (isRaw) { + scaled = parameters[param].Update(value, Utility::ScaleFloat(value, 0.001, 0.050, Parameter::LINEAR)); + } else { + parameters[param].SetScaledValue(value); + } + return scaled; + case PARAM_DECAY: + if (isRaw) { + scaled = parameters[param].Update(value, Utility::ScaleFloat(value, 0.001, 5, Parameter::EXPONENTIAL)); + } else { + parameters[param].SetScaledValue(value); + } + return scaled; + case PARAM_FREQUENCY: + if (isRaw) { + scaled = parameters[param].Update(value, Utility::ScaleFloat(value, 20, 15000, Parameter::EXPONENTIAL)); + } else { + parameters[param].SetScaledValue(value); + } + clockedNoise.SetFreq(scaled); + return scaled; + } + } + + return 0.0f; +} \ No newline at end of file diff --git a/optic/HachiKit/DigiClap.h b/optic/HachiKit/DigiClap.h new file mode 100644 index 000000000..88f1ddd88 --- /dev/null +++ b/optic/HachiKit/DigiClap.h @@ -0,0 +1,57 @@ +#ifndef DIGICLAP_H +#define DIGICLAP_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "Utility.h" +#include "Param.h" + +using namespace daisy; +using namespace daisysp; + +class DigiClap: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 3; + // This is the order params will appear in the UI. + static const uint8_t PARAM_SPREAD = 0; + static const uint8_t PARAM_DECAY = 1; + static const uint8_t PARAM_FREQUENCY = 2; + + static const uint8_t REPEATS = 3; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float spread, float decay, float frequency); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + std::string GetParamString(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + + std::string Name() { return "Blank"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Sprd", "Dcy", "Freq" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + float repeat; + // audio objects + ClockedNoise clockedNoise; + AdEnv env; + + float SetParam(uint8_t param, float value, bool isRaw); + +}; + + + +#endif diff --git a/optic/HachiKit/FmDrum.cpp b/optic/HachiKit/FmDrum.cpp new file mode 100644 index 000000000..d1e2d6961 --- /dev/null +++ b/optic/HachiKit/FmDrum.cpp @@ -0,0 +1,132 @@ +#include "FmDrum.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + +void FmDrum::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 68, 3.3, 2.2, 0.001, 0.043, -50); +} + +void FmDrum::Init(std::string slot, float sample_rate, float frequency, float ratio, float modAmount, float attack, float decay, float curve) { + + this->slot = slot; + + // 2-osc fm object + fm.Init(sample_rate); + SetParam(PARAM_FREQUENCY, frequency); + SetParam(PARAM_RATIO, ratio); + SetParam(PARAM_MOD_AMT, modAmount); + + ampEnv.Init(sample_rate); + ampEnv.SetMax(1); + ampEnv.SetMin(0); + SetParam(PARAM_ATTACK, attack); + SetParam(PARAM_DECAY, decay); + SetParam(PARAM_ENV_CURVE, curve); +} + +float FmDrum::Process() { + return velocity * fm.Process() * ampEnv.Process(); +} + +void FmDrum::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + fm.Reset(); + ampEnv.Trigger(); + } +} + +float FmDrum::GetParam(uint8_t param) { + return param < PARAM_COUNT ? parameters[param].GetScaledValue() : 0.0f; +} + +std::string FmDrum::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + return std::to_string((int)GetParam(param));// + "hz"; + case PARAM_RATIO: + return Utility::FloatToString2(GetParam(param)); + case PARAM_MOD_AMT: + return Utility::FloatToString2(GetParam(param)); + case PARAM_ATTACK: + case PARAM_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + case PARAM_ENV_CURVE: + return std::to_string((int)GetParam(param)); + } + } + return ""; + } + +float FmDrum::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 20, 5000, Parameter::EXPONENTIAL)); + fm.SetFrequency(scaled); + break; + case PARAM_RATIO: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.125, 8, Parameter::EXPONENTIAL)); + fm.SetRatio(scaled); + break; + case PARAM_MOD_AMT: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0, 5, Parameter::EXPONENTIAL)); + fm.SetIndex(scaled); + break; + case PARAM_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + ampEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + ampEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_ENV_CURVE: + scaled = raw; + // TODO: set the curve + break; + } + } + + return scaled; +} + +void FmDrum::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } +} + +void FmDrum::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + parameters[param].SetScaledValue(scaled); + fm.SetFrequency(scaled); + break; + case PARAM_RATIO: + parameters[param].SetScaledValue(scaled); + fm.SetRatio(scaled); + break; + case PARAM_MOD_AMT: + parameters[param].SetScaledValue(scaled); + fm.SetIndex(scaled); + break; + case PARAM_ATTACK: + parameters[param].SetScaledValue(scaled); + ampEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_DECAY: + parameters[param].SetScaledValue(scaled); + ampEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_ENV_CURVE: + parameters[param].SetScaledValue(scaled); + // TODO: set the curve + break; + } + }} diff --git a/optic/HachiKit/FmDrum.h b/optic/HachiKit/FmDrum.h new file mode 100644 index 000000000..078df14b3 --- /dev/null +++ b/optic/HachiKit/FmDrum.h @@ -0,0 +1,54 @@ +#ifndef FMDRUM_H +#define FMDRUM_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "Utility.h" +#include "Param.h" + +using namespace daisy; +using namespace daisysp; + +class FmDrum: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 6; + // This is the order params will appear in the UI. + static const uint8_t PARAM_FREQUENCY = 0; + static const uint8_t PARAM_RATIO = 1; + static const uint8_t PARAM_MOD_AMT = 2; + static const uint8_t PARAM_DECAY = 3; + static const uint8_t PARAM_ATTACK = 4; + static const uint8_t PARAM_ENV_CURVE = 5; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float frequency, float ratio, float modAmount, float attack, float decay, float curve); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + std::string GetParamString(uint8_t param); + + std::string Name() { return "Blank"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Freq", "Ratio", "Mod", "Dcy", "Atk", "Curve" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + Fm2 fm; + AdEnv ampEnv; + +}; + + + +#endif diff --git a/optic/HachiKit/HachiKit.cpp b/optic/HachiKit/HachiKit.cpp new file mode 100644 index 000000000..bf157e1fa --- /dev/null +++ b/optic/HachiKit/HachiKit.cpp @@ -0,0 +1,374 @@ +#include +#include + +#include "daisy_patch.h" +#include "daisysp.h" +#include "util/CpuLoadMeter.h" +#include +#include "Utility.h" +#include "BitArray.h" +#include "Screen.h" +#include "IDrum.h" +#include "Bd8.h" +#include "Sd8.h" +#include "SdNoise.h" +#include "FmDrum.h" +#include "HhSource68.h" +#include "ClickSource.h" +#include "Ch.h" +#include "Oh.h" +#include "Cy.h" +#include "Cow8.h" +#include "Tom.h" +#include "Clap.h" +#include "DigiClap.h" + +using namespace daisy; +using namespace daisysp; + +#define MINIMUM_NOTE 36 +#define KNOB_COUNT 4 +#define CPU_SINGLE false + +DaisyPatch hw; +Screen screen(&hw.display); +CpuLoadMeter meter; + +IDrum *drums[16]; +uint8_t drumCount = 1; +Bd8 bd; +SdNoise rs; +Sd8 sd; +Clap cp; +DigiClap sd2; +Tom lt, mt, ht; +Ch ch; +Oh oh; +Cy cy; +Cow8 cb; +FmDrum fm1, fm2; + +// Shared sound sources +HhSource68 source68; +ClickSource clickSource; + +uint8_t currentDrum = 0; +uint8_t currentKnobRow = 0; +u8 maxDrum = 1; +float lastKnobValue[KNOB_COUNT]; + +u8 cycle = 0; +u8 cycleLength = 8; +float savedSignal = 0.0f; +u32 usageCounter = 0; + + + + +// Display the available parameter names. +void DisplayParamMenu() { + + screen.DrawRect(0, 9, 127, 36, false, true); + // hw.display.DrawLine(0,12,127,12, true); + // hw.display.DrawLine(0,33,127,33, true); + // hw.display.DrawLine(0,44,127,44, true); + + uint8_t param; + for (int knob = 0; knob < KNOB_COUNT; knob++) { + // for (u8 row = 0; row <= drums[currentDrum]->PARAM_COUNT / 4; row++) { + for (u8 row = 0; row < 2; row++) { + Rectangle rect2(knob * 32, (row + 1) * 12, 32, 12); + param = row * KNOB_COUNT + knob; + std::string sc = drums[currentDrum]->GetParamName(param); + bool selected = row == currentKnobRow; + // hw.display.WriteStringAligned(sc.c_str(), Font_6x8, rect2, Alignment::centered, true); + screen.DrawButton(rect2, sc, selected, selected, !selected); + // hw.display.SetCursor(rect2.GetX(), rect2.GetY()); + // hw.display.WriteString(sc.c_str(), Font_6x8, true); + // hw.display.DrawLine(0, rect2.GetY() + 11, 127, rect2.GetY() + 11, true); + } + } + + // screen.DrawLine(0,11,127,11, true); + // screen.DrawLine(0,36,127,36, true); + +} + +// Display the current values and parameter names of model params for 4 knobs. +// Knob number == param number, since model params are listed in UI order. +void DisplayKnobValues() { + + screen.DrawRect(0, 0, 127, 11, false, true); + // screen.DrawLine(0,10,127,10, true); + + uint8_t param; + for (int knob = 0; knob < KNOB_COUNT; knob++) { + // top row: current param values from knobs + param = currentKnobRow * KNOB_COUNT + knob; + Rectangle rect(knob * 32, 0, 32, 8); + std::string sc = drums[currentDrum]->GetParamString(param); + screen.WriteStringAligned(sc.c_str(), Font_6x8, rect, Alignment::centered, true); + // screen.DrawButton(rect, sc, false, true, false); + + // for (u8 row = 0; row <= drums[currentDrum]->PARAM_COUNT / 4; row++) { + // Rectangle rect2(knob * 32, (row + 1) * 11, 32, 11); + // param = row * KNOB_COUNT + knob; + // sc = drums[currentDrum]->GetParamName(param); + // bool selected = row == currentKnobRow; + // // hw.display.WriteStringAligned(sc.c_str(), Font_6x8, rect2, Alignment::centered, true); + // screen.DrawButton(rect2, sc, selected, selected, !selected); + // // hw.display.SetCursor(rect2.GetX(), rect2.GetY()); + // // hw.display.WriteString(sc.c_str(), Font_6x8, true); + // hw.display.DrawLine(0, rect2.GetY() + 11, 127, rect2.GetY() + 11, true); + // } + } +} + +void DrawScreen(bool clearFirst) { + if (clearFirst) { + hw.display.Fill(false); + } + DisplayParamMenu(); + DisplayKnobValues(); + screen.DrawMenu(currentDrum); + hw.display.Update(); +} + +void ProcessEncoder() { + + bool redraw = false; + int inc = hw.encoder.Increment(); + int newDrum = Utility::LimitInt(currentDrum + inc, 0, drumCount-1); + if (newDrum != currentDrum) { + drums[newDrum]->ResetParams(); + currentDrum = newDrum; + redraw = true; + usageCounter = 0; + screen.SetScreenOn(true); + hw.display.Fill(false); + } + + if (hw.encoder.RisingEdge()) { + currentKnobRow = (currentKnobRow + 1) % 2; + redraw = true; + drums[currentDrum]->ResetParams(); + usageCounter = 0; + screen.SetScreenOn(true); + hw.display.Fill(false); + } + + if (redraw) { + DrawScreen(false); + hw.display.Update(); + } +} + +// Process the current knob values and update model params accordingly. +// Knob number == param number, since model params are listed in UI order. +void ProcessKnobs() { + + for (int knob = 0; knob < KNOB_COUNT; knob++) { + float sig = hw.controls[knob].Value(); + uint8_t param = currentKnobRow * KNOB_COUNT + knob; + drums[currentDrum]->UpdateParam(param, sig); + if (std::abs(sig - lastKnobValue[knob]) > 0.1f) { // TODO: use delta value from Param? + usageCounter = 0; + screen.SetScreenOn(true); + lastKnobValue[knob] = sig; + DrawScreen(true); + } + + } +} + +void ProcessControls() +{ + hw.ProcessAnalogControls(); + hw.ProcessDigitalControls(); + + ProcessEncoder(); + ProcessKnobs(); + // ProcessGates(); +} + + +void AudioCallback(AudioHandle::InputBuffer in, + AudioHandle::OutputBuffer out, + size_t size) +{ + meter.OnBlockStart(); + + ProcessControls(); + + for(size_t i = 0; i < size; i++) { + + // Process shared sound sources + source68.Process(); + clickSource.Process(); + + float sig = 0.0f; + float limited = 0.0f; + cycle = (cycle + 1) % 8; + if (cycle < 9) { + if (CPU_SINGLE) { + sig = drums[currentDrum]->Process(); + limited = sig; + } else { + for (uint8_t i = 0; i < drumCount; i++) { + sig += drums[i]->Process(); + } + limited = sig / drumCount; + } + } + + out[0][i] = out[1][i] = limited; + out[2][i] = out[3][i] = sig; + } + + meter.OnBlockEnd(); +} + + +// Typical Switch case for Message Type. +void HandleMidiMessage(MidiEvent m) +{ + + switch(m.type) + { + case NoteOn: + { + // NoteOnEvent p = m.AsNoteOn(); + // This is to avoid Max/MSP Note outs for now.. + // if(m.data[1] != 0) + // { + // p = m.AsNoteOn(); + // osc.SetFreq(mtof(p.note)); + // osc.SetAmp((p.velocity / 127.0f)); + // } else { + // osc.SetAmp(0.0f); + // } + // screen.OledMessage("Midi: " + std::to_string(p.note) + ", " + std::to_string(p.velocity) + " ", 3); + + NoteOnEvent p = m.AsNoteOn(); + float velocity = p.velocity / 127.0f; + if (p.velocity > 0) { + if (p.note >= MINIMUM_NOTE && p.note < MINIMUM_NOTE + drumCount) { + u8 drum = p.note - MINIMUM_NOTE; + drums[drum]->Trigger(velocity); + screen.ScreensaveEvent(drum); + } + } + } + break; + case NoteOff: + { + } + break; + case ControlChange: + { + ControlChangeEvent p = m.AsControlChange(); + switch(p.control_number) + { + case 1: + break; + case 2: + break; + default: break; + } + break; + } + default: break; + } +} + + +// Main -- Init, and Midi Handling +int main(void) +{ + // Init + float samplerate; + hw.Init(true); // "true" boosts processor speed from 400mhz to 480mhz + hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_32KHZ); // 8,16,32,48 + samplerate = hw.AudioSampleRate(); + + meter.Init(samplerate, 128, 1.0f); + + // Shared sound sources + source68.Init("", samplerate, HhSource68::MORPH_808_VALUE); + clickSource.Init("", samplerate, 1500, 191, 116); + + bd.Init("BD", samplerate); + rs.Init("RS", samplerate); + sd.Init("SD", samplerate); + cp.Init("CP", samplerate, 0.012, 0.8); + + sd2.Init("S2", samplerate, 0.012, 0.8, 3000); + lt.Init("LT", samplerate, 80, &clickSource); + mt.Init("MT", samplerate, 91, &clickSource); + ht.Init("HT", samplerate, 106, &clickSource); + + ch.Init("CH", samplerate, 0.001, 0.5, &source68, HhSource68::MORPH_808_VALUE, 6000, 16000); + oh.Init("OH", samplerate, 0.001, 0.13, 0.05, &source68, HhSource68::MORPH_808_VALUE, 6000, 16000); + cy.Init("CY", samplerate, 0.001, 3.5, &source68, 1700, 2400); + cb.Init("CB", samplerate, 0.001, 0.5, &source68, 1700, 2400); + fm1.Init("LC", samplerate, 98, 3.3, 2.2, 0.001, 0.101, -50); + fm2.Init("HC", samplerate, 131, 3.3, 2.2, 0.001, 0.101, -50); + + drums[0] = &bd; + drums[1] = &rs; + drums[2] = &sd; + drums[3] = &cp; + drums[4] = &sd2; + drums[5] = < + drums[6] = &ch; + drums[7] = &mt; + drums[8] = &oh; + drums[9] = &ht; + drums[10] = &cy; + drums[11] = &fm1; + drums[12] = &fm2; + drums[13] = &cb; + drumCount = 14; + currentDrum = 0; + + for (u8 i = 0; i < KNOB_COUNT; i++) { + lastKnobValue[i] = 0.0f; + } + + //display + hw.display.Fill(false); + // screen.OledMessage("Hachikit 0.1", 5); + // Utility::DrawDrums(&hw.display, 5); + screen.DrawMenu(currentDrum); + DisplayParamMenu(); + hw.display.Update(); + + + // Start stuff. + hw.SetAudioBlockSize(128); + hw.midi.StartReceive(); + hw.StartAdc(); + hw.StartAudio(AudioCallback); + for(;;) + { + hw.midi.Listen(); + // Handle MIDI Events + while(hw.midi.HasEvents()) + { + HandleMidiMessage(hw.midi.PopEvent()); + } + DisplayKnobValues(); + + float avgCpu = meter.GetAvgCpuLoad(); + screen.OledMessage("cpu:" + std::to_string((int)(avgCpu * 100)) + "%", 4); + + usageCounter++; + if (usageCounter > 10000) { // 10000=about 90 seconds + if (screen.IsScreenOn()) { + screen.SetScreenOn(false); + } + screen.Screensave(); + } + hw.display.Update(); + } +} diff --git a/optic/HachiKit/HhSource68.cpp b/optic/HachiKit/HhSource68.cpp new file mode 100644 index 000000000..571f3bfb4 --- /dev/null +++ b/optic/HachiKit/HhSource68.cpp @@ -0,0 +1,163 @@ +#include "HhSource68.h" + +using namespace daisy; +using namespace daisysp; + +const float HhSource68::freqs606[OSC_COUNT] = { 245, 306, 365, 415, 437, 619 }; +const float HhSource68::freqs808[OSC_COUNT] = { 204, 298, 366, 515, 540, 800 }; +const float HhSource68::MIN_FREQ_FACTOR = 0.1; +const float HhSource68::MORPH_606_VALUE = 0.35; +const float HhSource68::MORPH_808_VALUE = 0.65; +const float HhSource68::HPF_MAX = 12000; +const float HhSource68::HPF_MIN = 100; +const float HhSource68::LPF_MAX = 18000; +const float HhSource68::LPF_MIN = 100; +const float HhSource68::GAIN_MAX = 100; + + +void HhSource68::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, MORPH_808_VALUE); +} + +void HhSource68::Init(std::string slot, float sample_rate, float morph) { + + this->slot = slot; + + oscs[0] = &osc0; + oscs[1] = &osc1; + oscs[2] = &osc2; + oscs[3] = &osc3; + oscs[4] = &osc4; + oscs[5] = &osc5; + + for (int osc = 0; osc < OSC_COUNT; osc++) { + oscs[osc]->Init(sample_rate); + oscs[osc]->SetWaveform(Oscillator::WAVE_SQUARE); + oscs[osc]->SetFreq(freqs808[osc]); + } + + hpf.Init(sample_rate); + hpf.SetRes(0.05); + hpf.SetDrive(.002); + hpf.SetFreq(2700); + + lpf.Init(sample_rate); + lpf.SetRes(0.05); + lpf.SetDrive(.002); + lpf.SetFreq(5000); + + SetMorph(morph); + signal = 0.0f; +} + +float HhSource68::Process() { + float sig = cowSignal = lowCowSignal = 0.0f; + for (int osc = 0; osc < OSC_COUNT; osc++) { + float oscSignal = oscs[osc]->Process(); + sig += oscSignal; + if (osc == 4 || osc == 5) { + cowSignal += oscSignal; + } + if (osc == 3 || osc == 4) { + lowCowSignal += oscSignal; + } + } + // sig = sig / OSC_COUNT; + + hpf.Process(sig); + lpf.Process(hpf.High()); + // signal = gain * lpf.Low();; + signal = lpf.Low();; + + return signal; +} + +void HhSource68::Trigger(float velocity) { + // NOP +} + +float HhSource68::Signal() { + return signal; +} + +float HhSource68::Cowbell(bool isLow) { + return isLow ? lowCowSignal : cowSignal; +} + +void HhSource68::SetMorph(float morph) { + + float range68 = MORPH_808_VALUE - MORPH_606_VALUE; // assumes 8>6 and both betw 0 and 1 + float weight8 = (morph - MORPH_606_VALUE) * (1 / range68); + float weight6 = 1 - weight8; + + for (int osc = 0; osc < OSC_COUNT; osc++) { + float freq = weight6 * freqs606[osc] + weight8 * freqs808[osc]; + // freq = std::max(freq, freqs606[osc] * MIN_FREQ_FACTOR); // make sure freqs don't go to zero (and mins aren't all the same) + freq = std::max(freq, 200.0f); + oscs[osc]->SetFreq(freq); + } +} + +float HhSource68::GetParam(uint8_t param) { + return param < PARAM_COUNT ? parameters[param].GetScaledValue() : 0.0f; +} + +std::string HhSource68::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_MORPH: + return std::to_string((int)GetParam(param * 100)); + case PARAM_HPF: + case PARAM_LPF: + return std::to_string((int)GetParam(param));// + "hz"; + } + } + return ""; + } + +float HhSource68::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_MORPH: + scaled = parameters[param].Update(raw, Utility::Limit(raw)); + SetMorph(scaled); + break; + case PARAM_HPF: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, HPF_MIN, HPF_MAX, Parameter::EXPONENTIAL)); + hpf.SetFreq(scaled); + break; + case PARAM_LPF: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, LPF_MIN, LPF_MAX, Parameter::EXPONENTIAL)); + lpf.SetFreq(scaled); + break; + } + } + + return scaled; +} + +void HhSource68::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } +} + +void HhSource68::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_MORPH: + parameters[param].SetScaledValue(scaled); + SetMorph(scaled); + break; + case PARAM_HPF: + parameters[param].SetScaledValue(scaled); + hpf.SetFreq(scaled); + break; + case PARAM_LPF: + parameters[param].SetScaledValue(scaled); + lpf.SetFreq(scaled); + break; + } + } +} diff --git a/optic/HachiKit/HhSource68.h b/optic/HachiKit/HhSource68.h new file mode 100644 index 000000000..29c15d22a --- /dev/null +++ b/optic/HachiKit/HhSource68.h @@ -0,0 +1,84 @@ +#ifndef HHSOURCE68_H +#define HHSOURCE68_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include "IDrum.h" +#include "Param.h" + +using namespace daisy; +using namespace daisysp; + +class HhSource68: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 3; + static const uint8_t PARAM_MORPH = 0; + static const uint8_t PARAM_HPF = 1; + static const uint8_t PARAM_LPF = 2; + + // how many square waves make up the sound source + static const uint8_t OSC_COUNT = 6; + + // the morph value at which the sound imitates a 606 + static const float MORPH_606_VALUE; + // the morph value at which the sound imitates an 808 + static const float MORPH_808_VALUE; + + static const float HPF_MAX; + static const float HPF_MIN; + static const float LPF_MAX; + static const float LPF_MIN; + static const float GAIN_MAX; + + /** Initialize model with default parameters. + * \param sample_rate audio sample rate. + */ + void Init(std::string slot, float sample_rate); + + /** Initialize model with specified parameters. + * \param sample_rate audio sample rate. + * \param frequency oscillator frequency in hertz. + */ + void Init(std::string slot, float sample_rate, float morph); + + float Process(); + void Trigger(float velocity); + float Signal(); + float Cowbell(bool isLow); + + float GetParam(uint8_t param); + std::string GetParamString(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + + std::string Name() { return "Bd8"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + static const float freqs606[]; + static const float freqs808[]; + static const float MIN_FREQ_FACTOR; + + std::string paramNames[PARAM_COUNT] = { "Mrph", "HPF", "LPF" }; + std::string slot; + Param parameters[PARAM_COUNT]; + + float signal = 0.0f; + float cowSignal = 0.0f; + float lowCowSignal = 0.0f; + float gain = 1.0; + Oscillator* oscs[OSC_COUNT]; + Oscillator osc0, osc1, osc2, osc3, osc4, osc5; + Svf hpf, lpf; + + void SetMorph(float morph); + +}; + + + +#endif diff --git a/optic/HachiKit/IDrum.h b/optic/HachiKit/IDrum.h new file mode 100644 index 000000000..558d2efc3 --- /dev/null +++ b/optic/HachiKit/IDrum.h @@ -0,0 +1,74 @@ +#ifndef IDRUM_H +#define IDRUM_H + +using namespace daisy; +using namespace daisysp; + +#include "daisy_patch.h" +#include "daisysp.h" +#include + + +class IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 0; + + virtual ~IDrum() { + + } + + /** Initialize model with default parameters. + * \param sample_rate audio sample rate. + */ + virtual void Init(std::string slot, float sample_rate) = 0; + + /** + * Calculate the next sample value. + */ + virtual float Process() = 0; + + /** + * Trigger the sound from the beginning. + */ + virtual void Trigger(float velocity) = 0; + + /** Get the current value of a parameter. + * \param param index of the desired parameter (must be < PARAM_COUNT). + */ + virtual float GetParam(uint8_t param) = 0; + + /** Set the value of a parameter, regardless of the current value. + * Useful for initializing values on creation, or for setting from snapshots. + * Value is whatever is appropriate for this parameter; allows more intuitive creation + * of default values. + * + * \param param index of the desired parameter (must be slot = slot; + + // env settings + env.Init(sample_rate); + SetParam(PARAM_ATTACK, attack); + SetParam(PARAM_HOLD, hold); + SetParam(PARAM_DECAY, decay); + + // source settings + this->source = source; + SetParam(PARAM_MORPH, morph); + SetParam(PARAM_HPF, hpf); + SetParam(PARAM_LPF, lpf); + +} + +float Oh::Process() { + if (source == NULL) { + return 0.0f; + } + + float sig = source->Signal() * env.Process(); + return velocity * sig; +} + +void Oh::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + env.Trigger(); + } +} + +float Oh::GetParam(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + case PARAM_HOLD: + case PARAM_DECAY: + return parameters[param].GetScaledValue(); + case PARAM_MORPH: + return source->GetParam(HhSource68::PARAM_MORPH); + case PARAM_HPF: + return source->GetParam(HhSource68::PARAM_HPF); + case PARAM_LPF: + return source->GetParam(HhSource68::PARAM_LPF); + } + } + return 0.0f; +} + +std::string Oh::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + case PARAM_HOLD: + case PARAM_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + case PARAM_MORPH: + return std::to_string((int)(GetParam(param) * 100)); + case PARAM_HPF: + case PARAM_LPF: + return std::to_string((int)GetParam(param)); + } + } + return ""; + } + +float Oh::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < Oh::PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + env.SetAttack(scaled); + break; + case PARAM_HOLD: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + env.SetHold(scaled); + break; + case PARAM_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + env.SetDecay(scaled); + break; + case PARAM_MORPH: + source->UpdateParam(HhSource68::PARAM_MORPH, raw); + break; + case PARAM_HPF: + source->UpdateParam(HhSource68::PARAM_HPF, raw); + break; + case PARAM_LPF: + source->UpdateParam(HhSource68::PARAM_LPF, raw); + break; + } + } + + return scaled; +} + +void Oh::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } + source->ResetParams(); +} + +void Oh::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + parameters[param].SetScaledValue(scaled); + env.SetAttack(scaled); + break; + case PARAM_HOLD: + parameters[param].SetScaledValue(scaled); + env.SetHold(scaled); + break; + case PARAM_DECAY: + parameters[param].SetScaledValue(scaled); + env.SetDecay(scaled); + break; + case PARAM_MORPH: + source->SetParam(HhSource68::PARAM_MORPH, scaled); + break; + case PARAM_HPF: + source->SetParam(HhSource68::PARAM_HPF, scaled); + break; + case PARAM_LPF: + source->SetParam(HhSource68::PARAM_LPF, scaled); + break; + } + } +} diff --git a/optic/HachiKit/Oh.h b/optic/HachiKit/Oh.h new file mode 100644 index 000000000..fc8e51ace --- /dev/null +++ b/optic/HachiKit/Oh.h @@ -0,0 +1,58 @@ +#ifndef OH_H +#define OH_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "ISource.h" +#include "Utility.h" +#include "Param.h" +#include "HhSource68.h" +#include "AhdEnv.h" + +using namespace daisy; +using namespace daisysp; + +class Oh: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 6; + // This is the order params will appear in the UI. + static const uint8_t PARAM_ATTACK = 0; + static const uint8_t PARAM_HOLD = 1; + static const uint8_t PARAM_DECAY = 2; + // These are pass-thru params that belong to the sound source and aren't tracked in Oh + static const uint8_t PARAM_LPF = 3; + static const uint8_t PARAM_MORPH = 4; + static const uint8_t PARAM_HPF = 5; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float attack, float hold, float decay, HhSource68 *source, float morph, float hpf, float lpf); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + std::string GetParamString(uint8_t param); + + std::string Name() { return "Blank"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Atk", "Hold", "Dcy", "Lpf", "Mrph", "Hpf" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + HhSource68 *source = NULL; + AhdEnv env; + +}; + + + +#endif diff --git a/optic/HachiKit/Param.h b/optic/HachiKit/Param.h new file mode 100644 index 000000000..e4b472435 --- /dev/null +++ b/optic/HachiKit/Param.h @@ -0,0 +1,81 @@ +/** + * Param tracks the value of a model parameter in response to a control (e.g. analog pot) + * - ignores small changes to avoid jitter. + * - doesn't update the stored value until the input has been swept past that value, to avoid jumps. + * + * Raw value is between 0 and 1. Scaled value can be in any range; Param does not compute the + * scaling, so must be managed externally. + * +*/ +#ifndef PARAM_H +#define PARAM_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + +class Param { + + public: + + Param() { + Reset(); + } + + /** + * GetScaledValue returns the stored scaled value. + */ + float GetScaledValue() { + return scaled; + } + + /** + * SetScaledValue stores the value and overrides the jitter and jump checking. + */ + void SetScaledValue(float scaled) { + this->scaled = scaled; + Reset(); + } + + /** + * Reset sets the Param back to a state where it is waiting for control. + */ + void Reset() { + raw = -1.0f; // any value (0-1) will appear as a change + active = lower = higher = false; + } + + /** + * Update checks the raw value for jitter and prevents jump in value. + * Should be called with both the raw value and the computed scaled value. + * + * Returns the scaled value. + */ + float Update(float raw, float scaled) { + if (!active) { + if (scaled <= this->scaled || (raw <= 0.01 && raw >= 0.0f)) lower = true; + if (scaled >= this->scaled || raw >= 0.99) higher = true; + active = (lower && higher); + } + if (active && std::abs(raw - this->raw) > delta) { + this->raw = raw; + this->scaled = scaled; + } + return this->scaled; + } + + private: + float delta = 0.001f; + float raw = 0.0f; + float scaled = 0.0f; + bool active = false; + bool lower = false; + bool higher = false; + +}; + + +#endif diff --git a/optic/HachiKit/README.md b/optic/HachiKit/README.md new file mode 100644 index 000000000..31ae6a26d --- /dev/null +++ b/optic/HachiKit/README.md @@ -0,0 +1,42 @@ +# HachiKit + +## Description +A 16-voice virtual analog drumkit. + + +## Author + +Perkowitz + +## Performance + +Basic modules require the following CPU percentages (at 48k, 400mhz): +- Oscillator: 2.3% +- SVF: 1.3% +- WhiteNoise: 0.4% +- AdEnv: 1.3% +- ADSR: 0.8% +- OnePole: 0.6% +- AnalogBassDrum: 21.9% +- AnalogSnareDrum: 87.2% +- SquareNoise: 1.8% +- ZOscillator: 6.0% +- ModalVoice: 47.8% +- Wavefolder: 0.9% +- ClockedNoise: 1.4% +- FormantOscillator: 3.1% + + +Drum sounds require the following percentages of CPU to run (at 48k, 400mhz): +- ClickSource - 12% +- HhSource68 - 8% +- Bd8 - +- Ch - 3% (+ HhSource68) +- Cow8 - +- Cy - +- FmDrum - +- Oh - 3% (+ HhSource68) +- Sd8 - +- SdNoise - +- Tom - 7% (+ ClickSource) + diff --git a/optic/HachiKit/Screen.cpp b/optic/HachiKit/Screen.cpp new file mode 100644 index 000000000..3f77b6af5 --- /dev/null +++ b/optic/HachiKit/Screen.cpp @@ -0,0 +1,131 @@ +#include "Screen.h" + +using namespace daisy; +using namespace daisysp; + +const FontDef Screen::FONT = Font_6x8; +const std::string Screen::menuItems[] = { "BD", "RS", "SD", "CP", + "S2", "LT", "CH", "MT", + "MA", "HT", "OH", "LC", + "HC", "CY", "CB", "CL" }; +#define MENU_SIZE 16 + +void Screen::DrawRect(uint_fast8_t x1, uint_fast8_t y1, uint_fast8_t x2, uint_fast8_t y2, bool on, bool fill) { + if (!screenOn) { return; } + + display->DrawRect(x1, y1, x2, y2, on, fill); +} + +Rectangle Screen::WriteStringAligned( + const char* str, + const FontDef& font, + Rectangle boundingBox, + Alignment alignment, + bool on) { + + if (!screenOn) { return boundingBox; } + + return display->WriteStringAligned(str, font, boundingBox, alignment, on); +} + +void Screen::DrawLine( + uint_fast8_t x1, + uint_fast8_t y1, + uint_fast8_t x2, + uint_fast8_t y2, + bool on) { + + if (!screenOn) { return; } + + display->DrawLine(x1, y1, x2, y2, on); +} + +void Screen::DrawRect(Rectangle rect, bool on, bool fill) { + if (!screenOn) { return; } + + display->DrawRect(rect.GetX(), rect.GetY(), rect.GetX() + rect.GetWidth(), rect.GetY() + rect.GetHeight(), on, fill); +} + +void Screen::DrawRectFilled(Rectangle rect, bool border, bool fill) { + if (!screenOn) { return; } + + DrawRect(rect, fill, true); + DrawRect(rect, border, false); +} + +void Screen::DrawButton(Rectangle rect, std::string str, bool border, bool fill, bool text) { + if (!screenOn) { return; } + + DrawRectFilled(rect, border, fill); + // display->WriteStringAligned(str.c_str(), FONT, rect, Alignment::centered, text); + display->SetCursor(rect.GetX() + 2, rect.GetY() + 2); + display->WriteString(str.c_str(), FONT, text); +} + +void Screen::DrawMenu(uint8_t selected) { + if (!screenOn) { return; } + + uint8_t itemWidth = FONT.FontWidth * 2 + 3; + uint8_t itemHeight = FONT.FontWidth + 4; + uint8_t displayCount = std::min(WIDTH / itemWidth, MENU_SIZE); + uint8_t highlightPos = displayCount / 2; + // highlightPos = 4; + uint8_t start = std::min(std::max(0, selected - highlightPos), MENU_SIZE - displayCount); + + for (uint8_t pos = start; pos < start + displayCount; pos++) { + bool sel = pos == selected; + uint8_t x = itemWidth * (pos - start); + uint8_t y = HEIGHT - itemHeight; + Rectangle rect(x, y, itemWidth, itemHeight); + DrawButton(rect, menuItems[pos], true, sel, !sel); + } + +} + +void Screen::SetScreenOn(bool screenOn) { + this->screenOn = screenOn; + if (!screenOn) { + screenCounter = 0; + } +} + +void Screen::Screensave() { + if (screenOn) { return; } + + // horizontal wipe + u8 x = (screenCounter / 8) % (WIDTH + 1); + display->DrawLine(x, 0, x, HEIGHT, false); + if (x < WIDTH - 1) { + display->DrawLine(x + 1, 0, x + 1, HEIGHT, true); + } + screenCounter++; +} + +void Screen::ScreensaveEvent(u8 drum) { + if (screenOn) { return; } + + // show notes with horizontal wipe + u8 x = (screenCounter / 8) % (WIDTH + 1); + if (x > 6 && x < WIDTH - 2) { + display->DrawCircle(x - 4, (15-drum) * 4 + 1, 1, true); + } +} + +void Screen::OledMessage(std::string message, int row) { + if (!screenOn) { return; } + + char* mstr = &message[0]; + display->SetCursor(0, row * 10); + display->WriteString(mstr, Font_6x8, true); + display->Update(); +} + +void Screen::OledMessage(std::string message, int row, int column) { + if (!screenOn) { return; } + + char* mstr = &message[0]; + display->SetCursor(column * 8, row * 10); + display->WriteString(mstr, Font_6x8, true); + display->Update(); +} + diff --git a/optic/HachiKit/Screen.h b/optic/HachiKit/Screen.h new file mode 100644 index 000000000..bd6821db1 --- /dev/null +++ b/optic/HachiKit/Screen.h @@ -0,0 +1,61 @@ +#ifndef SCREEN_H +#define SCREEN_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + +class Screen { + + public: + static const uint8_t HEIGHT = 63; + static const uint8_t WIDTH = 127; + static const FontDef FONT; + static const std::string menuItems[]; + + Screen(OledDisplay *display) { + this->display = display; + } + + // pass-throughs to hw display + void DrawRect(uint_fast8_t x1, uint_fast8_t y1, uint_fast8_t x2, uint_fast8_t y2, bool on, bool fill); + Rectangle WriteStringAligned(const char* str, + const FontDef& font, + Rectangle boundingBox, + Alignment alignment, + bool on); + void DrawLine(uint_fast8_t x1, + uint_fast8_t y1, + uint_fast8_t x2, + uint_fast8_t y2, + bool on); + + + void DrawRect(Rectangle rect, bool on, bool fill); + void DrawRectFilled(Rectangle rect, bool border, bool fill); + + void DrawButton(Rectangle rect, std::string str, bool border, bool text, bool fill); + + void DrawMenu(uint8_t selected); + + void SetScreenOn(bool screenOn); + bool IsScreenOn() { return screenOn; } + void Screensave(); + void ScreensaveEvent(u8 drum); + + void OledMessage(std::string message, int row); + void OledMessage(std::string message, int row, int column); + + private: + OledDisplay *display; + bool screenOn = true; + u16 screenCounter = 0; + +}; + + +#endif diff --git a/optic/HachiKit/Sd8.cpp b/optic/HachiKit/Sd8.cpp new file mode 100644 index 000000000..6cd48cfb0 --- /dev/null +++ b/optic/HachiKit/Sd8.cpp @@ -0,0 +1,146 @@ +#include "Sd8.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + +const std::string Sd8::paramNames[] = { "oFrq", "oDcy", "nDcy", "Mix", "oAtk", "nAtk" }; + + +void Sd8::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 153, 0.001, 1.212, 0.001, 0.971, 0.5); +} + +void Sd8::Init(std::string slot, float sample_rate, float oscFrequency, float oscAttack, float oscDecay, float noiseAttack, float noiseDecay, float mix) { + + this->slot = slot; + + // oscillator settings + osc.Init(sample_rate); + SetParam(PARAM_OSC_FREQUENCY, oscFrequency); + osc.SetWaveform(Oscillator::WAVE_SIN); + + // oscEnv settings + oscEnv.Init(sample_rate); + oscEnv.SetMax(1); + oscEnv.SetMin(0); + oscEnv.SetCurve(-20); + SetParam(PARAM_OSC_ATTACK, oscAttack); + SetParam(PARAM_OSC_DECAY, oscDecay); + + // noise + noise.Init(); + + // noiseEnv settings + noiseEnv.Init(sample_rate); + noiseEnv.SetMax(1); + noiseEnv.SetMin(0); + noiseEnv.SetCurve(-20); + SetParam(PARAM_NOISE_ATTACK, noiseAttack); + SetParam(PARAM_NOISE_DECAY, noiseDecay); + SetParam(PARAM_MIX, mix); +} + +float Sd8::Process() { + float sig = (1 - parameters[PARAM_MIX].GetScaledValue()) * osc.Process() * oscEnv.Process(); + sig += parameters[PARAM_MIX].GetScaledValue() * noise.Process() * noiseEnv.Process(); + return velocity * sig * 10; +} + +void Sd8::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + osc.Reset(); + oscEnv.Trigger(); + noiseEnv.Trigger(); + } +} + +float Sd8::GetParam(uint8_t param) { + return param < PARAM_COUNT ? parameters[param].GetScaledValue() : 0.0f; +} + +std::string Sd8::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_OSC_FREQUENCY: + return std::to_string((int)GetParam(param));// + "hz"; + case PARAM_OSC_ATTACK: + case PARAM_OSC_DECAY: + case PARAM_NOISE_ATTACK: + case PARAM_NOISE_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + case PARAM_MIX: + return std::to_string((int)(GetParam(param) * 100)); + } + } + return ""; + } + +float Sd8::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_OSC_FREQUENCY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 20, 5000, Parameter::EXPONENTIAL)); + osc.SetFreq(scaled); + break; + case PARAM_OSC_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + oscEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_OSC_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + oscEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_NOISE_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + noiseEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_NOISE_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + noiseEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_MIX: + scaled = parameters[param].Update(raw, Utility::LimitFloat(raw, 0, 1)); + break; + } + } + + return scaled; +} + +void Sd8::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } +} + +void Sd8::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_OSC_FREQUENCY: + parameters[param].SetScaledValue(scaled); + osc.SetFreq(scaled); + break; + case PARAM_OSC_ATTACK: + parameters[param].SetScaledValue(scaled); + oscEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_OSC_DECAY: + parameters[param].SetScaledValue(scaled); + oscEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_NOISE_ATTACK: + parameters[param].SetScaledValue(scaled); + noiseEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_NOISE_DECAY: + parameters[param].SetScaledValue(scaled); + noiseEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_MIX: + parameters[param].SetScaledValue(scaled); + break; + } + }} diff --git a/optic/HachiKit/Sd8.h b/optic/HachiKit/Sd8.h new file mode 100644 index 000000000..86eed07c9 --- /dev/null +++ b/optic/HachiKit/Sd8.h @@ -0,0 +1,56 @@ +#ifndef SD8_H +#define SD8_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "Utility.h" +#include "Param.h" + +using namespace daisy; +using namespace daisysp; + +class Sd8: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 6; + // This is the order params will appear in the UI. + static const uint8_t PARAM_OSC_FREQUENCY = 0; + static const uint8_t PARAM_OSC_DECAY = 1; + static const uint8_t PARAM_NOISE_DECAY = 2; + static const uint8_t PARAM_MIX = 3; + static const uint8_t PARAM_OSC_ATTACK = 4; + static const uint8_t PARAM_NOISE_ATTACK = 5; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float oscFrequency, float oscAttack, float oscDecay, float noiseAttack, float noiseDecay, float mix); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + std::string GetParamString(uint8_t param); + + std::string Name() { return "Sd8"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + static const std::string paramNames[PARAM_COUNT]; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + Oscillator osc; + AdEnv oscEnv; + WhiteNoise noise; + AdEnv noiseEnv; + +}; + + + +#endif diff --git a/optic/HachiKit/SdNoise.cpp b/optic/HachiKit/SdNoise.cpp new file mode 100644 index 000000000..737a8cb6d --- /dev/null +++ b/optic/HachiKit/SdNoise.cpp @@ -0,0 +1,95 @@ +#include "SdNoise.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + +void SdNoise::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 0.01, 0.237, -30.0f); +} + +void SdNoise::Init(std::string slot, float sample_rate, float attack, float decay, float curve) { + this->slot = slot; + noise.Init(); + ampEnv.Init(sample_rate); + ampEnv.SetMax(1); + ampEnv.SetMin(0); + SetParam(PARAM_ATTACK, attack); + SetParam(PARAM_DECAY, decay); + SetParam(PARAM_CURVE, curve); +} + +float SdNoise::Process() { + return velocity * noise.Process() * ampEnv.Process(); +} + +void SdNoise::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + ampEnv.Trigger(); + } +} + +float SdNoise::GetParam(uint8_t param) { + return param < PARAM_COUNT ? parameters[param].GetScaledValue() : 0.0f; +} + +std::string SdNoise::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + case PARAM_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + case PARAM_CURVE: + return std::to_string((int)GetParam(param)); + } + } + return ""; + } + +float SdNoise::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + ampEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + ampEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_CURVE: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, -100, 100, Parameter::LINEAR)); + ampEnv.SetCurve(scaled); + break; + } + } + + return scaled; +} + +void SdNoise::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } +} + +void SdNoise::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_ATTACK: + parameters[param].SetScaledValue(scaled); + ampEnv.SetTime(ADENV_SEG_ATTACK, scaled); + break; + case PARAM_DECAY: + parameters[param].SetScaledValue(scaled); + ampEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_CURVE: + parameters[param].SetScaledValue(scaled); + ampEnv.SetCurve(scaled); + break; + } + } +} diff --git a/optic/HachiKit/SdNoise.h b/optic/HachiKit/SdNoise.h new file mode 100644 index 000000000..31952d669 --- /dev/null +++ b/optic/HachiKit/SdNoise.h @@ -0,0 +1,51 @@ +#ifndef SDNOISE_H +#define SDNOISE_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "Param.h" + +using namespace daisy; +using namespace daisysp; + + +class SdNoise: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 3; + // This is the order params will appear in the UI. + static const uint8_t PARAM_ATTACK = 0; + static const uint8_t PARAM_DECAY = 1; + static const uint8_t PARAM_CURVE = 2; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float attack, float decay, float curve); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + std::string GetParamString(uint8_t param); + + std::string Name() { return "SdNoise"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Atk", "Dcy", "Crv" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + WhiteNoise noise; + AdEnv ampEnv; + +}; + + + +#endif diff --git a/optic/HachiKit/Tom.cpp b/optic/HachiKit/Tom.cpp new file mode 100644 index 000000000..4bcc7c81f --- /dev/null +++ b/optic/HachiKit/Tom.cpp @@ -0,0 +1,161 @@ +#include "Tom.h" +#include "Utility.h" + +using namespace daisy; +using namespace daisysp; + + +void Tom::Init(std::string slot, float sample_rate) { + Init(slot, sample_rate, 80, NULL); +} + +void Tom::Init(std::string slot, float sample_rate, float frequency, ClickSource *clickSource) { + + this->slot = slot; + this->clickSource = clickSource; + + osc.Init(sample_rate); + SetParam(PARAM_FREQUENCY, frequency); + osc.SetWaveform(Oscillator::WAVE_POLYBLEP_TRI); + + ampEnv.Init(sample_rate); + ampEnv.SetMax(1); + ampEnv.SetMin(0); + ampEnv.SetCurve(-10); + ampEnv.SetTime(ADENV_SEG_ATTACK, 0.001); + SetParam(PARAM_AMP_DECAY, 1.1); + SetParam(PARAM_PITCH_MOD, 60); + + pitchEnv.Init(sample_rate); + pitchEnv.SetMax(1); + pitchEnv.SetMin(0); + pitchEnv.SetCurve(-20); + pitchEnv.SetTime(ADENV_SEG_ATTACK, 0.001); + pitchEnv.SetTime(ADENV_SEG_DECAY, 0.25); +} + +float Tom::Process() { + + float clickSignal = clickSource == NULL ? 0.0f : clickSource->Signal(); + + // sine osc freq is modulated by pitch env, amp by amp env + float pitchEnvSignal = pitchEnv.Process(); + float ampEnvSignal = ampEnv.Process(); + osc.SetFreq(parameters[PARAM_FREQUENCY].GetScaledValue() + parameters[PARAM_PITCH_MOD].GetScaledValue() * pitchEnvSignal); + float oscSignal = osc.Process() * ampEnvSignal; + + // TODO: figure out why this wasn't working + // // // apply velocity scaling more strong to noise than osc + // // float signal = noiseSignal * velocity + oscSignal * (0.4 + velocity * 0.6); + // // return signal; + return (clickSignal + oscSignal) * velocity; +} + +void Tom::Trigger(float velocity) { + this->velocity = Utility::Limit(velocity); + if (this->velocity > 0) { + clickSource->Trigger(velocity); + ampEnv.Trigger(); + pitchEnv.Trigger(); + osc.Reset(); + } +} + +float Tom::GetParam(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + case PARAM_AMP_DECAY: + case PARAM_PITCH_MOD: + return param < PARAM_COUNT ? parameters[param].GetScaledValue() : 0.0f; + case PARAM_LPF_MOD: + return clickSource->GetParam(ClickSource::PARAM_LPF_MOD); + case PARAM_HPF: + return clickSource->GetParam(ClickSource::PARAM_HPF); + case PARAM_LPF: + return clickSource->GetParam(ClickSource::PARAM_LPF); + } + } + + return 0.0f; +} + +std::string Tom::GetParamString(uint8_t param) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + case PARAM_PITCH_MOD: + return std::to_string((int)GetParam(param)); + case PARAM_AMP_DECAY: + return std::to_string((int)(GetParam(param) * 1000));// + "ms"; + case PARAM_LPF_MOD: + return clickSource->GetParamString(ClickSource::PARAM_LPF_MOD); + case PARAM_HPF: + return clickSource->GetParamString(ClickSource::PARAM_HPF); + case PARAM_LPF: + return clickSource->GetParamString(ClickSource::PARAM_LPF); + } + } + return ""; + } + +float Tom::UpdateParam(uint8_t param, float raw) { + float scaled = raw; + if (param < Tom::PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 20, 1000, Parameter::EXPONENTIAL)); + break; + case PARAM_AMP_DECAY: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0.01, 5, Parameter::EXPONENTIAL)); + ampEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_PITCH_MOD: + scaled = parameters[param].Update(raw, Utility::ScaleFloat(raw, 0, 2000, Parameter::EXPONENTIAL)); + break; + case PARAM_LPF_MOD: + clickSource->UpdateParam(ClickSource::PARAM_LPF_MOD, raw); + break; + case PARAM_HPF: + clickSource->UpdateParam(ClickSource::PARAM_HPF, raw); + break; + case PARAM_LPF: + clickSource->UpdateParam(ClickSource::PARAM_LPF, raw); + break; + } + } + + return scaled; +} + +void Tom::ResetParams() { + for (u8 param = 0; param < PARAM_COUNT; param++) { + parameters[param].Reset(); + } +} + +void Tom::SetParam(uint8_t param, float scaled) { + if (param < PARAM_COUNT) { + switch (param) { + case PARAM_FREQUENCY: + parameters[param].SetScaledValue(scaled); + break; + case PARAM_AMP_DECAY: + parameters[param].SetScaledValue(scaled); + ampEnv.SetTime(ADENV_SEG_DECAY, scaled); + break; + case PARAM_PITCH_MOD: + parameters[param].SetScaledValue(scaled); + break; + case PARAM_LPF_MOD: + clickSource->SetParam(ClickSource::PARAM_LPF_MOD, scaled); + break; + case PARAM_HPF: + clickSource->SetParam(ClickSource::PARAM_LPF_MOD, scaled); + break; + case PARAM_LPF: + clickSource->SetParam(ClickSource::PARAM_LPF_MOD, scaled); + break; + } + } +} diff --git a/optic/HachiKit/Tom.h b/optic/HachiKit/Tom.h new file mode 100644 index 000000000..ce6e3bc74 --- /dev/null +++ b/optic/HachiKit/Tom.h @@ -0,0 +1,58 @@ +#ifndef TOM_H +#define TOM_H + +#include "daisy_patch.h" +#include "daisysp.h" +#include +#include "IDrum.h" +#include "ISource.h" +#include "Utility.h" +#include "Param.h" +#include "ClickSource.h" + +using namespace daisy; +using namespace daisysp; + +class Tom: public IDrum { + + public: + // Number of settable parameters for this model. + static const uint8_t PARAM_COUNT = 6; + // This is the order params will appear in the UI. + static const uint8_t PARAM_FREQUENCY = 0; + static const uint8_t PARAM_AMP_DECAY = 1; + static const uint8_t PARAM_PITCH_MOD = 2; + // These are pass-thru params that belong to the click source and aren't tracked in Tom + static const uint8_t PARAM_LPF_MOD = 3; + static const uint8_t PARAM_HPF = 4; + static const uint8_t PARAM_LPF = 5; + + void Init(std::string slot, float sample_rate); + void Init(std::string slot, float sample_rate, float frequency, ClickSource *clickSource); + float Process(); + void Trigger(float velocity); + + float GetParam(uint8_t param); + float UpdateParam(uint8_t param, float value); + void SetParam(uint8_t param, float value); + void ResetParams(); + std::string GetParamString(uint8_t param); + + std::string Name() { return "Tom"; } + std::string Slot() { return slot; } + std::string GetParamName(uint8_t param) { return param < PARAM_COUNT ? paramNames[param] : ""; } + + private: + std::string paramNames[PARAM_COUNT] = { "Freq", "aDcy", "pMod", "fMod", "Hpf", "Lpf" }; + std::string slot; + Param parameters[PARAM_COUNT]; + float velocity; + Oscillator osc; + AdEnv ampEnv, pitchEnv; + ClickSource *clickSource; + +}; + + + +#endif diff --git a/optic/HachiKit/Utility.h b/optic/HachiKit/Utility.h new file mode 100644 index 000000000..0d0abb11f --- /dev/null +++ b/optic/HachiKit/Utility.h @@ -0,0 +1,104 @@ +#ifndef UTILITY_H +#define UTILITY_H + +#include "daisy_patch.h" +#include "daisysp.h" + +using namespace daisy; +using namespace daisysp; + +typedef signed long s32; +typedef signed short s16; +typedef signed char s8; + +typedef unsigned long long u64; +typedef unsigned long u32; +typedef unsigned short u16; +typedef unsigned char u8; + + +#define MAX_ENV_TIME 20 + +class Utility { + + public: + static float Limit(float value) { + return std::min(1.0f, std::max(0.0f, value)); + } + + static float LimitFloat(float value, float min, float max) { + return std::min(max, std::max(min, value)); + } + + static float LimitInt(int value, int min, int max) { + return std::min(max, std::max(min, value)); + } + + static float ScaleFloat(float value, float min, float max, Parameter::Curve curve) { + + float scaled = value; + switch(curve) + { + case Parameter::LINEAR: + scaled = (value * (max - min)) + min; + break; + case Parameter::EXPONENTIAL: + scaled = ((value * value) * (max - min)) + min; + break; + case Parameter::LOGARITHMIC: + scaled = expf((value * (max - min)) + min); + break; + case Parameter::CUBE: + scaled = ((value * (value * value)) * (max - min)) + min; + break; + default: break; + } + return std::min(max, std::max(min, scaled)); + } + + /** Convert a float to a string, since the DaisySP skips this functionality for compactness. + */ + static std::string FloatToString(float value, uint8_t digits) { + int left = std::floor(value); + int right = std::floor(value * pow(10, digits)); + return std::to_string(left) + "." + std::to_string(right); + } + + /** Convert a float to a string, since the DaisySP skips this functionality for compactness. + */ + static std::string FloatToString3(float value) { + int left = std::floor(value); + int right = std::floor(value * 1000); + return std::to_string(left) + "." + std::to_string(right); + } + + /** Convert a float to a string, since the DaisySP skips this functionality for compactness. + */ + static std::string FloatToString2(float value) { + int left = std::floor(value); + int right = std::floor(value * 100); + return std::to_string(left) + "." + std::to_string(right); + } + + static void DrawDrums(daisy::OledDisplay *display, uint8_t current) { + display->DrawRect(0, 48, 127, 63, true, false); + display->SetCursor(8, 52); + display->WriteString("BD", Font_6x8, true); + display->SetCursor(104, 52); + display->WriteString("36", Font_6x8, true); + + for (uint8_t i = 0; i < 16; i++) { + uint8_t x = 32 + (i % 8) * 8; + uint8_t y = 48 + (i / 8) * 8; + bool fill = i == current; + display->DrawRect(x, y, x+7, y + 7, true, fill); + if (fill) { + display->DrawRect(x, y, x+7, y + 7, false, false); + } + } + + } +}; + + +#endif