Add pitch LFO

This patch wires up the LFO to the synth unit, and makes it affect pitch
(control over amplitude is not there yet). It also adds LFO delay to the
base LFO implementation, and there are some other cleanups as well (for
example, not unpacking patch data every note).

LFO speeds, delays, and pitch modulation ranges have been calibrated
against the DX7, but testing hasn't been exhaustive.
master
Raph Levien 12 years ago
parent 05355d7ef5
commit f52201cc11
  1. 1
      android/jni/Android.mk
  2. 7
      cpp/src/android_glue.cc
  3. 21
      cpp/src/dx7note.cc
  4. 4
      cpp/src/dx7note.h
  5. 28
      cpp/src/lfo.cc
  6. 7
      cpp/src/lfo.h
  7. 32
      cpp/src/synth_unit.cc
  8. 11
      cpp/src/synth_unit.h

@ -10,6 +10,7 @@ LOCAL_SRC_FILES := android_glue.cc \
fm_core.cc \
fm_op_kernel.cc \
freqlut.cc \
lfo.cc \
patch.cc \
pitchenv.cc \
resofilter.cc \

@ -26,9 +26,6 @@
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "synth", __VA_ARGS__)
#include "synth.h"
#include "freqlut.h"
#include "exp2.h"
#include "sin.h"
#include "synth_unit.h"
RingBuffer *ring_buffer;
@ -171,9 +168,7 @@ Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env,
assert(SL_RESULT_SUCCESS == result);
buffer_size = buf_size;
Freqlut::init(sample_rate);
Exp2::init();
Sin::init();
SynthUnit::Init(sample_rate);
ring_buffer = new RingBuffer();
stats_ring_buffer = new RingBuffer();
synth_unit = new SynthUnit(ring_buffer);

@ -123,9 +123,11 @@ int ScaleLevel(int midinote, int break_pt, int left_depth, int right_depth,
}
}
void Dx7Note::init(const char bulk[128], int midinote, int velocity) {
char patch[156];
UnpackPatch(bulk, patch); // TODO: move this out, take unpacked patch
static const uint8_t pitchmodsenstab[] = {
0, 10, 20, 33, 55, 92, 153, 255
};
void Dx7Note::init(const char patch[156], int midinote, int velocity) {
int rates[4];
int levels[4];
for (int op = 0; op < 6; op++) {
@ -172,17 +174,24 @@ void Dx7Note::init(const char bulk[128], int midinote, int velocity) {
algorithm_ = patch[134];
int feedback = patch[135];
fb_shift_ = feedback != 0 ? 8 - feedback : 16;
pitchmoddepth_ = (patch[139] * 165) >> 6;
pitchmodsens_ = pitchmodsenstab[patch[143] & 7];
}
void Dx7Note::compute(int32_t *buf) {
int32_t pitchenvlevel = pitchenv_.getsample();
void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay) {
int32_t pitchmod = pitchenv_.getsample();
uint32_t pmd = pitchmoddepth_ * lfo_delay; // Q32
// TODO: add modulation sources (mod wheel, etc)
int32_t senslfo = pitchmodsens_ * (lfo_val - (1 << 23));
pitchmod += (((int64_t)pmd) * (int64_t)senslfo) >> 39;
// TODO: add pitch bend
for (int op = 0; op < 6; op++) {
params_[op].gain[0] = params_[op].gain[1];
int32_t level = env_[op].getsample();
int32_t gain = Exp2::lookup(level - (14 * (1 << 24)));
//int32_t gain = pow(2, 10 + level * (1.0 / (1 << 24)));
// TODO: probably don't apply pitch env to fixed freq
params_[op].freq = Freqlut::lookup(basepitch_[op] + pitchenvlevel);
params_[op].freq = Freqlut::lookup(basepitch_[op] + pitchmod);
params_[op].gain[1] = gain;
}
core_.compute(buf, params_, algorithm_, fb_buf_, fb_shift_);

@ -36,7 +36,7 @@ class Dx7Note {
// Note: this _adds_ to the buffer. Interesting question whether it's
// worth it...
void compute(int32_t *buf);
void compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay);
void keyup();
@ -57,6 +57,8 @@ class Dx7Note {
int32_t fb_shift_;
int algorithm_;
int pitchmoddepth_;
int pitchmodsens_;
};
#endif // SYNTH_DX7NOTE_H_

@ -21,6 +21,8 @@
#include "sin.h"
#include "lfo.h"
uint32_t Lfo::unit_;
void Lfo::init(double sample_rate) {
// constant is 1 << 32 / 15.5s / 11
Lfo::unit_ = (int32_t)(N * 25190424 / sample_rate + 0.5);
@ -31,6 +33,17 @@ void Lfo::reset(const char params[6]) {
int sr = rate == 0 ? 1 : (165 * rate) >> 6;
sr *= sr < 160 ? 11 : (11 + ((sr - 160) >> 4));
delta_ = unit_ * sr;
int a = 99 - params[1]; // LFO delay
if (a == 99) {
delayinc_ = ~0u;
delayinc2_ = ~0u;
} else {
a = (16 + (a & 15)) << (1 + (a >> 4));
delayinc_ = unit_ * a;
a &= 0xff80;
a = max(0x80, a);
delayinc2_ = unit_ * a;
}
waveform_ = params[5];
sync_ = params[4] != 0;
}
@ -62,8 +75,23 @@ int32_t Lfo::getsample() {
return 1 << 23;
}
int32_t Lfo::getdelay() {
uint32_t delta = delaystate_ < (1U << 31) ? delayinc_ : delayinc2_;
uint32_t d = delaystate_ + delta;
if (d < delayinc_) {
return 1 << 24;
}
delaystate_ = d;
if (d < (1U << 31)) {
return 0;
} else {
return (d >> 7) & ((1 << 24) - 1);
}
}
void Lfo::keydown() {
if (sync_) {
phase_ = (1U << 31) - 1;
}
delaystate_ = 0;
}

@ -24,6 +24,9 @@ class Lfo {
// result is 0..1 in Q24
int32_t getsample();
// result is 0..1 in Q24
int32_t getdelay();
void keydown();
private:
static uint32_t unit_;
@ -33,4 +36,8 @@ class Lfo {
uint8_t waveform_;
uint8_t randstate_;
bool sync_;
uint32_t delaystate_;
uint32_t delayinc_;
uint32_t delayinc2_;
};

@ -26,6 +26,10 @@
#include <string.h>
#include "synth.h"
#include "freqlut.h"
#include "sin.h"
#include "exp2.h"
#include "patch.h"
#include "synth_unit.h"
#include "aligned_buf.h"
@ -41,6 +45,13 @@ char epiano[] = {
69, 46, 80, 73, 65, 78, 79, 32, 49, 32
};
void SynthUnit::Init(double sample_rate) {
Freqlut::init(sample_rate);
Exp2::init();
Sin::init();
Lfo::init(sample_rate);
}
SynthUnit::SynthUnit(RingBuffer *ring_buffer) {
ring_buffer_ = ring_buffer;
for (int note = 0; note < max_active_notes; ++note) {
@ -93,6 +104,13 @@ int SynthUnit::AllocateNote() {
return -1;
}
void SynthUnit::ProgramChange(int p) {
current_patch_ = p;
const uint8_t *patch = patch_data_ + 128 * current_patch_;
UnpackPatch((const char *)patch, unpacked_patch_);
lfo_.reset(unpacked_patch_ + 137);
}
int SynthUnit::ProcessMidiMessage(const uint8_t *buf, int buf_size) {
uint8_t cmd = buf[0];
uint8_t cmd_type = cmd & 0xf0;
@ -118,13 +136,12 @@ int SynthUnit::ProcessMidiMessage(const uint8_t *buf, int buf_size) {
// note on
int note_ix = AllocateNote();
if (note_ix >= 0) {
lfo_.keydown(); // TODO: should only do this if # keys down was 0
active_note_[note_ix].midi_note = buf[1];
active_note_[note_ix].keydown = true;
active_note_[note_ix].sustained = sustain_;
active_note_[note_ix].live = true;
const uint8_t *patch = patch_data_ + 128 * current_patch_;
active_note_[note_ix].dx7_note->init(
(const char *)patch, buf[1], buf[2]);
active_note_[note_ix].dx7_note->init(unpacked_patch_, buf[1], buf[2]);
}
return 3;
}
@ -154,9 +171,9 @@ int SynthUnit::ProcessMidiMessage(const uint8_t *buf, int buf_size) {
if (buf_size >= 2) {
// program change
int program_number = buf[1];
current_patch_ = min(program_number, 31);
ProgramChange(min(program_number, 31));
char name[11];
memcpy(name, patch_data_ + 128 * current_patch_ + 118, 10);
memcpy(name, unpacked_patch_ + 145, 10);
name[10] = 0;
#ifdef VERBOSE
std::cout << "Loaded patch " << current_patch_ << ": " << name << "\r";
@ -172,6 +189,7 @@ int SynthUnit::ProcessMidiMessage(const uint8_t *buf, int buf_size) {
if (buf_size >= 4104) {
// TODO: check checksum?
memcpy(patch_data_, buf + 6, 4096);
ProgramChange(current_patch_);
return 4104;
}
return 0;
@ -218,9 +236,11 @@ void SynthUnit::GetSamples(int n_samples, int16_t *buffer) {
for (int j = 0; j < N; ++j) {
audiobuf.get()[j] = 0;
}
int32_t lfovalue = lfo_.getsample();
int32_t lfodelay = lfo_.getdelay();
for (int note = 0; note < max_active_notes; ++note) {
if (active_note_[note].live) {
active_note_[note].dx7_note->compute(audiobuf.get());
active_note_[note].dx7_note->compute(audiobuf.get(), lfovalue, lfodelay);
}
}
const int32_t *bufs[] = { audiobuf.get() };

@ -15,6 +15,7 @@
*/
#include "dx7note.h"
#include "lfo.h"
#include "ringbuffer.h"
#include "resofilter.h"
@ -28,6 +29,8 @@ struct ActiveNote {
class SynthUnit {
public:
static void Init(double sample_rate);
explicit SynthUnit(RingBuffer *ring_buffer);
void GetSamples(int n_samples, int16_t *buffer);
@ -40,6 +43,9 @@ class SynthUnit {
// none available.
int AllocateNote();
// zero-based
void ProgramChange(int p);
int ProcessMidiMessage(const uint8_t *buf, int buf_size);
RingBuffer *ring_buffer_;
@ -52,6 +58,11 @@ class SynthUnit {
uint8_t patch_data_[4096];
int current_patch_;
char unpacked_patch_[156];
// The original DX7 had one single LFO. Later units had an LFO per note.
Lfo lfo_;
ResoFilter filter_;
int32_t filter_control_[2];
bool sustain_;

Loading…
Cancel
Save