fixed envelope sustain level, basic LFO + PWM
This commit is contained in:
parent
34800af7a1
commit
b8265f6938
@ -17,17 +17,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ic29.hpp"
|
#include "ic29.hpp"
|
||||||
|
|
||||||
#include "ic29tables.hpp"
|
#include "ic29tables.hpp"
|
||||||
|
|
||||||
Synth ic29;
|
Synth ic29;
|
||||||
|
|
||||||
Synth::Synth() {
|
Synth::Synth() {
|
||||||
d_debug("initialising synth\n");
|
d_debug("initialising synth\n");
|
||||||
envAtk = 0x20;
|
envAtk = 0x00;
|
||||||
envDcy = 0x50;
|
envDcy = 0x50;
|
||||||
envStn = 0x1f;
|
envStn = 0x7f;
|
||||||
envRls = 0x3f;
|
envRls = 0x3f;
|
||||||
portaCoeff = 0x0;
|
portaCoeff = 0x0;
|
||||||
|
lfo.speed = 0x1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::buildTables(double sampleRate) {
|
void Synth::buildTables(double sampleRate) {
|
||||||
@ -35,7 +37,7 @@ void Synth::buildTables(double sampleRate) {
|
|||||||
// slightly flat middle C from ROM divider table
|
// slightly flat middle C from ROM divider table
|
||||||
// actually adjusted a little so that the notes are bang on
|
// actually adjusted a little so that the notes are bang on
|
||||||
// on the real synth the tuning knob is tweaked a little off to pull it in
|
// on the real synth the tuning knob is tweaked a little off to pull it in
|
||||||
pitchTable[i] = 260.15f * powf(2, (i - 36) / 12.0f) / sampleRate;
|
pitchTable[i] = 260.15f * powf(2, (i - 36) / 12.0f) / sampleRate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +45,10 @@ void Synth::run() {
|
|||||||
// handle a "loop" worth of envelopes, pitch calculations, etc
|
// handle a "loop" worth of envelopes, pitch calculations, etc
|
||||||
// callled once every 4.3ms block of samples
|
// callled once every 4.3ms block of samples
|
||||||
|
|
||||||
|
ic29.lfo.run();
|
||||||
|
|
||||||
|
masterPitch = 0x1818;
|
||||||
|
|
||||||
for (uint8_t i = 0; i < NUM_VOICES; i++) {
|
for (uint8_t i = 0; i < NUM_VOICES; i++) {
|
||||||
ic29.voices[i].update();
|
ic29.voices[i].update();
|
||||||
}
|
}
|
||||||
@ -60,14 +66,32 @@ void Synth::voiceOff(uint8_t voice) {
|
|||||||
ic29.voices[voice].off();
|
ic29.voices[voice].off();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::basePitch() {
|
LFO::LFO() {
|
||||||
uint16_t pitch = 0x1818;
|
lfoOut = 0;
|
||||||
|
phase = 0;
|
||||||
|
|
||||||
pitch += lfoPitch;
|
// phase is where we are in the LFO delay cycle
|
||||||
pitch += bendPitch;
|
// the delay envelope sets the depth of pitch and VCF modulation
|
||||||
// tuning too but that's zero by default;
|
// running normally the amplitude is maxed out, and when the first
|
||||||
|
// key is struck the holdoff timer and envelope will be reset to zero
|
||||||
|
delayPhase = LFO_RUN;
|
||||||
|
}
|
||||||
|
|
||||||
masterPitch = pitch;
|
void LFO::run() {
|
||||||
|
// slightly different from the real synth code which does not use signed
|
||||||
|
// variables, since the CPU doesn't support them
|
||||||
|
|
||||||
|
lfoOut += phase ? lfoRateTable[speed] : -lfoRateTable[speed];
|
||||||
|
if (lfoOut > 0x1fff) {
|
||||||
|
lfoOut = 0x1fff;
|
||||||
|
phase = 0;
|
||||||
|
}
|
||||||
|
if (lfoOut < -0x1fff) {
|
||||||
|
lfoOut = -0x1fff;
|
||||||
|
phase = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//printf("lfoOut=%04x\n", lfoOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
Envelope::Envelope() {
|
Envelope::Envelope() {
|
||||||
@ -76,8 +100,7 @@ Envelope::Envelope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Envelope::run() {
|
void Envelope::run() {
|
||||||
|
uint16_t tempStn = ic29.envStn << 7;
|
||||||
uint16_t tempStn = ic29.envStn << 6;
|
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case ENV_ATK:
|
case ENV_ATK:
|
||||||
level += atkTable[ic29.envAtk];
|
level += atkTable[ic29.envAtk];
|
||||||
@ -88,9 +111,7 @@ void Envelope::run() {
|
|||||||
break;
|
break;
|
||||||
case ENV_DCY:
|
case ENV_DCY:
|
||||||
if (level > tempStn) {
|
if (level > tempStn) {
|
||||||
// level = ((level * ic29.envDcy) >> 16;
|
level = (((level - tempStn) * dcyTable[ic29.envDcy]) >> 16) + tempStn;
|
||||||
level = (((level - tempStn) * dcyTable[ic29.envDcy]) >> 16) + tempStn;
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
level = tempStn;
|
level = tempStn;
|
||||||
}
|
}
|
||||||
@ -105,19 +126,26 @@ void Envelope::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Voice::Voice() {
|
Voice::Voice() {
|
||||||
subosc = 1;
|
subosc = .11;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Voice::calcPitch() {
|
void Voice::calcPitch() {
|
||||||
uint16_t target = note << 8;
|
uint16_t target = note << 8;
|
||||||
|
|
||||||
|
// Portamento is a linear change of pitch - it'll take twice as long
|
||||||
|
// to jump two octaves as it takes to jump one
|
||||||
|
// By comparison "glide" is like an RC filter, for example in the TB303
|
||||||
|
// This is implemented here by adding on a step value until you pass
|
||||||
|
// the desired final pitch. Once that happens the value is clamped to the
|
||||||
|
// desired pitch.
|
||||||
|
|
||||||
if (ic29.portaCoeff != 0) {
|
if (ic29.portaCoeff != 0) {
|
||||||
// porta up
|
// portamento up
|
||||||
if (pitch < target) {
|
if (pitch < target) {
|
||||||
pitch += ic29.portaCoeff;
|
pitch += ic29.portaCoeff;
|
||||||
if (pitch > target) pitch = target;
|
if (pitch > target) pitch = target;
|
||||||
}
|
}
|
||||||
// porta down
|
// portamento down
|
||||||
if (pitch > target) {
|
if (pitch > target) {
|
||||||
pitch -= ic29.portaCoeff;
|
pitch -= ic29.portaCoeff;
|
||||||
if (pitch < target) pitch = target;
|
if (pitch < target) pitch = target;
|
||||||
@ -126,17 +154,17 @@ void Voice::calcPitch() {
|
|||||||
pitch = target;
|
pitch = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
pitch += 0x1818; //ic29.masterPitch;
|
pitch += ic29.masterPitch;
|
||||||
|
|
||||||
if (pitch < 0x3000) pitch = 0x3000; // lowest note
|
if (pitch < 0x3000) pitch = 0x3000; // lowest note
|
||||||
if (pitch > 0x9700) pitch = 0x6700; // highest note
|
if (pitch > 0x9700) pitch = 0x6700; // highest note
|
||||||
|
|
||||||
pitch -= 0x3000;
|
pitch -= 0x3000;
|
||||||
//pitch &= 0xff00;
|
|
||||||
|
|
||||||
|
// interpolate between the two table values
|
||||||
double o1 = ic29.pitchTable[pitch >> 8];
|
double o1 = ic29.pitchTable[pitch >> 8];
|
||||||
double o2 = ic29.pitchTable[(pitch >> 8) + 1];
|
double o2 = ic29.pitchTable[(pitch >> 8) + 1];
|
||||||
double frac = (pitch & 0xff) / 255.0;
|
double frac = (pitch & 0xff) / 256.0f;
|
||||||
|
|
||||||
omega = ((o2 - o1) * frac) + o1;
|
omega = ((o2 - o1) * frac) + o1;
|
||||||
}
|
}
|
||||||
@ -155,7 +183,7 @@ void Voice::on(uint8_t key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Voice::off() {
|
void Voice::off() {
|
||||||
// I need to rethink this bit FIXME
|
// sustain - I need to rethink this bit FIXME
|
||||||
voiceState = V_OFF;
|
voiceState = V_OFF;
|
||||||
if (!ic29.sustained) {
|
if (!ic29.sustained) {
|
||||||
env.off();
|
env.off();
|
||||||
|
@ -20,6 +20,24 @@
|
|||||||
|
|
||||||
#include "peacock.hpp"
|
#include "peacock.hpp"
|
||||||
|
|
||||||
|
class LFO {
|
||||||
|
public:
|
||||||
|
LFO();
|
||||||
|
void run();
|
||||||
|
int16_t lfoOut;
|
||||||
|
uint8_t speed;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t
|
||||||
|
phase;
|
||||||
|
uint16_t holdoff;
|
||||||
|
uint16_t envelope;
|
||||||
|
enum { LFO_RUN,
|
||||||
|
LFO_HOLDOFF,
|
||||||
|
LFO_RAMP } delayPhase;
|
||||||
|
static const uint16_t lfoRateTable[128];
|
||||||
|
};
|
||||||
|
|
||||||
class Envelope {
|
class Envelope {
|
||||||
public:
|
public:
|
||||||
Envelope();
|
Envelope();
|
||||||
@ -99,9 +117,8 @@ class Synth {
|
|||||||
int16_t lfoPitch;
|
int16_t lfoPitch;
|
||||||
int16_t bendPitch;
|
int16_t bendPitch;
|
||||||
Voice voices[NUM_VOICES];
|
Voice voices[NUM_VOICES];
|
||||||
|
LFO lfo;
|
||||||
|
|
||||||
void runLfo();
|
|
||||||
void basePitch();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// global
|
// global
|
||||||
|
@ -67,7 +67,7 @@ extern const uint8_t lfoDepthTable[128] = {
|
|||||||
0xb0, 0xb4, 0xb8, 0xbc, 0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4, 0xd8, 0xdc,
|
0xb0, 0xb4, 0xb8, 0xbc, 0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4, 0xd8, 0xdc,
|
||||||
0xe0, 0xe4, 0xe8, 0xec, 0xf0, 0xf8, 0xff, 0xff};
|
0xe0, 0xe4, 0xe8, 0xec, 0xf0, 0xf8, 0xff, 0xff};
|
||||||
|
|
||||||
extern const uint16_t lfoRateTable[128] = {
|
const uint16_t LFO::lfoRateTable[128] = {
|
||||||
0x0005, 0x000f, 0x0019, 0x0028, 0x0037, 0x0046, 0x0050, 0x005a, 0x0064,
|
0x0005, 0x000f, 0x0019, 0x0028, 0x0037, 0x0046, 0x0050, 0x005a, 0x0064,
|
||||||
0x006e, 0x0078, 0x0082, 0x008c, 0x0096, 0x00a0, 0x00aa, 0x00b4, 0x00be,
|
0x006e, 0x0078, 0x0082, 0x008c, 0x0096, 0x00a0, 0x00aa, 0x00b4, 0x00be,
|
||||||
0x00c8, 0x00d2, 0x00dc, 0x00e6, 0x00f0, 0x00fa, 0x0104, 0x010e, 0x0118,
|
0x00c8, 0x00d2, 0x00dc, 0x00e6, 0x00f0, 0x00fa, 0x0104, 0x010e, 0x0118,
|
||||||
|
@ -30,7 +30,10 @@ static inline float poly3blep1(float t) {
|
|||||||
void Voice::run(float *buffer, uint32_t samples) {
|
void Voice::run(float *buffer, uint32_t samples) {
|
||||||
// generate a full block of samples for the oscillator
|
// generate a full block of samples for the oscillator
|
||||||
|
|
||||||
float y, out, pw = 0.0, t;
|
float y, out, pw = .50, t;
|
||||||
|
|
||||||
|
float saw = 0;
|
||||||
|
pw = 0.5-(ic29.lfo.lfoOut + 0x2000) / 61600.0f;
|
||||||
|
|
||||||
float gain = env.level / 16384.0;
|
float gain = env.level / 16384.0;
|
||||||
|
|
||||||
@ -52,15 +55,15 @@ void Voice::run(float *buffer, uint32_t samples) {
|
|||||||
if (pulseStage) {
|
if (pulseStage) {
|
||||||
if (phase < 1) break; // it's not time to reset the saw
|
if (phase < 1) break; // it's not time to reset the saw
|
||||||
t = (phase - 1) / omega;
|
t = (phase - 1) / omega;
|
||||||
y += poly3blep0(t) * (0.8 + 0.63 - subosc);
|
y += poly3blep0(t) * (0.8 * saw + 0.63 - subosc);
|
||||||
delay += poly3blep1(t) * (0.8 + 0.63 - subosc);
|
delay += poly3blep1(t) * (0.8 * saw + 0.63 - subosc);
|
||||||
pulseStage = 0;
|
pulseStage = 0;
|
||||||
phase -= 1;
|
phase -= 1;
|
||||||
subosc = -subosc;
|
subosc = -subosc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delay += (0.8 - (1.6 * phase)); // magic numbers observed on oscilloscope from real synth
|
delay += saw * (0.8 - (1.6 * phase)); // magic numbers observed on oscilloscope from real synth
|
||||||
delay += (0.63 - (pw * 1.26)) + (pulseStage ? -0.63f : 0.63f); // add in the scaled pulsewidth to restore DC level
|
delay += (0.63 - (pw * 1.26)) + (pulseStage ? -0.63f : 0.63f); // add in the scaled pulsewidth to restore DC level
|
||||||
// the DC correction is important because the hardware synth is AC-coupled effectively high-passing
|
// the DC correction is important because the hardware synth is AC-coupled effectively high-passing
|
||||||
// the signal at about 10Hz or so, preventing any PWM rumble from leaking through!
|
// the signal at about 10Hz or so, preventing any PWM rumble from leaking through!
|
||||||
|
Loading…
Reference in New Issue
Block a user