diff --git a/android/jni/Android.mk b/android/jni/Android.mk index c4a9db2..9c3b5d6 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -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 \ diff --git a/cpp/src/android_glue.cc b/cpp/src/android_glue.cc index 511acff..b8b6ee5 100644 --- a/cpp/src/android_glue.cc +++ b/cpp/src/android_glue.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); diff --git a/cpp/src/dx7note.cc b/cpp/src/dx7note.cc index 805b7b5..caf3e0d 100644 --- a/cpp/src/dx7note.cc +++ b/cpp/src/dx7note.cc @@ -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_); diff --git a/cpp/src/dx7note.h b/cpp/src/dx7note.h index 8e12159..947967a 100644 --- a/cpp/src/dx7note.h +++ b/cpp/src/dx7note.h @@ -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_ diff --git a/cpp/src/lfo.cc b/cpp/src/lfo.cc index 4089ae4..6b86df5 100644 --- a/cpp/src/lfo.cc +++ b/cpp/src/lfo.cc @@ -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; } diff --git a/cpp/src/lfo.h b/cpp/src/lfo.h index 18f4f70..809df1e 100644 --- a/cpp/src/lfo.h +++ b/cpp/src/lfo.h @@ -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_; }; diff --git a/cpp/src/synth_unit.cc b/cpp/src/synth_unit.cc index 946150c..bc9e564 100644 --- a/cpp/src/synth_unit.cc +++ b/cpp/src/synth_unit.cc @@ -26,6 +26,10 @@ #include #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() }; diff --git a/cpp/src/synth_unit.h b/cpp/src/synth_unit.h index a484000..94c0347 100644 --- a/cpp/src/synth_unit.h +++ b/cpp/src/synth_unit.h @@ -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_;