Created
August 2, 2018 12:53
-
-
Save NullMember/5aeb5a0349dac39b64f58e46ff42b7ac to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
in this library we are using fix16_t format and it have four bytes | |
0x00 00 . 00 00 | |
ˆ ˆ dot ˆ ˆ | |
MSB of whole part LSB of whole part MSB of frac part LSB of frac part | |
*/ | |
#ifndef DDSOSC_H | |
#define DDSOSC_H | |
#include <mbed.h> | |
#include <fixmath.h> | |
//#include "FixMath/fixmath.h" | |
#define FIX_POS 16 | |
#define OUTPUT_MASK 0xFF | |
#define OUTPUT_SHIFT 9 | |
#define OUTPUT_BIT_DEPTH 8 | |
#define WAVETABLE 0 | |
#define PULSE_WAVE 1 | |
#define TRIANGLE_WAVE 2 | |
#define SAWTOOTH_WAVE 3 | |
const fix16_t signal_min = -65536; //-1 | |
const fix16_t signal_max = 65535; // 0.9999847412109375 | |
const fix16_t signal_add = -65536; //-1 | |
// very basic mixer | |
class Mixer{ | |
public: | |
fix16_t mix2(fix16_t, fix16_t); | |
fix16_t mix4(fix16_t, fix16_t, fix16_t, fix16_t); | |
uint8_t output(fix16_t); | |
}mixer; | |
fix16_t inline Mixer::mix2(fix16_t _1, fix16_t _2){ | |
return fix16_add(_1, _2) >> 1; | |
} | |
fix16_t inline Mixer::mix4(fix16_t _1, fix16_t _2, fix16_t _3, fix16_t _4){ | |
return fix16_add(fix16_add(_1, _2), fix16_add(_3, _4)) >> 2; | |
} | |
uint8_t inline Mixer::output(fix16_t _input){ | |
fix16_t _signal = ((_input > signal_max) ? signal_max : ((_input < signal_min) ? signal_min : _input)); // Satuaration (clipping) | |
// All signal values are between -1 to 1 | |
// We don't want negative values so add -1 to output to make it between 0 to 2 | |
// We need to do signal / 2 and also take MSB of fractional part of fixed point 32-bit value (signal & 0x0000.FF00) | |
// Combine operations for lowering needed CPU cycles | |
// operation is same as | |
// output += signal_add (add -1 to output) | |
// output >>= 1 (divide by 2) | |
// output &= 0x0000.FF00 (take MSB of fractional part) | |
// output >> 8 (shift 0x0000 FF00 to 0x0000 00FF) | |
_signal = ((_signal + signal_add) >> OUTPUT_SHIFT) & OUTPUT_MASK; | |
return (uint8_t)_signal; // cast output as unsigned 8-bit (LSB of output) | |
} | |
class DDSOsc{ | |
private: | |
const int16_t * LUT; | |
uint32_t sampleRate; | |
uint8_t waveType; | |
fix16_t pulseWidth; | |
fix16_t tableSize; | |
fix16_t tdivs; | |
fix16_t phaseAcc; | |
fix16_t tuningWord; | |
fix16_t oscfrequency; | |
fix16_t oscvolume; | |
fix16_t oscphase; | |
fix16_t fmFrequency; | |
fix16_t outputVolume; | |
fix16_t _output; | |
uint32_t indexMask; | |
public: | |
DDSOsc(const int16_t *, int, uint32_t, float, float, float); //table, table size, sample rate, frequency, volume, phase | |
DDSOsc(uint8_t, uint32_t, float, float, float); //wave type, sample rate, frequency, volume, phase | |
void FM(fix16_t); | |
void AM(fix16_t); | |
void PM(fix16_t); | |
void frequency(float); | |
void frequency(int); | |
void phase(float); | |
void phase(int); | |
void volume(float); | |
void volume(int); | |
void pulse(float); | |
void pulse(int); | |
fix16_t update(); // Not saturated output in fix16_t | |
uint8_t output(); // Saturated output for DAC (0,256) in uint8_t | |
}; | |
inline DDSOsc::DDSOsc(const int16_t * _LUT, int _tableSize = 256, uint32_t _sampleRate = 44100, float _frequency = 0, float _volume = 0, float _phase = 0){ | |
LUT = _LUT; | |
tableSize = fix16_from_int(_tableSize); | |
sampleRate = _sampleRate; | |
tdivs = fix16_from_float((float(_tableSize) / float(_sampleRate))); | |
oscfrequency = fix16_from_float(_frequency); | |
oscvolume = fix16_from_float(_volume); | |
oscphase = fix16_mul(fix16_from_float(_phase), tableSize); | |
tuningWord = fix16_mul(oscfrequency, tdivs); | |
phaseAcc = oscphase; | |
indexMask = ((_tableSize - 1) << FIX_POS) | 0xFFFF; | |
} | |
inline DDSOsc::DDSOsc(uint8_t _waveType, uint32_t _sampleRate = 44100, float _frequency = 0, float _volume = 0, float _phase = 0){ | |
waveType = _waveType; | |
sampleRate = _sampleRate; | |
tableSize = fix16_from_int(256); //because we have 8-bit DAC | |
tdivs = fix16_from_float((256.0f / float(_sampleRate))); // table size / sample rate. we need to use this value to calculate new tuning word every time frequency changes | |
oscfrequency = fix16_from_float(_frequency); | |
oscvolume = fix16_from_float(_volume); | |
oscphase = fix16_mul(fix16_from_float(_phase), tableSize); | |
tuningWord = fix16_mul(oscfrequency, tdivs); | |
phaseAcc = oscphase; | |
indexMask = 0x00FFFFFF; | |
pulseWidth = fix16_from_int(127); | |
} | |
inline void DDSOsc::frequency(float _frequency){ | |
oscfrequency = fix16_from_float(_frequency); | |
tuningWord = fix16_mul(oscfrequency, tdivs); | |
return; | |
} | |
inline void DDSOsc::frequency(int _frequency){ | |
oscfrequency = fix16_from_int(_frequency); | |
tuningWord = fix16_mul(oscfrequency, tdivs); | |
return; | |
} | |
inline void DDSOsc::phase(float _phase){ | |
oscphase = fix16_from_float(_phase); | |
phaseAcc = fix16_mul(oscphase, tableSize); | |
return; | |
} | |
inline void DDSOsc::phase(int _phase){ | |
oscphase = fix16_from_int(_phase); | |
phaseAcc = fix16_mul(oscphase, tableSize); | |
} | |
inline void DDSOsc::volume(float _volume){ | |
oscvolume = fix16_from_float(_volume); | |
outputVolume = oscvolume; | |
return; | |
} | |
inline void DDSOsc::volume(int _volume){ | |
oscvolume = fix16_from_int(_volume); | |
outputVolume = oscvolume; | |
} | |
inline void DDSOsc::pulse(float _pulseWidth){ | |
pulseWidth = fix16_from_float(fix16_mul((_pulseWidth > 1.0) ? 1.0 : ((_pulseWidth < 0.0) ? 0.0 : _pulseWidth), tableSize)); | |
return; | |
} | |
inline void DDSOsc::pulse(int _pulseWidth){ | |
//if given pulse value out of bounds saturate | |
pulseWidth = fix16_from_int((_pulseWidth > 255) ? 255 : ((_pulseWidth < 0) ? 0 : _pulseWidth)); | |
return; | |
} | |
inline void DDSOsc::FM(fix16_t _FM){ | |
fmFrequency = fix16_add(oscfrequency, _FM); | |
tuningWord = fix16_mul(fmFrequency, tdivs); | |
return; | |
} | |
inline void DDSOsc::AM(fix16_t _AM){ | |
outputVolume = fix16_mul(oscvolume, _AM); | |
return; | |
} | |
inline void DDSOsc::PM(fix16_t _PM){ | |
phaseAcc = fix16_mul(_PM, tableSize); | |
return; | |
} | |
inline fix16_t DDSOsc::update(){ | |
phaseAcc += tuningWord; //calculate new phaseAcc value | |
phaseAcc &= 0x00FFFFFF; //we just want fractional part and LSB of whole part | |
fix16_t _phaseAcc; | |
switch(waveType){ | |
case WAVETABLE: | |
// get whole part LSB and add it to lookup table pointer | |
// multiply lookup table value by 2 | |
// because our lookup table values are stored in signed 16-bit for lowering memory usage (we are using Signed Q15.16 (32-bit) format for calculations) | |
// multiplying by 2 is make it compatible with our fixed point format | |
// then multiply by outputVolume | |
_output = fix16_mul(fix16_t(*(LUT + (phaseAcc >> FIX_POS)) << 1), outputVolume); //get value from lookup table and multiply by volume | |
break; | |
case PULSE_WAVE: | |
// if phase accumulator is smaller or equal to pulse width output = signal_max (+1 ish :D) | |
if(phaseAcc <= pulseWidth) | |
_output = signal_max; | |
// if phase accumulator bigger than pulse width output = signal_min (-1) | |
else | |
_output = signal_min; | |
// multiply by outputVolume | |
_output = fix16_mul(_output, outputVolume); | |
break; | |
case TRIANGLE_WAVE: | |
// I know this method is not the best :D | |
_phaseAcc = (phaseAcc >> 6); | |
if(_phaseAcc < 0x00010000) | |
_output = _phaseAcc; | |
else if(_phaseAcc < 0x00030000) | |
_output = 0x00020000 - _phaseAcc; | |
else | |
_output = 0xFFFC0000 + _phaseAcc; | |
break; | |
case SAWTOOTH_WAVE: | |
// phase accumulator is between 0.0 to 256.0 but we need -1.0 to 1.0 | |
// so divide phase accumulator by 128 to make it 0.0 to 2.0 | |
// add -1.0 to phase accumulator to make it between -1.0 to 1.0 | |
// multiply by outputVolume | |
_output = fix16_mul((phaseAcc >> 7) + signal_add, outputVolume); | |
break; | |
default: | |
_output = 128; | |
break; | |
} | |
return _output; | |
} | |
inline uint8_t DDSOsc::output(){ | |
update(); | |
_output = ((_output > signal_max) ? signal_max : ((_output < signal_min) ? signal_min : _output)); // Satuaration (clipping) | |
// All signal values are between -1 to 1 | |
// We don't want negative values so add -1 to output to make it between 0 to 2 | |
// We need to do signal / 2 and also take MSB of fractional part of fixed point 32-bit value (signal & 0x0000.FF00) | |
// Combine operations for lowering needed CPU cycles | |
// operation is same as | |
// output += signal_add (add -1 to output) | |
// output >>= 1 (divide by 2) | |
// output &= 0x0000.FF00 (take MSB of fractional part) | |
// output >> 8 (shift 0x0000 FF00 to 0x0000 00FF) | |
_output = ((_output + signal_add) >> OUTPUT_SHIFT) & OUTPUT_MASK; | |
return (uint8_t)_output; // cast output as unsigned 8-bit (LSB of output) | |
} | |
#endif //DDSOSC_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment