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_core.cc \
fm_op_kernel.cc \ fm_op_kernel.cc \
freqlut.cc \ freqlut.cc \
lfo.cc \
patch.cc \ patch.cc \
pitchenv.cc \ pitchenv.cc \
resofilter.cc \ resofilter.cc \

@ -26,9 +26,6 @@
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "synth", __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "synth", __VA_ARGS__)
#include "synth.h" #include "synth.h"
#include "freqlut.h"
#include "exp2.h"
#include "sin.h"
#include "synth_unit.h" #include "synth_unit.h"
RingBuffer *ring_buffer; RingBuffer *ring_buffer;
@ -171,9 +168,7 @@ Java_com_google_synthesizer_android_AndroidGlue_start(JNIEnv *env,
assert(SL_RESULT_SUCCESS == result); assert(SL_RESULT_SUCCESS == result);
buffer_size = buf_size; buffer_size = buf_size;
Freqlut::init(sample_rate); SynthUnit::Init(sample_rate);
Exp2::init();
Sin::init();
ring_buffer = new RingBuffer(); ring_buffer = new RingBuffer();
stats_ring_buffer = new RingBuffer(); stats_ring_buffer = new RingBuffer();
synth_unit = new SynthUnit(ring_buffer); 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) { static const uint8_t pitchmodsenstab[] = {
char patch[156]; 0, 10, 20, 33, 55, 92, 153, 255
UnpackPatch(bulk, patch); // TODO: move this out, take unpacked patch };
void Dx7Note::init(const char patch[156], int midinote, int velocity) {
int rates[4]; int rates[4];
int levels[4]; int levels[4];
for (int op = 0; op < 6; op++) { 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]; algorithm_ = patch[134];
int feedback = patch[135]; int feedback = patch[135];
fb_shift_ = feedback != 0 ? 8 - feedback : 16; fb_shift_ = feedback != 0 ? 8 - feedback : 16;
pitchmoddepth_ = (patch[139] * 165) >> 6;
pitchmodsens_ = pitchmodsenstab[patch[143] & 7];
} }
void Dx7Note::compute(int32_t *buf) { void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay) {
int32_t pitchenvlevel = pitchenv_.getsample(); 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++) { for (int op = 0; op < 6; op++) {
params_[op].gain[0] = params_[op].gain[1]; params_[op].gain[0] = params_[op].gain[1];
int32_t level = env_[op].getsample(); int32_t level = env_[op].getsample();
int32_t gain = Exp2::lookup(level - (14 * (1 << 24))); int32_t gain = Exp2::lookup(level - (14 * (1 << 24)));
//int32_t gain = pow(2, 10 + level * (1.0 / (1 << 24))); //int32_t gain = pow(2, 10 + level * (1.0 / (1 << 24)));
// TODO: probably don't apply pitch env to fixed freq // 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; params_[op].gain[1] = gain;
} }
core_.compute(buf, params_, algorithm_, fb_buf_, fb_shift_); 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 // Note: this _adds_ to the buffer. Interesting question whether it's
// worth it... // worth it...
void compute(int32_t *buf); void compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay);
void keyup(); void keyup();
@ -57,6 +57,8 @@ class Dx7Note {
int32_t fb_shift_; int32_t fb_shift_;
int algorithm_; int algorithm_;
int pitchmoddepth_;
int pitchmodsens_;
}; };
#endif // SYNTH_DX7NOTE_H_ #endif // SYNTH_DX7NOTE_H_

@ -21,6 +21,8 @@
#include "sin.h" #include "sin.h"
#include "lfo.h" #include "lfo.h"
uint32_t Lfo::unit_;
void Lfo::init(double sample_rate) { void Lfo::init(double sample_rate) {
// constant is 1 << 32 / 15.5s / 11 // constant is 1 << 32 / 15.5s / 11
Lfo::unit_ = (int32_t)(N * 25190424 / sample_rate + 0.5); 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; int sr = rate == 0 ? 1 : (165 * rate) >> 6;
sr *= sr < 160 ? 11 : (11 + ((sr - 160) >> 4)); sr *= sr < 160 ? 11 : (11 + ((sr - 160) >> 4));
delta_ = unit_ * sr; 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]; waveform_ = params[5];
sync_ = params[4] != 0; sync_ = params[4] != 0;
} }
@ -62,8 +75,23 @@ int32_t Lfo::getsample() {
return 1 << 23; 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() { void Lfo::keydown() {
if (sync_) { if (sync_) {
phase_ = (1U << 31) - 1; phase_ = (1U << 31) - 1;
} }
delaystate_ = 0;
} }

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

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

@ -15,6 +15,7 @@
*/ */
#include "dx7note.h" #include "dx7note.h"
#include "lfo.h"
#include "ringbuffer.h" #include "ringbuffer.h"
#include "resofilter.h" #include "resofilter.h"
@ -28,6 +29,8 @@ struct ActiveNote {
class SynthUnit { class SynthUnit {
public: public:
static void Init(double sample_rate);
explicit SynthUnit(RingBuffer *ring_buffer); explicit SynthUnit(RingBuffer *ring_buffer);
void GetSamples(int n_samples, int16_t *buffer); void GetSamples(int n_samples, int16_t *buffer);
@ -40,6 +43,9 @@ class SynthUnit {
// none available. // none available.
int AllocateNote(); int AllocateNote();
// zero-based
void ProgramChange(int p);
int ProcessMidiMessage(const uint8_t *buf, int buf_size); int ProcessMidiMessage(const uint8_t *buf, int buf_size);
RingBuffer *ring_buffer_; RingBuffer *ring_buffer_;
@ -52,6 +58,11 @@ class SynthUnit {
uint8_t patch_data_[4096]; uint8_t patch_data_[4096];
int current_patch_; 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_; ResoFilter filter_;
int32_t filter_control_[2]; int32_t filter_control_[2];
bool sustain_; bool sustain_;

Loading…
Cancel
Save