Skip to content

Instantly share code, notes, and snippets.

@NullMember
Created Aug 2, 2018
Embed
What would you like to do?
/*
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