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)
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 {
// ((1 << 24) * log(10) / log(2) * .01) << 3
logfreq = (4458616 * ((coarse & 3) * 100 + fine)) >> 3;
// TODO: detune
logfreq += detune > 7 ? 13457 * (detune - 7) : 0;
}
int32_t base_freq = Freqlut::lookup(logfreq);
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...
Dx7Note::Dx7Note(const char bulk[128], int midinote, int velocity) {
void Dx7Note::init(const char bulk[128], int midinote, int velocity) {
char patch[156];
UnpackPatch(bulk, patch); // TODO: move this out, take unpacked patch
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];
}
int outlevel = patch[off + 16];
outlevel = Env::scaleoutlevel(outlevel);
#ifdef VERBOSE
for (int j = 8; j < 12; 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],
patch[off + 10], patch[off + 11], patch[off + 12]);
outlevel += level_scaling;
outlevel = min(99, outlevel);
outlevel = min(127, outlevel);
#ifdef VERBOSE
cout << op << ": " << level_scaling << " " << outlevel << endl;
#endif

@ -31,7 +31,7 @@ class Dx7Note {
// 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
// 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
// worth it...

@ -73,16 +73,19 @@ void Env::setparam(int param, int value) {
}
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) {
ix_ = newix;
if (ix_ < 4) {
int newlevel = levels_[ix_];
int actuallevel = newlevel >= 19 ?
14 + (newlevel >> 1) : levellut[newlevel];
actuallevel = (actuallevel << 6) + outlevel_ - 3360;
int actuallevel = scaleoutlevel(newlevel) >> 1;
actuallevel = (actuallevel << 6) + outlevel_ - 4256;
actuallevel = actuallevel < 16 ? 16 : actuallevel;
// level here is same as Java impl
targetlevel_ = actuallevel << 16;

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

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

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

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

Loading…
Cancel
Save