Add detune, other note improvements

Finally implements detune (using an approximation, but better than
nothing). Also minor improvements to note generation, including scaling
output level more accurately, and getting rid of dynamic allocations of
note objects.
master
Raph Levien 12 years ago
parent 68b44a4a89
commit 79cc5e39fd
  1. 12
      cpp/src/dx7note.cc
  2. 2
      cpp/src/dx7note.h
  3. 11
      cpp/src/env.cc
  4. 1
      cpp/src/env.h
  5. 3
      cpp/src/fm_core.cc
  6. 11
      cpp/src/synth_unit.cc
  7. 1
      cpp/src/synth_unit.h

@ -50,11 +50,13 @@ int32_t osc_freq(int midinote, int mode, int coarse, int fine, int detune) {
// (1 << 24) / log(2) // (1 << 24) / log(2)
logfreq += (int32_t)floor(24204406.323123 * log(1 + 0.01 * fine) + 0.5); logfreq += (int32_t)floor(24204406.323123 * log(1 + 0.01 * fine) + 0.5);
} }
// TODO: detune // This was measured at 7.213Hz per count at 9600Hz, but the exact
// value is somewhat dependent on midinote. Close enough for now.
logfreq += 12606 * (detune - 7);
} else { } else {
// ((1 << 24) * log(10) / log(2) * .01) << 3 // ((1 << 24) * log(10) / log(2) * .01) << 3
logfreq = (4458616 * ((coarse & 3) * 100 + fine)) >> 3; logfreq = (4458616 * ((coarse & 3) * 100 + fine)) >> 3;
// TODO: detune logfreq += detune > 7 ? 13457 * (detune - 7) : 0;
} }
int32_t base_freq = Freqlut::lookup(logfreq); int32_t base_freq = Freqlut::lookup(logfreq);
return base_freq; return base_freq;
@ -122,8 +124,7 @@ int ScaleLevel(int midinote, int break_pt, int left_depth, int right_depth,
} }
} }
// Considering making this an init method... void Dx7Note::init(const char bulk[128], int midinote, int velocity) {
Dx7Note::Dx7Note(const char bulk[128], int midinote, int velocity) {
char patch[156]; char patch[156];
UnpackPatch(bulk, patch); // TODO: move this out, take unpacked patch UnpackPatch(bulk, patch); // TODO: move this out, take unpacked patch
for (int op = 0; op < 6; op++) { for (int op = 0; op < 6; op++) {
@ -135,6 +136,7 @@ Dx7Note::Dx7Note(const char bulk[128], int midinote, int velocity) {
levels[i] = patch[off + 4 + i]; levels[i] = patch[off + 4 + i];
} }
int outlevel = patch[off + 16]; int outlevel = patch[off + 16];
outlevel = Env::scaleoutlevel(outlevel);
#ifdef VERBOSE #ifdef VERBOSE
for (int j = 8; j < 12; j++) { for (int j = 8; j < 12; j++) {
cout << (int)patch[off + j] << " "; cout << (int)patch[off + j] << " ";
@ -143,7 +145,7 @@ Dx7Note::Dx7Note(const char bulk[128], int midinote, int velocity) {
int level_scaling = ScaleLevel(midinote, patch[off + 8], patch[off + 9], int level_scaling = ScaleLevel(midinote, patch[off + 8], patch[off + 9],
patch[off + 10], patch[off + 11], patch[off + 12]); patch[off + 10], patch[off + 11], patch[off + 12]);
outlevel += level_scaling; outlevel += level_scaling;
outlevel = min(99, outlevel); outlevel = min(127, outlevel);
#ifdef VERBOSE #ifdef VERBOSE
cout << op << ": " << level_scaling << " " << outlevel << endl; cout << op << ": " << level_scaling << " " << outlevel << endl;
#endif #endif

@ -31,7 +31,7 @@ class Dx7Note {
// Interesting question: should the setup be in the constructor, or should // Interesting question: should the setup be in the constructor, or should
// there be an init method? The latter would make it easier to use a fixed // there be an init method? The latter would make it easier to use a fixed
// pool of note objects. // pool of note objects.
Dx7Note(const char patch[128], int midinote, int velocity); void init(const char patch[128], int midinote, int velocity);
// 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...

@ -73,16 +73,19 @@ void Env::setparam(int param, int value) {
} }
const int levellut[] = { const int levellut[] = {
0, 2, 4, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 0, 5, 9, 13, 17, 20, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 42, 43, 45, 46
}; };
int Env::scaleoutlevel(int outlevel) {
return outlevel >= 20 ? 28 + outlevel : levellut[outlevel];
}
void Env::advance(int newix) { void Env::advance(int newix) {
ix_ = newix; ix_ = newix;
if (ix_ < 4) { if (ix_ < 4) {
int newlevel = levels_[ix_]; int newlevel = levels_[ix_];
int actuallevel = newlevel >= 19 ? int actuallevel = scaleoutlevel(newlevel) >> 1;
14 + (newlevel >> 1) : levellut[newlevel]; actuallevel = (actuallevel << 6) + outlevel_ - 4256;
actuallevel = (actuallevel << 6) + outlevel_ - 3360;
actuallevel = actuallevel < 16 ? 16 : actuallevel; actuallevel = actuallevel < 16 ? 16 : actuallevel;
// level here is same as Java impl // level here is same as Java impl
targetlevel_ = actuallevel << 16; targetlevel_ = actuallevel << 16;

@ -39,6 +39,7 @@ class Env {
void keydown(bool down); void keydown(bool down);
void setparam(int param, int value); void setparam(int param, int value);
static int scaleoutlevel(int outlevel);
private: private:
int rates_[4]; int rates_[4];
int levels_[4]; int levels_[4];

@ -109,6 +109,7 @@ void FmCore::dump() {
void FmCore::compute(int32_t *output, FmOpParams *params, int algorithm, void FmCore::compute(int32_t *output, FmOpParams *params, int algorithm,
int32_t *fb_buf, int feedback_shift) { int32_t *fb_buf, int feedback_shift) {
const int kLevelThresh = 1120;
const FmAlgorithm alg = algorithms[algorithm]; const FmAlgorithm alg = algorithms[algorithm];
bool has_contents[3] = { true, false, false }; bool has_contents[3] = { true, false, false };
for (int op = 0; op < 6; op++) { for (int op = 0; op < 6; op++) {
@ -120,7 +121,7 @@ void FmCore::compute(int32_t *output, FmOpParams *params, int algorithm,
int32_t *outptr = (outbus == 0) ? output : buf_[outbus - 1].get(); int32_t *outptr = (outbus == 0) ? output : buf_[outbus - 1].get();
int32_t gain1 = param.gain[0]; int32_t gain1 = param.gain[0];
int32_t gain2 = param.gain[1]; int32_t gain2 = param.gain[1];
if (gain1 != 0 || gain2 != 0) { if (gain1 >= kLevelThresh || gain2 >= kLevelThresh) {
if (!has_contents[outbus]) { if (!has_contents[outbus]) {
add = false; add = false;
} }

@ -44,9 +44,10 @@ char epiano[] = {
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) {
active_note_[note].dx7_note = NULL; active_note_[note].dx7_note = new Dx7Note;
active_note_[note].keydown = false; active_note_[note].keydown = false;
active_note_[note].sustained = false; active_note_[note].sustained = false;
active_note_[note].live = false;
} }
input_buffer_index_ = 0; input_buffer_index_ = 0;
memcpy(patch_data_, epiano, sizeof(epiano)); memcpy(patch_data_, epiano, sizeof(epiano));
@ -117,13 +118,13 @@ 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) {
delete active_note_[note_ix].dx7_note;
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;
const uint8_t *patch = patch_data_ + 128 * current_patch_; const uint8_t *patch = patch_data_ + 128 * current_patch_;
active_note_[note_ix].dx7_note = active_note_[note_ix].dx7_note->init(
new Dx7Note((const char *)patch, buf[1], buf[2]); (const char *)patch, buf[1], buf[2]);
} }
return 3; return 3;
} }
@ -218,7 +219,7 @@ void SynthUnit::GetSamples(int n_samples, int16_t *buffer) {
audiobuf.get()[j] = 0; audiobuf.get()[j] = 0;
} }
for (int note = 0; note < max_active_notes; ++note) { for (int note = 0; note < max_active_notes; ++note) {
if (active_note_[note].dx7_note != NULL) { if (active_note_[note].live) {
active_note_[note].dx7_note->compute(audiobuf.get()); active_note_[note].dx7_note->compute(audiobuf.get());
} }
} }

@ -22,6 +22,7 @@ struct ActiveNote {
int midi_note; int midi_note;
bool keydown; bool keydown;
bool sustained; bool sustained;
bool live;
Dx7Note *dx7_note; Dx7Note *dx7_note;
}; };

Loading…
Cancel
Save