|
|
|
#include "synth_dexed.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
synth_dexed
|
|
|
|
|
|
|
|
synth_dexed is a port of the Dexed sound engine (https://github.com/asb2m10/dexed)
|
|
|
|
as library for the Teensy-3.5/3.6/4.x with an audio shield.
|
|
|
|
Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android
|
|
|
|
|
|
|
|
(c)2018-2021 H. Wirtz <wirtz@parasitstudio.de>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software Foundation,
|
|
|
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
Copyright (c) 2013-2014 Pascal Gauthier.
|
|
|
|
Copyright (c) 2013-2014 Filatov Vadim.
|
|
|
|
|
|
|
|
Filter taken from the Obxd project :
|
|
|
|
https://github.com/2DaT/Obxd
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software Foundation,
|
|
|
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
|
|
|
|
*/
|
|
|
|
const float dc = 1e-18;
|
|
|
|
|
|
|
|
inline static float tptpc(float& state, float inp, float cutoff) {
|
|
|
|
float v = (inp - state) * cutoff / (1 + cutoff);
|
|
|
|
float res = v + state;
|
|
|
|
state = res + v;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline static float tptlpupw(float & state , float inp , float cutoff , float srInv) {
|
|
|
|
cutoff = (cutoff * srInv) * M_PI;
|
|
|
|
float v = (inp - state) * cutoff / (1 + cutoff);
|
|
|
|
float res = v + state;
|
|
|
|
state = res + v;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
//static float linsc(float param,const float min,const float max) {
|
|
|
|
// return (param) * (max - min) + min;
|
|
|
|
//}
|
|
|
|
|
|
|
|
// static float logsc(float param, const float min, const float max, const float rolloff = 19.0f) {
|
|
|
|
// return ((EXP_FUNC(param * LOG_FUNC(rolloff + 1)) - 1.0f) / (rolloff)) * (max - min) + min;
|
|
|
|
// }
|
|
|
|
|
|
|
|
PluginFx::PluginFx() {
|
|
|
|
Cutoff = 1.0;
|
|
|
|
Reso = 0.0;
|
|
|
|
Gain = 1.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PluginFx::init(int sr) {
|
|
|
|
mm = 0;
|
|
|
|
s1 = s2 = s3 = s4 = c = d = 0;
|
|
|
|
R24 = 0;
|
|
|
|
|
|
|
|
mmch = (int)(mm * 3);
|
|
|
|
mmt = mm * 3 - mmch;
|
|
|
|
|
|
|
|
sampleRate = sr;
|
|
|
|
sampleRateInv = 1 / sampleRate;
|
|
|
|
#if defined(ARM_SQRT_FUNC)
|
|
|
|
float rcrate; ARM_SQRT_FUNC(44000 / sampleRate, &rcrate);
|
|
|
|
#else
|
|
|
|
float rcrate = SQRT_FUNC((44000 / sampleRate));
|
|
|
|
#endif
|
|
|
|
rcor24 = (970.0 / 44000) * rcrate;
|
|
|
|
rcor24Inv = 1 / rcor24;
|
|
|
|
|
|
|
|
bright = tanf((sampleRate * 0.5f - 10) * M_PI * sampleRateInv);
|
|
|
|
|
|
|
|
R = 1;
|
|
|
|
rcor = (480.0 / 44000) * rcrate;
|
|
|
|
rcorInv = 1 / rcor;
|
|
|
|
bandPassSw = false;
|
|
|
|
|
|
|
|
pCutoff = -1;
|
|
|
|
pReso = -1;
|
|
|
|
|
|
|
|
dc_r = 1.0 - (126.0 / sr);
|
|
|
|
dc_id = 0;
|
|
|
|
dc_od = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline float PluginFx::NR24(float sample, float g, float lpc) {
|
|
|
|
float ml = 1 / (1 + g);
|
|
|
|
float S = (lpc * (lpc * (lpc * s1 + s2) + s3) + s4) * ml;
|
|
|
|
float G = lpc * lpc * lpc * lpc;
|
|
|
|
float y = (sample - R24 * S) / (1 + R24 * G);
|
|
|
|
return y + 1e-8;
|
|
|
|
};
|
|
|
|
|
|
|
|
inline float PluginFx::NR(float sample, float g) {
|
|
|
|
float y = ((sample - R * s1 * 2 - g * s1 - s2) / (1 + g * (2 * R + g))) + dc;
|
|
|
|
return y;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PluginFx::process(float *work, int sampleSize) {
|
|
|
|
// very basic DC filter
|
|
|
|
float t_fd = work[0];
|
|
|
|
work[0] = work[0] - dc_id + dc_r * dc_od;
|
|
|
|
dc_id = t_fd;
|
|
|
|
for (int i = 1; i < sampleSize; i++) {
|
|
|
|
t_fd = work[i];
|
|
|
|
work[i] = work[i] - dc_id + dc_r * work[i - 1];
|
|
|
|
dc_id = t_fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
dc_od = work[sampleSize - 1];
|
|
|
|
|
|
|
|
// Gain
|
|
|
|
if (Gain == 0.0)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < sampleSize; i++ )
|
|
|
|
work[i] = 0.0;
|
|
|
|
}
|
|
|
|
else if ( Gain != 1.0)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < sampleSize; i++ )
|
|
|
|
work[i] *= Gain;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_FX
|
|
|
|
// don't apply the LPF if the cutoff is to maximum
|
|
|
|
if ( Cutoff == 1.0 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( Cutoff != pCutoff || Reso != pReso ) {
|
|
|
|
rReso = (0.991 - logsc(1 - Reso, 0, 0.991));
|
|
|
|
R24 = 3.5 * rReso;
|
|
|
|
|
|
|
|
float cutoffNorm = logsc(Cutoff, 60, 19000);
|
|
|
|
rCutoff = (float)tanf(cutoffNorm * sampleRateInv * M_PI);
|
|
|
|
|
|
|
|
pCutoff = Cutoff;
|
|
|
|
pReso = Reso;
|
|
|
|
|
|
|
|
R = 1 - rReso;
|
|
|
|
}
|
|
|
|
|
|
|
|
// THIS IS MY FAVORITE 4POLE OBXd filter
|
|
|
|
|
|
|
|
// maybe smooth this value
|
|
|
|
float g = rCutoff;
|
|
|
|
float lpc = g / (1 + g);
|
|
|
|
|
|
|
|
for (int i = 0; i < sampleSize; i++ ) {
|
|
|
|
float s = work[i];
|
|
|
|
s = s - 0.45 * tptlpupw(c, s, 15, sampleRateInv);
|
|
|
|
s = tptpc(d, s, bright);
|
|
|
|
|
|
|
|
float y0 = NR24(s, g, lpc);
|
|
|
|
|
|
|
|
//first low pass in cascade
|
|
|
|
float v = (y0 - s1) * lpc;
|
|
|
|
float res = v + s1;
|
|
|
|
s1 = res + v;
|
|
|
|
|
|
|
|
//damping
|
|
|
|
s1 = atanf(s1 * rcor24) * rcor24Inv;
|
|
|
|
float y1 = res;
|
|
|
|
float y2 = tptpc(s2, y1, g);
|
|
|
|
float y3 = tptpc(s3, y2, g);
|
|
|
|
float y4 = tptpc(s4, y3, g);
|
|
|
|
float mc = 0.0;
|
|
|
|
|
|
|
|
switch (mmch) {
|
|
|
|
case 0:
|
|
|
|
mc = ((1 - mmt) * y4 + (mmt) * y3);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
mc = ((1 - mmt) * y3 + (mmt) * y2);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
mc = ((1 - mmt) * y2 + (mmt) * y1);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
mc = y1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
//half volume comp
|
|
|
|
work[i] = mc * (1 + R24 * 0.45);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
// THIS IS THE 2POLE FILTER
|
|
|
|
|
|
|
|
for(int i=0; i < sampleSize; i++ ) {
|
|
|
|
float s = work[i];
|
|
|
|
s = s - 0.45*tptlpupw(c,s,15,sampleRateInv);
|
|
|
|
s = tptpc(d,s,bright);
|
|
|
|
|
|
|
|
//float v = ((sample- R * s1*2 - g2*s1 - s2)/(1+ R*g1*2 + g1*g2));
|
|
|
|
float v = NR(s,g);
|
|
|
|
float y1 = v*g + s1;
|
|
|
|
//damping
|
|
|
|
s1 = atanf(s1 * rcor) * rcorInv;
|
|
|
|
|
|
|
|
float y2 = y1*g + s2;
|
|
|
|
s2 = y2 + y1*g;
|
|
|
|
|
|
|
|
float mc;
|
|
|
|
if(!bandPassSw)
|
|
|
|
mc = (1-mm)*y2 + (mm)*v;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
|
|
|
mc =2 * ( mm < 0.5 ?
|
|
|
|
((0.5 - mm) * y2 + (mm) * y1):
|
|
|
|
((1-mm) * y1 + (mm-0.5) * v)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
work[i] = mc;
|
|
|
|
}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
float PluginFx::getGain(void)
|
|
|
|
{
|
|
|
|
return (Gain);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
MicroDexed
|
|
|
|
|
|
|
|
MicroDexed is a port of the Dexed sound engine
|
|
|
|
(https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6/4.x with audio shield.
|
|
|
|
Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android
|
|
|
|
|
|
|
|
(c)2018-2021 H. Wirtz <wirtz@parasitstudio.de>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software Foundation,
|
|
|
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
//extern config_t configuration;
|
|
|
|
|
|
|
|
Dexed::Dexed(uint8_t maxnotes, int rate)
|
|
|
|
{
|
|
|
|
Exp2::init();
|
|
|
|
Tanh::init();
|
|
|
|
Sin::init();
|
|
|
|
|
|
|
|
Freqlut::init(rate);
|
|
|
|
Lfo::init(rate);
|
|
|
|
PitchEnv::init(rate);
|
|
|
|
Env::init_sr(rate);
|
|
|
|
Porta::init_sr(rate);
|
|
|
|
fx.init(rate);
|
|
|
|
|
|
|
|
engineMsfa = new FmCore;
|
|
|
|
max_notes=maxnotes;
|
|
|
|
currentNote = 0;
|
|
|
|
resetControllers();
|
|
|
|
controllers.masterTune = 0;
|
|
|
|
controllers.opSwitch = 0x3f; // enable all operators
|
|
|
|
lastKeyDown = -1;
|
|
|
|
vuSignal = 0.0;
|
|
|
|
controllers.core = engineMsfa;
|
|
|
|
lfo.reset(data + 137);
|
|
|
|
sustain = false;
|
|
|
|
voices=NULL;
|
|
|
|
|
|
|
|
setMaxNotes(max_notes);
|
|
|
|
setMonoMode(false);
|
|
|
|
loadInitVoice();
|
|
|
|
|
|
|
|
xrun = 0;
|
|
|
|
render_time_max = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
Dexed::~Dexed()
|
|
|
|
{
|
|
|
|
currentNote = -1;
|
|
|
|
|
|
|
|
for (uint8_t note = 0; note < max_notes; note++)
|
|
|
|
delete voices[note].dx7_note;
|
|
|
|
|
|
|
|
for (uint8_t note = 0; note < max_notes; note++)
|
|
|
|
delete &voices[note];
|
|
|
|
|
|
|
|
delete(engineMsfa);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setMaxNotes(uint8_t new_max_notes)
|
|
|
|
{
|
|
|
|
uint8_t i=0;
|
|
|
|
|
|
|
|
max_notes=constrain(max_notes,0,_MAX_NOTES);
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print("Allocating memory for ");
|
|
|
|
Serial.print(max_notes,DEC);
|
|
|
|
Serial.println(" notes.");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if(voices)
|
|
|
|
{
|
|
|
|
panic();
|
|
|
|
for (i = 0; i < max_notes; i++)
|
|
|
|
{
|
|
|
|
if(voices[i].dx7_note)
|
|
|
|
delete voices[i].dx7_note;
|
|
|
|
}
|
|
|
|
delete(voices);
|
|
|
|
}
|
|
|
|
|
|
|
|
max_notes=constrain(new_max_notes,0,_MAX_NOTES);
|
|
|
|
|
|
|
|
if(max_notes>0)
|
|
|
|
{
|
|
|
|
voices=new ProcessorVoice[max_notes];
|
|
|
|
for (i = 0; i < max_notes; i++)
|
|
|
|
{
|
|
|
|
voices[i].dx7_note = new Dx7Note;
|
|
|
|
voices[i].keydown = false;
|
|
|
|
voices[i].sustained = false;
|
|
|
|
voices[i].live = false;
|
|
|
|
voices[i].key_pressed_timer = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
voices=NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::activate(void)
|
|
|
|
{
|
|
|
|
panic();
|
|
|
|
controllers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::deactivate(void)
|
|
|
|
{
|
|
|
|
panic();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::getSamples(uint16_t n_samples, int16_t* buffer)
|
|
|
|
{
|
|
|
|
uint16_t i, j;
|
|
|
|
uint8_t note;
|
|
|
|
float sumbuf[n_samples];
|
|
|
|
float s;
|
|
|
|
const double decayFactor = 0.99992;
|
|
|
|
|
|
|
|
if (refreshVoice)
|
|
|
|
{
|
|
|
|
for (i = 0; i < max_notes; i++)
|
|
|
|
{
|
|
|
|
if ( voices[i].live )
|
|
|
|
voices[i].dx7_note->update(data, voices[i].midi_note, voices[i].velocity, voices[i].porta, &controllers);
|
|
|
|
}
|
|
|
|
lfo.reset(data + 137);
|
|
|
|
refreshVoice = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < n_samples; i += _N_)
|
|
|
|
{
|
|
|
|
AlignedBuf<int32_t, _N_> audiobuf;
|
|
|
|
|
|
|
|
for (uint8_t j = 0; j < _N_; ++j)
|
|
|
|
{
|
|
|
|
audiobuf.get()[j] = 0;
|
|
|
|
sumbuf[i + j] = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t lfovalue = lfo.getsample();
|
|
|
|
int32_t lfodelay = lfo.getdelay();
|
|
|
|
|
|
|
|
for (note = 0; note < max_notes; note++)
|
|
|
|
{
|
|
|
|
if (voices[note].live)
|
|
|
|
{
|
|
|
|
voices[note].dx7_note->compute(audiobuf.get(), lfovalue, lfodelay, &controllers);
|
|
|
|
|
|
|
|
for (j = 0; j < _N_; ++j)
|
|
|
|
{
|
|
|
|
sumbuf[i + j] += signed_saturate_rshift(audiobuf.get()[j] >> 4, 24, 9) / 32768.0;
|
|
|
|
audiobuf.get()[j] = 0;
|
|
|
|
/*
|
|
|
|
int32_t val = audiobuf.get()[j];
|
|
|
|
val = val >> 4;
|
|
|
|
int32_t clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff : val >> 9;
|
|
|
|
float f = ((float) clip_val) / (float) 0x8000;
|
|
|
|
if ( f > 1.0 ) f = 1.0;
|
|
|
|
if ( f < -1.0 ) f = -1.0;
|
|
|
|
sumbuf[j] += f;
|
|
|
|
audiobuf.get()[j] = 0;
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fx.process(sumbuf, n_samples); // Needed for fx.Gain()!!!
|
|
|
|
|
|
|
|
// mild compression
|
|
|
|
for (i = 0; i < n_samples; i++)
|
|
|
|
{
|
|
|
|
s = abs(sumbuf[i]);
|
|
|
|
if (s > vuSignal)
|
|
|
|
vuSignal = s;
|
|
|
|
//else if (vuSignal > 0.001f)
|
|
|
|
else if (vuSignal > 0.0005f)
|
|
|
|
vuSignal *= decayFactor;
|
|
|
|
else
|
|
|
|
vuSignal = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//arm_scale_f32(sumbuf, 0.00015, sumbuf, AUDIO_BLOCK_SAMPLES);
|
|
|
|
arm_float_to_q15(sumbuf, buffer, AUDIO_BLOCK_SAMPLES);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::keydown(int16_t pitch, uint8_t velo) {
|
|
|
|
if ( velo == 0 ) {
|
|
|
|
keyup(pitch);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pitch += data[144] - TRANSPOSE_FIX;
|
|
|
|
|
|
|
|
int previousKeyDown = lastKeyDown;
|
|
|
|
lastKeyDown = pitch;
|
|
|
|
|
|
|
|
int porta = -1;
|
|
|
|
if ( controllers.portamento_enable_cc && previousKeyDown >= 0 )
|
|
|
|
porta = controllers.portamento_cc;
|
|
|
|
|
|
|
|
uint8_t note = currentNote;
|
|
|
|
uint8_t keydown_counter = 0;
|
|
|
|
|
|
|
|
if (!monoMode && refreshMode)
|
|
|
|
{
|
|
|
|
for (uint8_t i = 0; i < max_notes; i++)
|
|
|
|
{
|
|
|
|
if (voices[i].midi_note == pitch && voices[i].keydown == false && voices[i].live && voices[i].sustained == true)
|
|
|
|
{
|
|
|
|
// retrigger or refresh note?
|
|
|
|
voices[i].dx7_note->keyup();
|
|
|
|
voices[i].midi_note = pitch;
|
|
|
|
voices[i].velocity = velo;
|
|
|
|
voices[i].keydown = true;
|
|
|
|
voices[i].sustained = sustain;
|
|
|
|
voices[i].live = true;
|
|
|
|
voices[i].dx7_note->init(data, pitch, velo, pitch, porta, &controllers);
|
|
|
|
voices[i].key_pressed_timer = millis();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i <= max_notes; i++)
|
|
|
|
{
|
|
|
|
if (i == max_notes)
|
|
|
|
{
|
|
|
|
uint32_t min_timer = 0xffff;
|
|
|
|
|
|
|
|
if (monoMode)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// no free sound slot found, so use the oldest note slot
|
|
|
|
for (uint8_t n = 0; n < max_notes; n++)
|
|
|
|
{
|
|
|
|
if (voices[n].key_pressed_timer < min_timer)
|
|
|
|
{
|
|
|
|
min_timer = voices[n].key_pressed_timer;
|
|
|
|
note = n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
voices[note].keydown = false;
|
|
|
|
voices[note].sustained = false;
|
|
|
|
voices[note].live = false;
|
|
|
|
voices[note].key_pressed_timer = 0;
|
|
|
|
keydown_counter--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!voices[note].keydown)
|
|
|
|
{
|
|
|
|
currentNote = (note + 1) % max_notes;
|
|
|
|
voices[note].midi_note = pitch;
|
|
|
|
voices[note].velocity = velo;
|
|
|
|
voices[note].sustained = sustain;
|
|
|
|
voices[note].keydown = true;
|
|
|
|
int srcnote = (previousKeyDown >= 0) ? previousKeyDown : pitch;
|
|
|
|
voices[note].dx7_note->init(data, pitch, velo, srcnote, porta, &controllers);
|
|
|
|
if ( data[136] )
|
|
|
|
voices[note].dx7_note->oscSync();
|
|
|
|
voices[i].key_pressed_timer = millis();
|
|
|
|
keydown_counter++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
keydown_counter++;
|
|
|
|
}
|
|
|
|
note = (note + 1) % max_notes;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (keydown_counter == 0)
|
|
|
|
lfo.keydown();
|
|
|
|
|
|
|
|
if ( monoMode ) {
|
|
|
|
for (uint8_t i = 0; i < max_notes; i++) {
|
|
|
|
if ( voices[i].live ) {
|
|
|
|
// all keys are up, only transfer signal
|
|
|
|
if ( ! voices[i].keydown ) {
|
|
|
|
voices[i].live = false;
|
|
|
|
voices[note].dx7_note->transferSignal(*voices[i].dx7_note);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if ( voices[i].midi_note < pitch ) {
|
|
|
|
voices[i].live = false;
|
|
|
|
voices[note].dx7_note->transferState(*voices[i].dx7_note);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
voices[note].live = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::keyup(int16_t pitch) {
|
|
|
|
uint8_t note;
|
|
|
|
|
|
|
|
pitch = constrain(pitch, 0, 127);
|
|
|
|
|
|
|
|
pitch += data[144] - TRANSPOSE_FIX;
|
|
|
|
|
|
|
|
for (note = 0; note < max_notes; note++) {
|
|
|
|
if ( voices[note].midi_note == pitch && voices[note].keydown ) {
|
|
|
|
voices[note].keydown = false;
|
|
|
|
voices[note].key_pressed_timer = 0;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// note not found ?
|
|
|
|
if ( note >= max_notes ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( monoMode ) {
|
|
|
|
int16_t highNote = -1;
|
|
|
|
uint8_t target = 0;
|
|
|
|
for (int8_t i = 0; i < max_notes; i++) {
|
|
|
|
if ( voices[i].keydown && voices[i].midi_note > highNote ) {
|
|
|
|
target = i;
|
|
|
|
highNote = voices[i].midi_note;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( highNote != -1 && voices[note].live ) {
|
|
|
|
voices[note].live = false;
|
|
|
|
voices[note].key_pressed_timer = 0;
|
|
|
|
voices[target].live = true;
|
|
|
|
voices[target].dx7_note->transferState(*voices[note].dx7_note);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( sustain ) {
|
|
|
|
voices[note].sustained = true;
|
|
|
|
} else {
|
|
|
|
voices[note].dx7_note->keyup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::doRefreshVoice(void)
|
|
|
|
{
|
|
|
|
refreshVoice = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPAll(uint8_t ops)
|
|
|
|
{
|
|
|
|
controllers.opSwitch = ops;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Dexed::getMonoMode(void) {
|
|
|
|
return monoMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setMonoMode(bool mode) {
|
|
|
|
if (monoMode == mode)
|
|
|
|
return;
|
|
|
|
|
|
|
|
notesOff();
|
|
|
|
monoMode = mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setRefreshMode(bool mode) {
|
|
|
|
refreshMode = mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setSustain(bool s)
|
|
|
|
{
|
|
|
|
if (sustain == s)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sustain = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Dexed::getSustain(void)
|
|
|
|
{
|
|
|
|
return sustain;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::panic(void)
|
|
|
|
{
|
|
|
|
for (uint8_t i = 0; i < max_notes; i++)
|
|
|
|
{
|
|
|
|
if (voices[i].live == true) {
|
|
|
|
voices[i].keydown = false;
|
|
|
|
voices[i].live = false;
|
|
|
|
voices[i].sustained = false;
|
|
|
|
voices[i].key_pressed_timer = 0;
|
|
|
|
if ( voices[i].dx7_note != NULL ) {
|
|
|
|
voices[i].dx7_note->oscSync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setSustain(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::resetControllers(void)
|
|
|
|
{
|
|
|
|
controllers.values_[kControllerPitch] = 0x2000;
|
|
|
|
controllers.values_[kControllerPitchRange] = 0;
|
|
|
|
controllers.values_[kControllerPitchStep] = 0;
|
|
|
|
controllers.values_[kControllerPortamentoGlissando] = 0;
|
|
|
|
|
|
|
|
controllers.modwheel_cc = 0;
|
|
|
|
controllers.foot_cc = 0;
|
|
|
|
controllers.breath_cc = 0;
|
|
|
|
controllers.aftertouch_cc = 0;
|
|
|
|
controllers.portamento_enable_cc = false;
|
|
|
|
controllers.portamento_cc = 0;
|
|
|
|
controllers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::notesOff(void) {
|
|
|
|
for (uint8_t i = 0; i < max_notes; i++) {
|
|
|
|
if (voices[i].live == true) {
|
|
|
|
voices[i].keydown = false;
|
|
|
|
voices[i].live = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getMaxNotes(void)
|
|
|
|
{
|
|
|
|
return max_notes;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getNumNotesPlaying(void)
|
|
|
|
{
|
|
|
|
uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers
|
|
|
|
uint8_t i;
|
|
|
|
uint8_t count_playing_voices = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < max_notes; i++)
|
|
|
|
{
|
|
|
|
if (voices[i].live == true)
|
|
|
|
{
|
|
|
|
uint8_t op_amp = 0;
|
|
|
|
uint8_t op_carrier_num = 0;
|
|
|
|
|
|
|
|
memset(&voiceStatus, 0, sizeof(VoiceStatus));
|
|
|
|
voices[i].dx7_note->peekVoiceStatus(voiceStatus);
|
|
|
|
|
|
|
|
for (uint8_t op = 0; op < 6; op++)
|
|
|
|
{
|
|
|
|
if ((op_carrier & (1 << op)))
|
|
|
|
{
|
|
|
|
// this voice is a carrier!
|
|
|
|
op_carrier_num++;
|
|
|
|
if (voiceStatus.amp[op] <= VOICE_SILENCE_LEVEL && voiceStatus.ampStep[op] == 4)
|
|
|
|
{
|
|
|
|
// this voice produces no audio output
|
|
|
|
op_amp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (op_amp == op_carrier_num)
|
|
|
|
{
|
|
|
|
// all carrier-operators are silent -> disable the voice
|
|
|
|
voices[i].live = false;
|
|
|
|
voices[i].sustained = false;
|
|
|
|
voices[i].keydown = false;
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("Shutdown voice: "));
|
|
|
|
Serial.println(i, DEC);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
count_playing_voices++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (count_playing_voices);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Dexed::decodeVoice(uint8_t* new_data, uint8_t* encoded_data)
|
|
|
|
{
|
|
|
|
uint8_t* p_data = new_data;
|
|
|
|
uint8_t op;
|
|
|
|
uint8_t tmp;
|
|
|
|
char dexed_voice_name[11];
|
|
|
|
|
|
|
|
panic();
|
|
|
|
|
|
|
|
for (op = 0; op < 6; op++)
|
|
|
|
{
|
|
|
|
// DEXED_OP_EG_R1, // 0
|
|
|
|
// DEXED_OP_EG_R2, // 1
|
|
|
|
// DEXED_OP_EG_R3, // 2
|
|
|
|
// DEXED_OP_EG_R4, // 3
|
|
|
|
// DEXED_OP_EG_L1, // 4
|
|
|
|
// DEXED_OP_EG_L2, // 5
|
|
|
|
// DEXED_OP_EG_L3, // 6
|
|
|
|
// DEXED_OP_EG_L4, // 7
|
|
|
|
// DEXED_OP_LEV_SCL_BRK_PT, // 8
|
|
|
|
// DEXED_OP_SCL_LEFT_DEPTH, // 9
|
|
|
|
// DEXED_OP_SCL_RGHT_DEPTH, // 10
|
|
|
|
memcpy(&new_data[op * 21], &encoded_data[op * 17], 11);
|
|
|
|
tmp = encoded_data[(op * 17) + 11];
|
|
|
|
*(p_data + DEXED_OP_SCL_LEFT_CURVE + (op * 21)) = (tmp & 0x03);
|
|
|
|
*(p_data + DEXED_OP_SCL_RGHT_CURVE + (op * 21)) = (tmp & 0x0c) >> 2;
|
|
|
|
tmp = encoded_data[(op * 17) + 12];
|
|
|
|
*(p_data + DEXED_OP_OSC_DETUNE + (op * 21)) = (tmp & 0x78) >> 3;
|
|
|
|
*(p_data + DEXED_OP_OSC_RATE_SCALE + (op * 21)) = (tmp & 0x07);
|
|
|
|
tmp = encoded_data[(op * 17) + 13];
|
|
|
|
*(p_data + DEXED_OP_KEY_VEL_SENS + (op * 21)) = (tmp & 0x1c) >> 2;
|
|
|
|
*(p_data + DEXED_OP_AMP_MOD_SENS + (op * 21)) = (tmp & 0x03);
|
|
|
|
*(p_data + DEXED_OP_OUTPUT_LEV + (op * 21)) = encoded_data[(op * 17) + 14];
|
|
|
|
tmp = encoded_data[(op * 17) + 15];
|
|
|
|
*(p_data + DEXED_OP_FREQ_COARSE + (op * 21)) = (tmp & 0x3e) >> 1;
|
|
|
|
*(p_data + DEXED_OP_OSC_MODE + (op * 21)) = (tmp & 0x01);
|
|
|
|
*(p_data + DEXED_OP_FREQ_FINE + (op * 21)) = encoded_data[(op * 17) + 16];
|
|
|
|
}
|
|
|
|
// DEXED_PITCH_EG_R1, // 0
|
|
|
|
// DEXED_PITCH_EG_R2, // 1
|
|
|
|
// DEXED_PITCH_EG_R3, // 2
|
|
|
|
// DEXED_PITCH_EG_R4, // 3
|
|
|
|
// DEXED_PITCH_EG_L1, // 4
|
|
|
|
// DEXED_PITCH_EG_L2, // 5
|
|
|
|
// DEXED_PITCH_EG_L3, // 6
|
|
|
|
// DEXED_PITCH_EG_L4, // 7
|
|
|
|
memcpy(&new_data[DEXED_VOICE_OFFSET], &encoded_data[102], 8);
|
|
|
|
tmp = encoded_data[110];
|
|
|
|
*(p_data + DEXED_VOICE_OFFSET + DEXED_ALGORITHM) = (tmp & 0x1f);
|
|
|
|
tmp = encoded_data[111];
|
|
|
|
*(p_data + DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC) = (tmp & 0x08) >> 3;
|
|
|
|
*(p_data + DEXED_VOICE_OFFSET + DEXED_FEEDBACK) = (tmp & 0x07);
|
|
|
|
// DEXED_LFO_SPEED, // 11
|
|
|
|
// DEXED_LFO_DELAY, // 12
|
|
|
|
// DEXED_LFO_PITCH_MOD_DEP, // 13
|
|
|
|
// DEXED_LFO_AMP_MOD_DEP, // 14
|
|
|
|
memcpy(&new_data[DEXED_VOICE_OFFSET + DEXED_LFO_SPEED], &encoded_data[112], 4);
|
|
|
|
tmp = encoded_data[116];
|
|
|
|
*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS) = (tmp & 0x30) >> 4;
|
|
|
|
*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_WAVE) = (tmp & 0x0e) >> 1;
|
|
|
|
*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_SYNC) = (tmp & 0x01);
|
|
|
|
*(p_data + DEXED_VOICE_OFFSET + DEXED_TRANSPOSE) = encoded_data[117];
|
|
|
|
memcpy(&new_data[DEXED_VOICE_OFFSET + DEXED_NAME], &encoded_data[118], 10);
|
|
|
|
panic();
|
|
|
|
doRefreshVoice();
|
|
|
|
|
|
|
|
strncpy(dexed_voice_name, (char *)&encoded_data[118], sizeof(dexed_voice_name) - 1);
|
|
|
|
dexed_voice_name[10] = '\0';
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.print(F("Voice ["));
|
|
|
|
Serial.print(dexed_voice_name);
|
|
|
|
Serial.println(F("] decoded."));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Dexed::encodeVoice(uint8_t* encoded_data)
|
|
|
|
{
|
|
|
|
uint8_t* p_data = data;
|
|
|
|
uint8_t op;
|
|
|
|
|
|
|
|
for (op = 0; op < 6; op++)
|
|
|
|
{
|
|
|
|
// DEXED_OP_EG_R1, // 0
|
|
|
|
// DEXED_OP_EG_R2, // 1
|
|
|
|
// DEXED_OP_EG_R3, // 2
|
|
|
|
// DEXED_OP_EG_R4, // 3
|
|
|
|
// DEXED_OP_EG_L1, // 4
|
|
|
|
// DEXED_OP_EG_L2, // 5
|
|
|
|
// DEXED_OP_EG_L3, // 6
|
|
|
|
// DEXED_OP_EG_L4, // 7
|
|
|
|
// DEXED_OP_LEV_SCL_BRK_PT, // 8
|
|
|
|
// DEXED_OP_SCL_LEFT_DEPTH, // 9
|
|
|
|
// DEXED_OP_SCL_RGHT_DEPTH, // 10
|
|
|
|
memcpy(&encoded_data[op * 17], &data[op * 21], 11);
|
|
|
|
encoded_data[(op * 17) + 11] = ((*(p_data + DEXED_OP_SCL_RGHT_CURVE + (op * 21)) & 0x0c) << 2) | (*(p_data + DEXED_OP_SCL_LEFT_CURVE + (op * 21)) & 0x03);
|
|
|
|
encoded_data[(op * 17) + 12] = ((*(p_data + DEXED_OP_OSC_DETUNE + (op * 21)) & 0x0f) << 3) | (*(p_data + DEXED_OP_OSC_RATE_SCALE + (op * 21)) & 0x07);
|
|
|
|
encoded_data[(op * 17) + 13] = ((*(p_data + DEXED_OP_KEY_VEL_SENS + (op * 21)) & 0x07) << 2) | (*(p_data + DEXED_OP_AMP_MOD_SENS + (op * 21)) & 0x03);
|
|
|
|
encoded_data[(op * 17) + 14] = *(p_data + DEXED_OP_OUTPUT_LEV + (op * 21));
|
|
|
|
encoded_data[(op * 17) + 15] = ((*(p_data + DEXED_OP_FREQ_COARSE + (op * 21)) & 0x1f) << 1) | (*(p_data + DEXED_OP_OSC_MODE + (op * 21)) & 0x01);
|
|
|
|
encoded_data[(op * 17) + 16] = *(p_data + DEXED_OP_FREQ_FINE + (op * 21));
|
|
|
|
}
|
|
|
|
// DEXED_PITCH_EG_R1, // 0
|
|
|
|
// DEXED_PITCH_EG_R2, // 1
|
|
|
|
// DEXED_PITCH_EG_R3, // 2
|
|
|
|
// DEXED_PITCH_EG_R4, // 3
|
|
|
|
// DEXED_PITCH_EG_L1, // 4
|
|
|
|
// DEXED_PITCH_EG_L2, // 5
|
|
|
|
// DEXED_PITCH_EG_L3, // 6
|
|
|
|
// DEXED_PITCH_EG_L4, // 7
|
|
|
|
memcpy(&encoded_data[102], &data[DEXED_VOICE_OFFSET], 8);
|
|
|
|
encoded_data[110] = (*(p_data + DEXED_VOICE_OFFSET + DEXED_ALGORITHM) & 0x1f);
|
|
|
|
encoded_data[111] = (((*(p_data + DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC) & 0x01) << 3) | ((*(p_data + DEXED_VOICE_OFFSET + DEXED_FEEDBACK)) & 0x07));
|
|
|
|
// DEXED_LFO_SPEED, // 11
|
|
|
|
// DEXED_LFO_DELAY, // 12
|
|
|
|
// DEXED_LFO_PITCH_MOD_DEP, // 13
|
|
|
|
// DEXED_LFO_AMP_MOD_DEP, // 14
|
|
|
|
memcpy(&encoded_data[112], &data[DEXED_VOICE_OFFSET + DEXED_LFO_SPEED], 4);
|
|
|
|
encoded_data[116] = (((*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS) & 0x07) << 4) | (((*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_WAVE)) & 0x07) << 1) | ((*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_SYNC)) & 0x01));
|
|
|
|
encoded_data[117] = *(p_data + DEXED_VOICE_OFFSET + DEXED_TRANSPOSE);
|
|
|
|
memset(&encoded_data[118], 0, 10);
|
|
|
|
memcpy(&encoded_data[118], &data[DEXED_VOICE_OFFSET + DEXED_NAME], 10);
|
|
|
|
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Dexed::getVoiceData(uint8_t* data_copy)
|
|
|
|
{
|
|
|
|
memcpy(data_copy, data, sizeof(data));
|
|
|
|
return (true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setVoiceDataElement(uint8_t address, uint8_t value)
|
|
|
|
{
|
|
|
|
address = constrain(address, 0, NUM_VOICE_PARAMETERS);
|
|
|
|
data[address] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getVoiceDataElement(uint8_t address)
|
|
|
|
{
|
|
|
|
address = constrain(address, 0, NUM_VOICE_PARAMETERS);
|
|
|
|
return (data[address]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::loadVoiceParameters(uint8_t* new_data)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
|
|
char dexed_voice_name[11];
|
|
|
|
#endif
|
|
|
|
|
|
|
|
panic();
|
|
|
|
memcpy(&data, new_data, 155);
|
|
|
|
doRefreshVoice();
|
|
|
|
#ifdef DEBUG
|
|
|
|
strncpy(dexed_voice_name, (char *)&new_data[145], sizeof(dexed_voice_name) - 1);
|
|
|
|
dexed_voice_name[10] = '\0';
|
|
|
|
|
|
|
|
Serial.print(F("Voice ["));
|
|
|
|
Serial.print(dexed_voice_name);
|
|
|
|
Serial.println(F("] loaded."));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::loadInitVoice(void)
|
|
|
|
{
|
|
|
|
loadVoiceParameters(init_voice);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setPBController(uint8_t pb_range, uint8_t pb_step)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("Dexed::setPBController"));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
pb_range = constrain(pb_range, 0, 12);
|
|
|
|
pb_step = constrain(pb_step, 0, 12);
|
|
|
|
|
|
|
|
controllers.values_[kControllerPitchRange] = pb_range;
|
|
|
|
controllers.values_[kControllerPitchStep] = pb_step;
|
|
|
|
|
|
|
|
controllers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setMWController(uint8_t mw_range, uint8_t mw_assign, uint8_t mw_mode)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("Dexed::setMWController"));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
mw_range = constrain(mw_range, 0, 99);
|
|
|
|
mw_assign = constrain(mw_assign, 0, 7);
|
|
|
|
mw_mode = constrain(mw_mode, 0, MIDI_CONTROLLER_MODE_MAX);
|
|
|
|
|
|
|
|
controllers.wheel.setRange(mw_range);
|
|
|
|
controllers.wheel.setTarget(mw_assign);
|
|
|
|
controllers.wheel.setMode(mw_mode);
|
|
|
|
|
|
|
|
controllers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setFCController(uint8_t fc_range, uint8_t fc_assign, uint8_t fc_mode)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("Dexed::setFCController"));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
fc_range = constrain(fc_range, 0, 99);
|
|
|
|
fc_assign = constrain(fc_assign, 0, 7);
|
|
|
|
fc_mode = constrain(fc_mode, 0, MIDI_CONTROLLER_MODE_MAX);
|
|
|
|
|
|
|
|
controllers.foot.setRange(fc_range);
|
|
|
|
controllers.foot.setTarget(fc_assign);
|
|
|
|
controllers.foot.setMode(fc_mode);
|
|
|
|
|
|
|
|
controllers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setBCController(uint8_t bc_range, uint8_t bc_assign, uint8_t bc_mode)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("Dexed::setBCController"));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
bc_range = constrain(bc_range, 0, 99);
|
|
|
|
bc_assign = constrain(bc_assign, 0, 7);
|
|
|
|
bc_mode = constrain(bc_mode, 0, MIDI_CONTROLLER_MODE_MAX);
|
|
|
|
|
|
|
|
controllers.breath.setRange(bc_range);
|
|
|
|
controllers.breath.setTarget(bc_assign);
|
|
|
|
controllers.breath.setMode(bc_mode);
|
|
|
|
|
|
|
|
controllers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setATController(uint8_t at_range, uint8_t at_assign, uint8_t at_mode)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
|
|
Serial.println(F("Dexed::setATController"));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
at_range = constrain(at_range, 0, 99);
|
|
|
|
at_assign = constrain(at_assign, 0, 7);
|
|
|
|
at_mode = constrain(at_mode, 0, MIDI_CONTROLLER_MODE_MAX);
|
|
|
|
|
|
|
|
controllers.at.setRange(at_range);
|
|
|
|
controllers.at.setTarget(at_assign);
|
|
|
|
controllers.at.setMode(at_mode);
|
|
|
|
|
|
|
|
controllers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setPortamentoMode(uint8_t portamento_mode, uint8_t portamento_glissando, uint8_t portamento_time)
|
|
|
|
{
|
|
|
|
portamento_mode = constrain(portamento_mode, 0, 1);
|
|
|
|
portamento_glissando = constrain(portamento_glissando, 0, 1);
|
|
|
|
portamento_mode = constrain(portamento_mode, 0, 99);
|
|
|
|
|
|
|
|
controllers.portamento_cc = portamento_time;
|
|
|
|
controllers.portamento_enable_cc = portamento_mode > 63;
|
|
|
|
|
|
|
|
if (portamento_time > 0)
|
|
|
|
controllers.portamento_enable_cc = true;
|
|
|
|
else
|
|
|
|
controllers.portamento_enable_cc = false;
|
|
|
|
|
|
|
|
controllers.values_[kControllerPortamentoGlissando] = portamento_glissando;
|
|
|
|
|
|
|
|
controllers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Dexed::getXRun(void)
|
|
|
|
{
|
|
|
|
return (xrun);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t Dexed::getRenderTimeMax(void)
|
|
|
|
{
|
|
|
|
return (render_time_max);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::resetRenderTimeMax(void)
|
|
|
|
{
|
|
|
|
render_time_max = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::ControllersRefresh(void)
|
|
|
|
{
|
|
|
|
controllers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setMasterTune(int8_t mastertune)
|
|
|
|
{
|
|
|
|
mastertune = constrain(mastertune, -99, 99);
|
|
|
|
|
|
|
|
controllers.masterTune = (int(mastertune / 100.0 * 0x4000) << 11) * (1.0 / 12.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int8_t Dexed::getMasterTune(void)
|
|
|
|
{
|
|
|
|
return (controllers.masterTune);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setModWheel(uint8_t value)
|
|
|
|
{
|
|
|
|
value = constrain(value, 0, 127);
|
|
|
|
|
|
|
|
controllers.modwheel_cc = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getModWheel(void)
|
|
|
|
{
|
|
|
|
return (controllers.modwheel_cc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setBreathController(uint8_t value)
|
|
|
|
{
|
|
|
|
value = constrain(value, 0, 127);
|
|
|
|
|
|
|
|
controllers.breath_cc = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getBreathController(void)
|
|
|
|
{
|
|
|
|
return (controllers.breath_cc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setFootController(uint8_t value)
|
|
|
|
{
|
|
|
|
value = constrain(value, 0, 127);
|
|
|
|
|
|
|
|
controllers.foot_cc = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getFootController(void)
|
|
|
|
{
|
|
|
|
return (controllers.foot_cc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setAftertouch(uint8_t value)
|
|
|
|
{
|
|
|
|
value = constrain(value, 0, 127);
|
|
|
|
|
|
|
|
controllers.aftertouch_cc = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getAftertouch(void)
|
|
|
|
{
|
|
|
|
return (controllers.aftertouch_cc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setPitchbend(int16_t value)
|
|
|
|
{
|
|
|
|
value = constrain(value, -8192, 8191);
|
|
|
|
|
|
|
|
controllers.values_[kControllerPitch] = value + 0x2000; // -8192 to +8191 --> 0 to 16383
|
|
|
|
}
|
|
|
|
|
|
|
|
int16_t Dexed::getPitchbend(void)
|
|
|
|
{
|
|
|
|
return (controllers.values_[kControllerPitch] - 0x2000);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setPitchbendRange(uint8_t range)
|
|
|
|
{
|
|
|
|
range = constrain(range, 0, 12);
|
|
|
|
|
|
|
|
controllers.values_[kControllerPitchRange] = range;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getPitchbendRange(void)
|
|
|
|
{
|
|
|
|
return (controllers.values_[kControllerPitchRange]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setPitchbendStep(uint8_t step)
|
|
|
|
{
|
|
|
|
step = constrain(step, 0, 12);
|
|
|
|
|
|
|
|
controllers.values_[kControllerPitchStep] = step;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getPitchbendStep(void)
|
|
|
|
{
|
|
|
|
return (controllers.values_[kControllerPitchStep]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setModWheelRange(uint8_t range)
|
|
|
|
{
|
|
|
|
range = constrain(range, 0, 12);
|
|
|
|
|
|
|
|
controllers.wheel.setRange(range);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getModWheelRange(void)
|
|
|
|
{
|
|
|
|
return (controllers.wheel.getRange());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setModWheelTarget(uint8_t target)
|
|
|
|
{
|
|
|
|
target = constrain(target, 0, 7);
|
|
|
|
|
|
|
|
controllers.wheel.setTarget(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getModWheelTarget(void)
|
|
|
|
{
|
|
|
|
return (controllers.wheel.getTarget());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setFootControllerRange(uint8_t range)
|
|
|
|
{
|
|
|
|
range = constrain(range, 0, 12);
|
|
|
|
|
|
|
|
controllers.foot.setRange(range);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getFootControllerRange(void)
|
|
|
|
{
|
|
|
|
return (controllers.foot.getRange());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setFootControllerTarget(uint8_t target)
|
|
|
|
{
|
|
|
|
target = constrain(target, 0, 7);
|
|
|
|
|
|
|
|
controllers.foot.setTarget(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getFootControllerTarget(void)
|
|
|
|
{
|
|
|
|
return (controllers.foot.getTarget());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setBreathControllerRange(uint8_t range)
|
|
|
|
{
|
|
|
|
range = constrain(range, 0, 12);
|
|
|
|
|
|
|
|
controllers.breath.setRange(range);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getBreathControllerRange(void)
|
|
|
|
{
|
|
|
|
return (controllers.breath.getRange());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setBreathControllerTarget(uint8_t target)
|
|
|
|
{
|
|
|
|
target = constrain(target, 0, 7);
|
|
|
|
|
|
|
|
controllers.breath.setTarget(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getBreathControllerTarget(void)
|
|
|
|
{
|
|
|
|
return (controllers.breath.getTarget());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setAftertouchRange(uint8_t range)
|
|
|
|
{
|
|
|
|
range = constrain(range, 0, 12);
|
|
|
|
|
|
|
|
controllers.at.setRange(range);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getAftertouchRange(void)
|
|
|
|
{
|
|
|
|
return (controllers.at.getRange());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setAftertouchTarget(uint8_t target)
|
|
|
|
{
|
|
|
|
target = constrain(target, 0, 7);
|
|
|
|
|
|
|
|
controllers.at.setTarget(target);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getAftertouchTarget(void)
|
|
|
|
{
|
|
|
|
return (controllers.at.getTarget());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setFilterCutoff(float cutoff)
|
|
|
|
{
|
|
|
|
fx.Cutoff = cutoff;
|
|
|
|
}
|
|
|
|
|
|
|
|
float Dexed::getFilterCutoff(void)
|
|
|
|
{
|
|
|
|
return (fx.Cutoff);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setFilterResonance(float resonance)
|
|
|
|
{
|
|
|
|
fx.Reso = resonance;
|
|
|
|
}
|
|
|
|
|
|
|
|
float Dexed::getFilterResonance(void)
|
|
|
|
{
|
|
|
|
return (fx.Reso);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setGain(float gain)
|
|
|
|
{
|
|
|
|
fx.Gain = gain;
|
|
|
|
}
|
|
|
|
|
|
|
|
float Dexed::getGain(void)
|
|
|
|
{
|
|
|
|
return (fx.Gain);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPRateAll(uint8_t rate)
|
|
|
|
{
|
|
|
|
rate = constrain(rate, 0, 99);
|
|
|
|
|
|
|
|
for (uint8_t op = 0; op < 6; op++)
|
|
|
|
{
|
|
|
|
for (uint8_t step = 0; step < 4; step++)
|
|
|
|
{
|
|
|
|
data[(op * 21) + DEXED_OP_EG_R1 + step] = rate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPLevelAll(uint8_t level)
|
|
|
|
{
|
|
|
|
level = constrain(level, 0, 99);
|
|
|
|
|
|
|
|
for (uint8_t op = 0; op < 6; op++)
|
|
|
|
{
|
|
|
|
for (uint8_t step = 0; step < 4; step++)
|
|
|
|
{
|
|
|
|
data[(op * 21) + DEXED_OP_EG_L1 + step] = level;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPRateAllModulator(uint8_t step, uint8_t rate)
|
|
|
|
{
|
|
|
|
uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers
|
|
|
|
|
|
|
|
rate = constrain(rate, 0, 99);
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
|
|
|
|
for (uint8_t op = 0; op < 6; op++)
|
|
|
|
{
|
|
|
|
if ((op_carrier & (1 << op)) == 0)
|
|
|
|
data[(op * 21) + DEXED_OP_EG_R1 + step] = rate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPLevelAllModulator(uint8_t step, uint8_t level)
|
|
|
|
{
|
|
|
|
uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers
|
|
|
|
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
level = constrain(level, 0, 99);
|
|
|
|
|
|
|
|
for (uint8_t op = 0; op < 6; op++)
|
|
|
|
{
|
|
|
|
if ((op_carrier & (1 << op)) == 0)
|
|
|
|
data[(op * 21) + DEXED_OP_EG_L1 + step] = level;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPRateAllCarrier(uint8_t step, uint8_t rate)
|
|
|
|
{
|
|
|
|
uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers
|
|
|
|
|
|
|
|
rate = constrain(rate, 0, 99);
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
|
|
|
|
for (uint8_t op = 0; op < 6; op++)
|
|
|
|
{
|
|
|
|
if ((op_carrier & (1 << op)) == 1)
|
|
|
|
data[(op * 21) + DEXED_OP_EG_R1 + step] = rate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPLevelAllCarrier(uint8_t step, uint8_t level)
|
|
|
|
{
|
|
|
|
uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers
|
|
|
|
|
|
|
|
level = constrain(level, 0, 99);
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
|
|
|
|
for (uint8_t op = 0; op < 6; op++)
|
|
|
|
{
|
|
|
|
if ((op_carrier & (1 << op)) == 1)
|
|
|
|
data[(op * 21) + DEXED_OP_EG_L1 + step] = level;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPRate(uint8_t op, uint8_t step, uint8_t rate)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
rate = constrain(rate, 0, 99);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_EG_R1 + step] = rate;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPRate(uint8_t op, uint8_t step)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_EG_R1 + step]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPLevel(uint8_t op, uint8_t step, uint8_t level)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
level = constrain(level, 0, 99);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_EG_L1 + step] = level;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPLevel(uint8_t op, uint8_t step)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_EG_L1 + step]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPKeyboardLevelScalingBreakPoint(uint8_t op, uint8_t level)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
level = constrain(level, 0, 99);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_LEV_SCL_BRK_PT] = level;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPKeyboardLevelScalingBreakPoint(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_LEV_SCL_BRK_PT]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPKeyboardLevelScalingDepthLeft(uint8_t op, uint8_t depth)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
depth = constrain(depth, 0, 99);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_SCL_LEFT_DEPTH] = depth;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPKeyboardLevelScalingDepthLeft(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_SCL_LEFT_DEPTH]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPKeyboardLevelScalingDepthRight(uint8_t op, uint8_t depth)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
depth = constrain(depth, 0, 99);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_SCL_RGHT_DEPTH] = depth;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPKeyboardLevelScalingDepthRight(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_SCL_RGHT_DEPTH]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPKeyboardLevelScalingCurveLeft(uint8_t op, uint8_t curve)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
curve = constrain(curve, 0, 3);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_SCL_LEFT_CURVE] = curve;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPKeyboardLevelScalingCurveLeft(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_SCL_LEFT_CURVE]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPKeyboardLevelScalingCurveRight(uint8_t op, uint8_t curve)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
curve = constrain(curve, 0, 3);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_SCL_RGHT_CURVE] = curve;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPKeyboardLevelScalingCurveRight(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_SCL_RGHT_CURVE]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPKeyboardRateScale(uint8_t op, uint8_t scale)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
scale = constrain(scale, 0, 7);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_OSC_RATE_SCALE] = scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPKeyboardRateScale(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_OSC_RATE_SCALE]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPAmpModulationSensity(uint8_t op, uint8_t sensitivity)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
sensitivity = constrain(sensitivity, 0, 3);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_AMP_MOD_SENS] = sensitivity;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPAmpModulationSensity(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_AMP_MOD_SENS]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPKeyboardVelocitySensity(uint8_t op, uint8_t sensitivity)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
sensitivity = constrain(sensitivity, 0, 7);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_KEY_VEL_SENS] = sensitivity;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPKeyboardVelocitySensity(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_KEY_VEL_SENS]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPOutputLevel(uint8_t op, uint8_t level)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
level = constrain(level, 0, 99);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_OUTPUT_LEV] = level;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPOutputLevel(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_OUTPUT_LEV]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPMode(uint8_t op, uint8_t mode)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
mode = constrain(mode, 0, 1);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_OSC_MODE] = mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPMode(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_OSC_MODE]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPFrequencyCoarse(uint8_t op, uint8_t frq_coarse)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
frq_coarse = constrain(frq_coarse, 0, 31);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_FREQ_COARSE] = frq_coarse;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPFrequencyCoarse(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_FREQ_COARSE ]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPFrequencyFine(uint8_t op, uint8_t frq_fine)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
frq_fine = constrain(frq_fine, 0, 99);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_FREQ_FINE] = frq_fine;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPFrequencyFine(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_FREQ_FINE]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOPDetune(uint8_t op, uint8_t detune)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
detune = constrain(detune, 0, 14);
|
|
|
|
|
|
|
|
data[(op * 21) + DEXED_OP_OSC_DETUNE] = detune;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getOPDetune(uint8_t op)
|
|
|
|
{
|
|
|
|
op = constrain(op, 0, 5);
|
|
|
|
|
|
|
|
return (data[(op * 21) + DEXED_OP_OSC_DETUNE]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setPitchRate(uint8_t step, uint8_t rate)
|
|
|
|
{
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
rate = constrain(rate, 0, 99);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_PITCH_EG_R1 + step] = rate;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getPitchRate(uint8_t step)
|
|
|
|
{
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_PITCH_EG_R1 + step]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setPitchLevel(uint8_t step, uint8_t level)
|
|
|
|
{
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
level = constrain(level, 0, 99);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_PITCH_EG_L1 + step] = level;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getPitchLevel(uint8_t step)
|
|
|
|
{
|
|
|
|
step = constrain(step, 0, 3);
|
|
|
|
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_PITCH_EG_L1 + step]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setAlgorithm(uint8_t algorithm)
|
|
|
|
{
|
|
|
|
algorithm = constrain(algorithm, 0, 31);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_ALGORITHM] = algorithm;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getAlgorithm(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_ALGORITHM]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setFeedback(uint8_t feedback)
|
|
|
|
{
|
|
|
|
feedback = constrain(feedback, 0, 31);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_FEEDBACK] = feedback;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getFeedback(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_FEEDBACK]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setOscillatorSync(bool sync)
|
|
|
|
{
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC] = sync;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Dexed::getOscillatorSync(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setLFOSpeed(uint8_t speed)
|
|
|
|
{
|
|
|
|
speed = constrain(speed, 0, 99);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_LFO_SPEED] = speed;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getLFOSpeed(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_LFO_SPEED]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setLFODelay(uint8_t delay)
|
|
|
|
{
|
|
|
|
delay = constrain(delay, 0, 99);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_LFO_DELAY] = delay;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getLFODelay(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_LFO_DELAY]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setLFOPitchModulationDepth(uint8_t depth)
|
|
|
|
{
|
|
|
|
depth = constrain(depth, 0, 99);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_DEP] = depth;
|
|
|
|
}
|
|
|
|
uint8_t Dexed::getLFOPitchModulationDepth(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_DEP]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setLFOAmpModulationDepth(uint8_t depth)
|
|
|
|
{
|
|
|
|
depth = constrain(depth, 0, 99);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_LFO_AMP_MOD_DEP] = depth;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getLFOAmpModulationDepth(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_LFO_AMP_MOD_DEP]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setLFOSync(bool sync)
|
|
|
|
{
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_LFO_SYNC] = sync;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Dexed::getLFOSync(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_LFO_SYNC]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setLFOWaveform(uint8_t waveform)
|
|
|
|
{
|
|
|
|
waveform = constrain(waveform, 0, 5);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_LFO_WAVE] = waveform;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getLFOWaveform(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_LFO_WAVE]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setLFOPitchModulationSensitivity(uint8_t sensitivity)
|
|
|
|
{
|
|
|
|
sensitivity = constrain(sensitivity, 0, 5);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS] = sensitivity;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getLFOPitchModulationSensitivity(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setTranspose(uint8_t transpose)
|
|
|
|
{
|
|
|
|
transpose = constrain(transpose, 0, 48);
|
|
|
|
|
|
|
|
data[DEXED_VOICE_OFFSET + DEXED_TRANSPOSE] = transpose;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t Dexed::getTranspose(void)
|
|
|
|
{
|
|
|
|
return (data[DEXED_VOICE_OFFSET + DEXED_TRANSPOSE]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::setName(char* name)
|
|
|
|
{
|
|
|
|
strncpy(name, (char*)&data[DEXED_VOICE_OFFSET + DEXED_NAME], 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dexed::getName(char* buffer)
|
|
|
|
{
|
|
|
|
strncpy((char*)&data[DEXED_VOICE_OFFSET + DEXED_NAME], buffer, 10);
|
|
|
|
buffer[10] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioSynthDexed::update(void)
|
|
|
|
{
|
|
|
|
if (in_update == true)
|
|
|
|
{
|
|
|
|
xrun++;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
in_update = true;
|
|
|
|
|
|
|
|
elapsedMicros render_time;
|
|
|
|
audio_block_t *lblock;
|
|
|
|
|
|
|
|
lblock = allocate();
|
|
|
|
|
|
|
|
if (!lblock)
|
|
|
|
{
|
|
|
|
in_update = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
getSamples(AUDIO_BLOCK_SAMPLES, lblock->data);
|
|
|
|
|
|
|
|
if (render_time > audio_block_time_us) // everything greater audio_block_time_us (2.9ms for buffer size of 128) is a buffer underrun!
|
|
|
|
xrun++;
|
|
|
|
|
|
|
|
if (render_time > render_time_max)
|
|
|
|
render_time_max = render_time;
|
|
|
|
|
|
|
|
transmit(lblock, 0);
|
|
|
|
release(lblock);
|
|
|
|
|
|
|
|
in_update = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
// https://www.musicdsp.org/en/latest/Effects/169-compressor.html#
|
|
|
|
void compress
|
|
|
|
(
|
|
|
|
float* wav_in, // signal
|
|
|
|
int n, // N samples
|
|
|
|
double threshold, // threshold (percents)
|
|
|
|
double slope, // slope angle (percents)
|
|
|
|
int sr, // sample rate (smp/sec)
|
|
|
|
double tla, // lookahead (ms)
|
|
|
|
double twnd, // window time (ms)
|
|
|
|
double tatt, // attack time (ms)
|
|
|
|
double trel // release time (ms)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
typedef float stereodata[2];
|
|
|
|
stereodata* wav = (stereodata*) wav_in; // our stereo signal
|
|
|
|
threshold *= 0.01; // threshold to unity (0...1)
|
|
|
|
slope *= 0.01; // slope to unity
|
|
|
|
tla *= 1e-3; // lookahead time to seconds
|
|
|
|
twnd *= 1e-3; // window time to seconds
|
|
|
|
tatt *= 1e-3; // attack time to seconds
|
|
|
|
trel *= 1e-3; // release time to seconds
|
|
|
|
|
|
|
|
// attack and release "per sample decay"
|
|
|
|
double att = (tatt == 0.0) ? (0.0) : exp (-1.0 / (sr * tatt));
|
|
|
|
double rel = (trel == 0.0) ? (0.0) : exp (-1.0 / (sr * trel));
|
|
|
|
|
|
|
|
// envelope
|
|
|
|
double env = 0.0;
|
|
|
|
|
|
|
|
// sample offset to lookahead wnd start
|
|
|
|
int lhsmp = (int) (sr * tla);
|
|
|
|
|
|
|
|
// samples count in lookahead window
|
|
|
|
int nrms = (int) (sr * twnd);
|
|
|
|
|
|
|
|
// for each sample...
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
|
|
{
|
|
|
|
// now compute RMS
|
|
|
|
double summ = 0;
|
|
|
|
|
|
|
|
// for each sample in window
|
|
|
|
for (int j = 0; j < nrms; ++j)
|
|
|
|
{
|
|
|
|
int lki = i + j + lhsmp;
|
|
|
|
double smp;
|
|
|
|
|
|
|
|
// if we in bounds of signal?
|
|
|
|
// if so, convert to mono
|
|
|
|
if (lki < n)
|
|
|
|
smp = 0.5 * wav[lki][0] + 0.5 * wav[lki][1];
|
|
|
|
else
|
|
|
|
smp = 0.0; // if we out of bounds we just get zero in smp
|
|
|
|
|
|
|
|
summ += smp * smp; // square em..
|
|
|
|
}
|
|
|
|
|
|
|
|
double rms = sqrt (summ / nrms); // root-mean-square
|
|
|
|
|
|
|
|
// dynamic selection: attack or release?
|
|
|
|
double theta = rms > env ? att : rel;
|
|
|
|
|
|
|
|
// smoothing with capacitor, envelope extraction...
|
|
|
|
// here be aware of pIV denormal numbers glitch
|
|
|
|
env = (1.0 - theta) * rms + theta * env;
|
|
|
|
|
|
|
|
// the very easy hard knee 1:N compressor
|
|
|
|
double gain = 1.0;
|
|
|
|
if (env > threshold)
|
|
|
|
gain = gain - (env - threshold) * slope;
|
|
|
|
|
|
|
|
// result - two hard kneed compressed channels...
|
|
|
|
float leftchannel = wav[i][0] * gain;
|
|
|
|
float rightchannel = wav[i][1] * gain;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
Copyright 2016-2017 Pascal Gauthier.
|
|
|
|
Copyright 2012 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const int FEEDBACK_BITDEPTH = 8;
|
|
|
|
|
|
|
|
int32_t midinote_to_logfreq(int midinote) {
|
|
|
|
//const int32_t base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12)
|
|
|
|
const int32_t base = 50857777; // (1 << 24) * (LOG_FUNC(440) / LOG_FUNC(2) - 69/12)
|
|
|
|
const int32_t step = (1 << 24) / 12;
|
|
|
|
return base + step * midinote;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t logfreq_round2semi(int freq) {
|
|
|
|
const int base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12)
|
|
|
|
const int step = (1 << 24) / 12;
|
|
|
|
const int rem = (freq - base) % step;
|
|
|
|
return freq - rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int32_t coarsemul[] = {
|
|
|
|
-16777216, 0, 16777216, 26591258, 33554432, 38955489, 43368474, 47099600,
|
|
|
|
50331648, 53182516, 55732705, 58039632, 60145690, 62083076, 63876816,
|
|
|
|
65546747, 67108864, 68576247, 69959732, 71268397, 72509921, 73690858,
|
|
|
|
74816848, 75892776, 76922906, 77910978, 78860292, 79773775, 80654032,
|
|
|
|
81503396, 82323963, 83117622
|
|
|
|
};
|
|
|
|
|
|
|
|
int32_t osc_freq(int midinote, int mode, int coarse, int fine, int detune) {
|
|
|
|
// TODO: pitch randomization
|
|
|
|
int32_t logfreq;
|
|
|
|
if (mode == 0) {
|
|
|
|
logfreq = midinote_to_logfreq(midinote);
|
|
|
|
// could use more precision, closer enough for now. those numbers comes from my DX7
|
|
|
|
//FRAC_NUM detuneRatio = 0.0209 * exp(-0.396 * (((float)logfreq) / (1 << 24))) / 7;
|
|
|
|
FRAC_NUM detuneRatio = 0.0209 * EXP_FUNC(-0.396 * (((float)logfreq) / (1 << 24))) / 7;
|
|
|
|
logfreq += detuneRatio * logfreq * (detune - 7);
|
|
|
|
|
|
|
|
logfreq += coarsemul[coarse & 31];
|
|
|
|
if (fine) {
|
|
|
|
// (1 << 24) / log(2)
|
|
|
|
//logfreq += (int32_t)floor(24204406.323123 * log(1 + 0.01 * fine) + 0.5);
|
|
|
|
logfreq += (int32_t)floor(24204406.323123 * LOG_FUNC(1 + 0.01 * fine) + 0.5);
|
|
|
|
}
|
|
|
|
|
|
|
|
// // 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;
|
|
|
|
logfreq += detune > 7 ? 13457 * (detune - 7) : 0;
|
|
|
|
}
|
|
|
|
return logfreq;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t velocity_data[64] = {
|
|
|
|
0, 70, 86, 97, 106, 114, 121, 126, 132, 138, 142, 148, 152, 156, 160, 163,
|
|
|
|
166, 170, 173, 174, 178, 181, 184, 186, 189, 190, 194, 196, 198, 200, 202,
|
|
|
|
205, 206, 209, 211, 214, 216, 218, 220, 222, 224, 225, 227, 229, 230, 232,
|
|
|
|
233, 235, 237, 238, 240, 241, 242, 243, 244, 246, 246, 248, 249, 250, 251,
|
|
|
|
252, 253, 254
|
|
|
|
};
|
|
|
|
|
|
|
|
// See "velocity" section of notes. Returns velocity delta in microsteps.
|
|
|
|
int ScaleVelocity(int velocity, int sensitivity) {
|
|
|
|
int clamped_vel = max(0, min(127, velocity));
|
|
|
|
int vel_value = velocity_data[clamped_vel >> 1] - 239;
|
|
|
|
int scaled_vel = ((sensitivity * vel_value + 7) >> 3) << 4;
|
|
|
|
return scaled_vel;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ScaleRate(int midinote, int sensitivity) {
|
|
|
|
int x = min(31, max(0, midinote / 3 - 7));
|
|
|
|
int qratedelta = (sensitivity * x) >> 3;
|
|
|
|
#ifdef SUPER_PRECISE
|
|
|
|
int rem = x & 7;
|
|
|
|
if (sensitivity == 3 && rem == 3) {
|
|
|
|
qratedelta -= 1;
|
|
|
|
} else if (sensitivity == 7 && rem > 0 && rem < 4) {
|
|
|
|
qratedelta += 1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return qratedelta;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t exp_scale_data[] = {
|
|
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 14, 16, 19, 23, 27, 33, 39, 47, 56, 66,
|
|
|
|
80, 94, 110, 126, 142, 158, 174, 190, 206, 222, 238, 250
|
|
|
|
};
|
|
|
|
|
|
|
|
int ScaleCurve(int group, int depth, int curve) {
|
|
|
|
int scale;
|
|
|
|
if (curve == 0 || curve == 3) {
|
|
|
|
// linear
|
|
|
|
scale = (group * depth * 329) >> 12;
|
|
|
|
} else {
|
|
|
|
// exponential
|
|
|
|
int n_scale_data = sizeof(exp_scale_data);
|
|
|
|
int raw_exp = exp_scale_data[min(group, n_scale_data - 1)];
|
|
|
|
scale = (raw_exp * depth * 329) >> 15;
|
|
|
|
}
|
|
|
|
if (curve < 2) {
|
|
|
|
scale = -scale;
|
|
|
|
}
|
|
|
|
return scale;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ScaleLevel(int midinote, int break_pt, int left_depth, int right_depth,
|
|
|
|
int left_curve, int right_curve) {
|
|
|
|
int offset = midinote - break_pt - 17;
|
|
|
|
if (offset >= 0) {
|
|
|
|
return ScaleCurve((offset + 1) / 3, right_depth, right_curve);
|
|
|
|
} else {
|
|
|
|
return ScaleCurve(-(offset - 1) / 3, left_depth, left_curve);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const uint8_t pitchmodsenstab[] = {
|
|
|
|
0, 10, 20, 33, 55, 92, 153, 255
|
|
|
|
};
|
|
|
|
|
|
|
|
// 0, 66, 109, 255
|
|
|
|
static const uint32_t ampmodsenstab[] = {
|
|
|
|
0, 4342338, 7171437, 16777216
|
|
|
|
};
|
|
|
|
|
|
|
|
Dx7Note::Dx7Note() {
|
|
|
|
for (int op = 0; op < 6; op++) {
|
|
|
|
params_[op].phase = 0;
|
|
|
|
params_[op].gain_out = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//void Dx7Note::init(const uint8_t patch[156], int midinote, int velocity) {
|
|
|
|
void Dx7Note::init(const uint8_t patch[156], int midinote, int velocity, int srcnote, int porta, const Controllers * ctrls) {
|
|
|
|
int rates[4];
|
|
|
|
int levels[4];
|
|
|
|
for (int op = 0; op < 6; op++) {
|
|
|
|
int off = op * 21;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
rates[i] = patch[off + i];
|
|
|
|
levels[i] = patch[off + 4 + i];
|
|
|
|
}
|
|
|
|
|
|
|
|
int outlevel = patch[off + 16];
|
|
|
|
outlevel = Env::scaleoutlevel(outlevel);
|
|
|
|
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(127, outlevel);
|
|
|
|
outlevel = outlevel << 5;
|
|
|
|
outlevel += ScaleVelocity(velocity, patch[off + 15]);
|
|
|
|
outlevel = max(0, outlevel);
|
|
|
|
int rate_scaling = ScaleRate(midinote, patch[off + 13]);
|
|
|
|
env_[op].init(rates, levels, outlevel, rate_scaling);
|
|
|
|
|
|
|
|
int mode = patch[off + 17];
|
|
|
|
int coarse = patch[off + 18];
|
|
|
|
int fine = patch[off + 19];
|
|
|
|
int detune = patch[off + 20];
|
|
|
|
int32_t freq = osc_freq(midinote, mode, coarse, fine, detune);
|
|
|
|
opMode[op] = mode;
|
|
|
|
basepitch_[op] = freq;
|
|
|
|
porta_curpitch_[op] = freq;
|
|
|
|
ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3];
|
|
|
|
|
|
|
|
if (porta >= 0)
|
|
|
|
porta_curpitch_[op] = osc_freq(srcnote, mode, coarse, fine, detune);
|
|
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
rates[i] = patch[126 + i];
|
|
|
|
levels[i] = patch[130 + i];
|
|
|
|
}
|
|
|
|
pitchenv_.set(rates, levels);
|
|
|
|
algorithm_ = patch[134];
|
|
|
|
int feedback = patch[135];
|
|
|
|
fb_shift_ = feedback != 0 ? FEEDBACK_BITDEPTH - feedback : 16;
|
|
|
|
pitchmoddepth_ = (patch[139] * 165) >> 6;
|
|
|
|
pitchmodsens_ = pitchmodsenstab[patch[143] & 7];
|
|
|
|
ampmoddepth_ = (patch[140] * 165) >> 6;
|
|
|
|
porta_rateindex_ = (porta < 128) ? porta : 127;
|
|
|
|
porta_gliss_ = ctrls->values_[kControllerPortamentoGlissando];
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Controllers * ctrls) {
|
|
|
|
// ==== PITCH ====
|
|
|
|
uint32_t pmd = pitchmoddepth_ * lfo_delay; // Q32
|
|
|
|
int32_t senslfo = pitchmodsens_ * (lfo_val - (1 << 23));
|
|
|
|
int32_t pmod_1 = (((int64_t) pmd) * (int64_t) senslfo) >> 39;
|
|
|
|
pmod_1 = abs(pmod_1);
|
|
|
|
int32_t pmod_2 = (int32_t)(((int64_t)ctrls->pitch_mod * (int64_t)senslfo) >> 14);
|
|
|
|
pmod_2 = abs(pmod_2);
|
|
|
|
int32_t pitch_mod = max(pmod_1, pmod_2);
|
|
|
|
pitch_mod = pitchenv_.getsample() + (pitch_mod * (senslfo < 0 ? -1 : 1));
|
|
|
|
|
|
|
|
// ---- PITCH BEND ----
|
|
|
|
int pitchbend = ctrls->values_[kControllerPitch];
|
|
|
|
int32_t pb = (pitchbend - 0x2000);
|
|
|
|
if (pb != 0) {
|
|
|
|
if (ctrls->values_[kControllerPitchStep] == 0) {
|
|
|
|
pb = ((float) (pb << 11)) * ((float) ctrls->values_[kControllerPitchRange]) / 12.0;
|
|
|
|
} else {
|
|
|
|
int stp = 12 / ctrls->values_[kControllerPitchStep];
|
|
|
|
pb = pb * stp / 8191;
|
|
|
|
pb = (pb * (8191 / stp)) << 11;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int32_t pitch_base = pb + ctrls->masterTune;
|
|
|
|
pitch_mod += pitch_base;
|
|
|
|
|
|
|
|
// ==== AMP MOD ====
|
|
|
|
lfo_val = (1 << 24) - lfo_val;
|
|
|
|
uint32_t amod_1 = (uint32_t)(((int64_t) ampmoddepth_ * (int64_t) lfo_delay) >> 8); // Q24 :D
|
|
|
|
amod_1 = (uint32_t)(((int64_t) amod_1 * (int64_t) lfo_val) >> 24);
|
|
|
|
uint32_t amod_2 = (uint32_t)(((int64_t) ctrls->amp_mod * (int64_t) lfo_val) >> 7); // Q?? :|
|
|
|
|
uint32_t amd_mod = max(amod_1, amod_2);
|
|
|
|
|
|
|
|
// ==== EG AMP MOD ====
|
|
|
|
uint32_t amod_3 = (ctrls->eg_mod + 1) << 17;
|
|
|
|
amd_mod = max((1 << 24) - amod_3, amd_mod);
|
|
|
|
|
|
|
|
// ==== OP RENDER ====
|
|
|
|
for (int op = 0; op < 6; op++) {
|
|
|
|
// if ( ctrls->opSwitch[op] == '0' ) {
|
|
|
|
if (!(ctrls->opSwitch & (1 << op))) {
|
|
|
|
env_[op].getsample(); // advance the envelop even if it is not playing
|
|
|
|
params_[op].level_in = 0;
|
|
|
|
} else {
|
|
|
|
//int32_t gain = pow(2, 10 + level * (1.0 / (1 << 24)));
|
|
|
|
|
|
|
|
int32_t basepitch = basepitch_[op];
|
|
|
|
|
|
|
|
if ( opMode[op] )
|
|
|
|
params_[op].freq = Freqlut::lookup(basepitch + pitch_base);
|
|
|
|
else {
|
|
|
|
if ( porta_rateindex_ >= 0 ) {
|
|
|
|
basepitch = porta_curpitch_[op];
|
|
|
|
if ( porta_gliss_ )
|
|
|
|
basepitch = logfreq_round2semi(basepitch);
|
|
|
|
}
|
|
|
|
params_[op].freq = Freqlut::lookup(basepitch + pitch_mod);
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t level = env_[op].getsample();
|
|
|
|
if (ampmodsens_[op] != 0) {
|
|
|
|
uint32_t sensamp = (uint32_t)(((uint64_t) amd_mod) * ((uint64_t) ampmodsens_[op]) >> 24);
|
|
|
|
|
|
|
|
// TODO: mehhh.. this needs some real tuning.
|
|
|
|
//uint32_t pt = exp(((float)sensamp) / 262144 * 0.07 + 12.2);
|
|
|
|
uint32_t pt = EXP_FUNC(((float)sensamp) / 262144 * 0.07 + 12.2);
|
|
|
|
uint32_t ldiff = (uint32_t)(((uint64_t)level) * (((uint64_t)pt << 4)) >> 28);
|
|
|
|
level -= ldiff;
|
|
|
|
}
|
|
|
|
params_[op].level_in = level;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ==== PORTAMENTO ====
|
|
|
|
int porta = porta_rateindex_;
|
|
|
|
if ( porta >= 0 ) {
|
|
|
|
int32_t rate = Porta::rates[porta];
|
|
|
|
for (int op = 0; op < 6; op++) {
|
|
|
|
int32_t cur = porta_curpitch_[op];
|
|
|
|
int32_t dst = basepitch_[op];
|
|
|
|
|
|
|
|
bool going_up = cur < dst;
|
|
|
|
int32_t newpitch = cur + (going_up ? +rate : -rate);
|
|
|
|
|
|
|
|
if ( going_up ? (cur > dst) : (cur < dst) )
|
|
|
|
newpitch = dst;
|
|
|
|
|
|
|
|
porta_curpitch_[op] = newpitch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ctrls->core->render(buf, params_, algorithm_, fb_buf_, fb_shift_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dx7Note::keyup() {
|
|
|
|
for (int op = 0; op < 6; op++) {
|
|
|
|
env_[op].keydown(false);
|
|
|
|
}
|
|
|
|
pitchenv_.keydown(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dx7Note::update(const uint8_t patch[156], int midinote, int velocity, int porta, const Controllers * ctrls) {
|
|
|
|
int rates[4];
|
|
|
|
int levels[4];
|
|
|
|
for (int op = 0; op < 6; op++) {
|
|
|
|
int off = op * 21;
|
|
|
|
int mode = patch[off + 17];
|
|
|
|
int coarse = patch[off + 18];
|
|
|
|
int fine = patch[off + 19];
|
|
|
|
int detune = patch[off + 20];
|
|
|
|
int32_t freq = osc_freq(midinote, mode, coarse, fine, detune);
|
|
|
|
basepitch_[op] = freq;
|
|
|
|
porta_curpitch_[op] = freq;
|
|
|
|
ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3];
|
|
|
|
opMode[op] = mode;
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
rates[i] = patch[off + i];
|
|
|
|
levels[i] = patch[off + 4 + i];
|
|
|
|
}
|
|
|
|
int outlevel = patch[off + 16];
|
|
|
|
outlevel = Env::scaleoutlevel(outlevel);
|
|
|
|
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(127, outlevel);
|
|
|
|
outlevel = outlevel << 5;
|
|
|
|
outlevel += ScaleVelocity(velocity, patch[off + 15]);
|
|
|
|
outlevel = max(0, outlevel);
|
|
|
|
int rate_scaling = ScaleRate(midinote, patch[off + 13]);
|
|
|
|
env_[op].update(rates, levels, outlevel, rate_scaling);
|
|
|
|
}
|
|
|
|
algorithm_ = patch[134];
|
|
|
|
int feedback = patch[135];
|
|
|
|
fb_shift_ = feedback != 0 ? FEEDBACK_BITDEPTH - feedback : 16;
|
|
|
|
pitchmoddepth_ = (patch[139] * 165) >> 6;
|
|
|
|
pitchmodsens_ = pitchmodsenstab[patch[143] & 7];
|
|
|
|
ampmoddepth_ = (patch[140] * 165) >> 6;
|
|
|
|
porta_rateindex_ = (porta < 128) ? porta : 127;
|
|
|
|
porta_gliss_ = ctrls->values_[kControllerPortamentoGlissando];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dx7Note::peekVoiceStatus(VoiceStatus & status) {
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
|
|
status.amp[i] = Exp2::lookup(params_[i].level_in - (14 * (1 << 24)));
|
|
|
|
env_[i].getPosition(&status.ampStep[i]);
|
|
|
|
}
|
|
|
|
pitchenv_.getPosition(&status.pitchStep);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Used in monophonic mode to transfer voice state from different notes
|
|
|
|
*/
|
|
|
|
void Dx7Note::transferState(Dx7Note & src) {
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
|
|
env_[i].transfer(src.env_[i]);
|
|
|
|
params_[i].gain_out = src.params_[i].gain_out;
|
|
|
|
params_[i].phase = src.params_[i].phase;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dx7Note::transferSignal(Dx7Note & src) {
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
|
|
params_[i].gain_out = src.params_[i].gain_out;
|
|
|
|
params_[i].phase = src.params_[i].phase;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dx7Note::transferPortamento(Dx7Note & src) {
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
|
|
porta_curpitch_[i] = src.porta_curpitch_[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dx7Note::oscSync() {
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
|
|
params_[i].gain_out = 0;
|
|
|
|
params_[i].phase = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
Copyright 2017 Pascal Gauthier.
|
|
|
|
Copyright 2012 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
//using namespace std;
|
|
|
|
|
|
|
|
uint32_t Env::sr_multiplier = (1 << 24);
|
|
|
|
|
|
|
|
const int levellut[] = {
|
|
|
|
0, 5, 9, 13, 17, 20, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 42, 43, 45, 46
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef ACCURATE_ENVELOPE
|
|
|
|
const int statics[] = {
|
|
|
|
1764000, 1764000, 1411200, 1411200, 1190700, 1014300, 992250,
|
|
|
|
882000, 705600, 705600, 584325, 507150, 502740, 441000, 418950,
|
|
|
|
352800, 308700, 286650, 253575, 220500, 220500, 176400, 145530,
|
|
|
|
145530, 125685, 110250, 110250, 88200, 88200, 74970, 61740,
|
|
|
|
61740, 55125, 48510, 44100, 37485, 31311, 30870, 27562, 27562,
|
|
|
|
22050, 18522, 17640, 15435, 14112, 13230, 11025, 9261, 9261, 7717,
|
|
|
|
6615, 6615, 5512, 5512, 4410, 3969, 3969, 3439, 2866, 2690, 2249,
|
|
|
|
1984, 1896, 1808, 1411, 1367, 1234, 1146, 926, 837, 837, 705,
|
|
|
|
573, 573, 529, 441, 441
|
|
|
|
// and so on, I stopped measuring after R=76 (needs to be double-checked anyway)
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void Env::init_sr(double sampleRate) {
|
|
|
|
sr_multiplier = (44100.0 / sampleRate) * (1 << 24);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Env::init(const int r[4], const int l[4], int ol, int rate_scaling) {
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
rates_[i] = r[i];
|
|
|
|
levels_[i] = l[i];
|
|
|
|
}
|
|
|
|
outlevel_ = ol;
|
|
|
|
rate_scaling_ = rate_scaling;
|
|
|
|
level_ = 0;
|
|
|
|
down_ = true;
|
|
|
|
advance(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t Env::getsample() {
|
|
|
|
#ifdef ACCURATE_ENVELOPE
|
|
|
|
if (staticcount_) {
|
|
|
|
staticcount_ -= _N_;
|
|
|
|
if (staticcount_ <= 0) {
|
|
|
|
staticcount_ = 0;
|
|
|
|
advance(ix_ + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (ix_ < 3 || ((ix_ < 4) && !down_)) {
|
|
|
|
if (staticcount_) {
|
|
|
|
;
|
|
|
|
}
|
|
|
|
else if (rising_) {
|
|
|
|
const int jumptarget = 1716;
|
|
|
|
if (level_ < (jumptarget << 16)) {
|
|
|
|
level_ = jumptarget << 16;
|
|
|
|
}
|
|
|
|
level_ += (((17 << 24) - level_) >> 24) * inc_;
|
|
|
|
// TODO: should probably be more accurate when inc is large
|
|
|
|
if (level_ >= targetlevel_) {
|
|
|
|
level_ = targetlevel_;
|
|
|
|
advance(ix_ + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else { // !rising
|
|
|
|
level_ -= inc_;
|
|
|
|
if (level_ <= targetlevel_) {
|
|
|
|
level_ = targetlevel_;
|
|
|
|
advance(ix_ + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO: this would be a good place to set level to 0 when under threshold
|
|
|
|
return level_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Env::keydown(bool d) {
|
|
|
|
if (down_ != d) {
|
|
|
|
down_ = d;
|
|
|
|
advance(d ? 0 : 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 = scaleoutlevel(newlevel) >> 1;
|
|
|
|
actuallevel = (actuallevel << 6) + outlevel_ - 4256;
|
|
|
|
actuallevel = actuallevel < 16 ? 16 : actuallevel;
|
|
|
|
// level here is same as Java impl
|
|
|
|
targetlevel_ = actuallevel << 16;
|
|
|
|
rising_ = (targetlevel_ > level_);
|
|
|
|
|
|
|
|
// rate
|
|
|
|
int qrate = (rates_[ix_] * 41) >> 6;
|
|
|
|
qrate += rate_scaling_;
|
|
|
|
qrate = min(qrate, 63);
|
|
|
|
|
|
|
|
#ifdef ACCURATE_ENVELOPE
|
|
|
|
if (targetlevel_ == level_ || (ix_ == 0 && newlevel == 0)) {
|
|
|
|
// approximate number of samples at 44.100 kHz to achieve the time
|
|
|
|
// empirically gathered using 2 TF1s, could probably use some double-checking
|
|
|
|
// and cleanup, but it's pretty close for now.
|
|
|
|
int staticrate = rates_[ix_];
|
|
|
|
staticrate += rate_scaling_; // needs to be checked, as well, but seems correct
|
|
|
|
staticrate = min(staticrate, 99);
|
|
|
|
staticcount_ = staticrate < 77 ? statics[staticrate] : 20 * (99 - staticrate);
|
|
|
|
if (staticrate < 77 && (ix_ == 0 && newlevel == 0)) {
|
|
|
|
staticcount_ /= 20; // attack is scaled faster
|
|
|
|
}
|
|
|
|
staticcount_ = (int)(((int64_t)staticcount_ * (int64_t)sr_multiplier) >> 24);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
staticcount_ = 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
inc_ = (4 + (qrate & 3)) << (2 + LG_N + (qrate >> 2));
|
|
|
|
// meh, this should be fixed elsewhere
|
|
|
|
inc_ = (int)(((int64_t)inc_ * (int64_t)sr_multiplier) >> 24);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Env::update(const int r[4], const int l[4], int ol, int rate_scaling) {
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
rates_[i] = r[i];
|
|
|
|
levels_[i] = l[i];
|
|
|
|
}
|
|
|
|
outlevel_ = ol;
|
|
|
|
rate_scaling_ = rate_scaling;
|
|
|
|
if ( down_ ) {
|
|
|
|
// for now we simply reset ourselves at level 3
|
|
|
|
int newlevel = levels_[2];
|
|
|
|
int actuallevel = scaleoutlevel(newlevel) >> 1;
|
|
|
|
actuallevel = (actuallevel << 6) - 4256;
|
|
|
|
actuallevel = actuallevel < 16 ? 16 : actuallevel;
|
|
|
|
targetlevel_ = actuallevel << 16;
|
|
|
|
advance(2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Env::getPosition(char *step) {
|
|
|
|
*step = ix_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Env::transfer(Env & src) {
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
rates_[i] = src.rates_[i];
|
|
|
|
levels_[i] = src.levels_[i];
|
|
|
|
}
|
|
|
|
outlevel_ = src.outlevel_;
|
|
|
|
rate_scaling_ = src.rate_scaling_;
|
|
|
|
level_ = src.level_;
|
|
|
|
targetlevel_ = src.targetlevel_;
|
|
|
|
rising_ = src.rising_;
|
|
|
|
ix_ = src.ix_;
|
|
|
|
down_ = src.down_;
|
|
|
|
#ifdef ACCURATE_ENVELOPE
|
|
|
|
staticcount_ = src.staticcount_;
|
|
|
|
#endif
|
|
|
|
inc_ = src.inc_;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
Copyright 2013 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
#define exp2(arg) pow(2.0, arg)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int32_t exp2tab[EXP2_N_SAMPLES << 1];
|
|
|
|
|
|
|
|
void Exp2::init() {
|
|
|
|
FRAC_NUM inc = exp2(1.0 / EXP2_N_SAMPLES);
|
|
|
|
FRAC_NUM y = 1 << 30;
|
|
|
|
for (int i = 0; i < EXP2_N_SAMPLES; i++) {
|
|
|
|
exp2tab[(i << 1) + 1] = (int32_t)floor(y + 0.5);
|
|
|
|
y *= inc;
|
|
|
|
}
|
|
|
|
for (int i = 0; i < EXP2_N_SAMPLES - 1; i++) {
|
|
|
|
exp2tab[i << 1] = exp2tab[(i << 1) + 3] - exp2tab[(i << 1) + 1];
|
|
|
|
}
|
|
|
|
exp2tab[(EXP2_N_SAMPLES << 1) - 2] = (1U << 31) - exp2tab[(EXP2_N_SAMPLES << 1) - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t tanhtab[TANH_N_SAMPLES << 1];
|
|
|
|
|
|
|
|
static FRAC_NUM dtanh(FRAC_NUM y) {
|
|
|
|
return 1 - y * y;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Tanh::init() {
|
|
|
|
FRAC_NUM step = 4.0 / TANH_N_SAMPLES;
|
|
|
|
FRAC_NUM y = 0;
|
|
|
|
for (int i = 0; i < TANH_N_SAMPLES; i++) {
|
|
|
|
tanhtab[(i << 1) + 1] = (1 << 24) * y + 0.5;
|
|
|
|
//printf("%d\n", tanhtab[(i << 1) + 1]);
|
|
|
|
// Use a basic 4th order Runge-Kutte to compute tanh from its
|
|
|
|
// differential equation.
|
|
|
|
FRAC_NUM k1 = dtanh(y);
|
|
|
|
FRAC_NUM k2 = dtanh(y + 0.5 * step * k1);
|
|
|
|
FRAC_NUM k3 = dtanh(y + 0.5 * step * k2);
|
|
|
|
FRAC_NUM k4 = dtanh(y + step * k3);
|
|
|
|
FRAC_NUM dy = (step / 6) * (k1 + k4 + 2 * (k2 + k3));
|
|
|
|
y += dy;
|
|
|
|
}
|
|
|
|
for (int i = 0; i < TANH_N_SAMPLES - 1; i++) {
|
|
|
|
tanhtab[i << 1] = tanhtab[(i << 1) + 3] - tanhtab[(i << 1) + 1];
|
|
|
|
}
|
|
|
|
int32_t lasty = (1 << 24) * y + 0.5;
|
|
|
|
tanhtab[(TANH_N_SAMPLES << 1) - 2] = lasty - tanhtab[(TANH_N_SAMPLES << 1) - 1];
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
Copyright 2012 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
//using namespace std;
|
|
|
|
|
|
|
|
const FmAlgorithm FmCore::algorithms[32] = {
|
|
|
|
{ { 0xc1, 0x11, 0x11, 0x14, 0x01, 0x14 } }, // 1
|
|
|
|
{ { 0x01, 0x11, 0x11, 0x14, 0xc1, 0x14 } }, // 2
|
|
|
|
{ { 0xc1, 0x11, 0x14, 0x01, 0x11, 0x14 } }, // 3
|
|
|
|
{ { 0xc1, 0x11, 0x94, 0x01, 0x11, 0x14 } }, // 4
|
|
|
|
{ { 0xc1, 0x14, 0x01, 0x14, 0x01, 0x14 } }, // 5
|
|
|
|
{ { 0xc1, 0x94, 0x01, 0x14, 0x01, 0x14 } }, // 6
|
|
|
|
{ { 0xc1, 0x11, 0x05, 0x14, 0x01, 0x14 } }, // 7
|
|
|
|
{ { 0x01, 0x11, 0xc5, 0x14, 0x01, 0x14 } }, // 8
|
|
|
|
{ { 0x01, 0x11, 0x05, 0x14, 0xc1, 0x14 } }, // 9
|
|
|
|
{ { 0x01, 0x05, 0x14, 0xc1, 0x11, 0x14 } }, // 10
|
|
|
|
{ { 0xc1, 0x05, 0x14, 0x01, 0x11, 0x14 } }, // 11
|
|
|
|
{ { 0x01, 0x05, 0x05, 0x14, 0xc1, 0x14 } }, // 12
|
|
|
|
{ { 0xc1, 0x05, 0x05, 0x14, 0x01, 0x14 } }, // 13
|
|
|
|
{ { 0xc1, 0x05, 0x11, 0x14, 0x01, 0x14 } }, // 14
|
|
|
|
{ { 0x01, 0x05, 0x11, 0x14, 0xc1, 0x14 } }, // 15
|
|
|
|
{ { 0xc1, 0x11, 0x02, 0x25, 0x05, 0x14 } }, // 16
|
|
|
|
{ { 0x01, 0x11, 0x02, 0x25, 0xc5, 0x14 } }, // 17
|
|
|
|
{ { 0x01, 0x11, 0x11, 0xc5, 0x05, 0x14 } }, // 18
|
|
|
|
{ { 0xc1, 0x14, 0x14, 0x01, 0x11, 0x14 } }, // 19
|
|
|
|
{ { 0x01, 0x05, 0x14, 0xc1, 0x14, 0x14 } }, // 20
|
|
|
|
{ { 0x01, 0x14, 0x14, 0xc1, 0x14, 0x14 } }, // 21
|
|
|
|
{ { 0xc1, 0x14, 0x14, 0x14, 0x01, 0x14 } }, // 22
|
|
|
|
{ { 0xc1, 0x14, 0x14, 0x01, 0x14, 0x04 } }, // 23
|
|
|
|
{ { 0xc1, 0x14, 0x14, 0x14, 0x04, 0x04 } }, // 24
|
|
|
|
{ { 0xc1, 0x14, 0x14, 0x04, 0x04, 0x04 } }, // 25
|
|
|
|
{ { 0xc1, 0x05, 0x14, 0x01, 0x14, 0x04 } }, // 26
|
|
|
|
{ { 0x01, 0x05, 0x14, 0xc1, 0x14, 0x04 } }, // 27
|
|
|
|
{ { 0x04, 0xc1, 0x11, 0x14, 0x01, 0x14 } }, // 28
|
|
|
|
{ { 0xc1, 0x14, 0x01, 0x14, 0x04, 0x04 } }, // 29
|
|
|
|
{ { 0x04, 0xc1, 0x11, 0x14, 0x04, 0x04 } }, // 30
|
|
|
|
{ { 0xc1, 0x14, 0x04, 0x04, 0x04, 0x04 } }, // 31
|
|
|
|
{ { 0xc4, 0x04, 0x04, 0x04, 0x04, 0x04 } }, // 32
|
|
|
|
};
|
|
|
|
|
|
|
|
int n_out(const FmAlgorithm & alg) {
|
|
|
|
int count = 0;
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
|
|
if ((alg.ops[i] & 7) == OUT_BUS_ADD) count++;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t FmCore::get_carrier_operators(uint8_t algorithm)
|
|
|
|
{
|
|
|
|
uint8_t op_out = 0;
|
|
|
|
FmAlgorithm alg = algorithms[algorithm];
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < 6; i++)
|
|
|
|
{
|
|
|
|
if ((alg.ops[i]&OUT_BUS_ADD) == OUT_BUS_ADD)
|
|
|
|
op_out |= 1 << i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return op_out & 0x3f;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FmCore::dump() {
|
|
|
|
#ifdef VERBOSE
|
|
|
|
for (int i = 0; i < 32; i++) {
|
|
|
|
cout << (i + 1) << ":";
|
|
|
|
const FmAlgorithm &alg = algorithms[i];
|
|
|
|
for (int j = 0; j < 6; j++) {
|
|
|
|
int flags = alg.ops[j];
|
|
|
|
cout << " ";
|
|
|
|
if (flags & FB_IN) cout << "[";
|
|
|
|
cout << (flags & IN_BUS_ONE ? "1" : flags & IN_BUS_TWO ? "2" : "0") << "->";
|
|
|
|
cout << (flags & OUT_BUS_ONE ? "1" : flags & OUT_BUS_TWO ? "2" : "0");
|
|
|
|
if (flags & OUT_BUS_ADD) cout << "+";
|
|
|
|
//cout << alg.ops[j].in << "->" << alg.ops[j].out;
|
|
|
|
if (flags & FB_OUT) cout << "]";
|
|
|
|
}
|
|
|
|
cout << " " << n_out(alg);
|
|
|
|
cout << endl;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void FmCore::render(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++) {
|
|
|
|
int flags = alg.ops[op];
|
|
|
|
bool add = (flags & OUT_BUS_ADD) != 0;
|
|
|
|
FmOpParams ¶m = params[op];
|
|
|
|
int inbus = (flags >> 4) & 3;
|
|
|
|
int outbus = flags & 3;
|
|
|
|
int32_t *outptr = (outbus == 0) ? output : buf_[outbus - 1].get();
|
|
|
|
int32_t gain1 = param.gain_out;
|
|
|
|
int32_t gain2 = Exp2::lookup(param.level_in - (14 * (1 << 24)));
|
|
|
|
param.gain_out = gain2;
|
|
|
|
|
|
|
|
if (gain1 >= kLevelThresh || gain2 >= kLevelThresh) {
|
|
|
|
if (!has_contents[outbus]) {
|
|
|
|
add = false;
|
|
|
|
}
|
|
|
|
if (inbus == 0 || !has_contents[inbus]) {
|
|
|
|
// todo: more than one op in a feedback loop
|
|
|
|
if ((flags & 0xc0) == 0xc0 && feedback_shift < 16) {
|
|
|
|
// cout << op << " fb " << inbus << outbus << add << endl;
|
|
|
|
FmOpKernel::compute_fb(outptr, param.phase, param.freq,
|
|
|
|
gain1, gain2,
|
|
|
|
fb_buf, feedback_shift, add);
|
|
|
|
} else {
|
|
|
|
// cout << op << " pure " << inbus << outbus << add << endl;
|
|
|
|
FmOpKernel::compute_pure(outptr, param.phase, param.freq,
|
|
|
|
gain1, gain2, add);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// cout << op << " normal " << inbus << outbus << " " << param.freq << add << endl;
|
|
|
|
FmOpKernel::compute(outptr, buf_[inbus - 1].get(),
|
|
|
|
param.phase, param.freq, gain1, gain2, add);
|
|
|
|
}
|
|
|
|
has_contents[outbus] = true;
|
|
|
|
} else if (!add) {
|
|
|
|
has_contents[outbus] = false;
|
|
|
|
}
|
|
|
|
param.phase += param.freq << LG_N;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
Copyright 2012 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_NEON
|
|
|
|
static bool hasNeon() {
|
|
|
|
return true;
|
|
|
|
return (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
extern "C"
|
|
|
|
void neon_fm_kernel(const int *in, const int *busin, int *out, int count,
|
|
|
|
int32_t phase0, int32_t freq, int32_t gain1, int32_t dgain);
|
|
|
|
|
|
|
|
#else
|
|
|
|
static bool hasNeon() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void FmOpKernel::compute(int32_t *output, const int32_t *input,
|
|
|
|
int32_t phase0, int32_t freq,
|
|
|
|
int32_t gain1, int32_t gain2, bool add) {
|
|
|
|
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
|
|
|
|
int32_t gain = gain1;
|
|
|
|
int32_t phase = phase0;
|
|
|
|
if (hasNeon()) {
|
|
|
|
#ifdef HAVE_NEON
|
|
|
|
neon_fm_kernel(input, add ? output : zeros, output, _N_,
|
|
|
|
phase0, freq, gain, dgain);
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
if (add) {
|
|
|
|
for (int i = 0; i < _N_; i++) {
|
|
|
|
gain += dgain;
|
|
|
|
int32_t y = Sin::lookup(phase + input[i]);
|
|
|
|
int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24;
|
|
|
|
output[i] += y1;
|
|
|
|
phase += freq;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i = 0; i < _N_; i++) {
|
|
|
|
gain += dgain;
|
|
|
|
int32_t y = Sin::lookup(phase + input[i]);
|
|
|
|
int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24;
|
|
|
|
output[i] = y1;
|
|
|
|
phase += freq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
|
|
|
|
int32_t gain1, int32_t gain2, bool add) {
|
|
|
|
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
|
|
|
|
int32_t gain = gain1;
|
|
|
|
int32_t phase = phase0;
|
|
|
|
if (hasNeon()) {
|
|
|
|
#ifdef HAVE_NEON
|
|
|
|
neon_fm_kernel(zeros, add ? output : zeros, output, _N_,
|
|
|
|
phase0, freq, gain, dgain);
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
if (add) {
|
|
|
|
for (int i = 0; i < _N_; i++) {
|
|
|
|
gain += dgain;
|
|
|
|
int32_t y = Sin::lookup(phase);
|
|
|
|
int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24;
|
|
|
|
output[i] += y1;
|
|
|
|
phase += freq;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i = 0; i < _N_; i++) {
|
|
|
|
gain += dgain;
|
|
|
|
int32_t y = Sin::lookup(phase);
|
|
|
|
int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24;
|
|
|
|
output[i] = y1;
|
|
|
|
phase += freq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define noDOUBLE_ACCURACY
|
|
|
|
#define HIGH_ACCURACY
|
|
|
|
|
|
|
|
void FmOpKernel::compute_fb(int32_t *output, int32_t phase0, int32_t freq,
|
|
|
|
int32_t gain1, int32_t gain2,
|
|
|
|
int32_t *fb_buf, int fb_shift, bool add) {
|
|
|
|
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
|
|
|
|
int32_t gain = gain1;
|
|
|
|
int32_t phase = phase0;
|
|
|
|
int32_t y0 = fb_buf[0];
|
|
|
|
int32_t y = fb_buf[1];
|
|
|
|
if (add) {
|
|
|
|
for (int i = 0; i < _N_; i++) {
|
|
|
|
gain += dgain;
|
|
|
|
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1);
|
|
|
|
y0 = y;
|
|
|
|
y = Sin::lookup(phase + scaled_fb);
|
|
|
|
y = ((int64_t)y * (int64_t)gain) >> 24;
|
|
|
|
output[i] += y;
|
|
|
|
phase += freq;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i = 0; i < _N_; i++) {
|
|
|
|
gain += dgain;
|
|
|
|
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1);
|
|
|
|
y0 = y;
|
|
|
|
y = Sin::lookup(phase + scaled_fb);
|
|
|
|
y = ((int64_t)y * (int64_t)gain) >> 24;
|
|
|
|
output[i] = y;
|
|
|
|
phase += freq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fb_buf[0] = y0;
|
|
|
|
fb_buf[1] = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// Experimental sine wave generators below
|
|
|
|
#if 0
|
|
|
|
// Results: accuracy 64.3 mean, 170 worst case
|
|
|
|
// high accuracy: 5.0 mean, 49 worst case
|
|
|
|
void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
|
|
|
|
int32_t gain1, int32_t gain2, bool add) {
|
|
|
|
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
|
|
|
|
int32_t gain = gain1;
|
|
|
|
int32_t phase = phase0;
|
|
|
|
#ifdef HIGH_ACCURACY
|
|
|
|
int32_t u = Sin::compute10(phase << 6);
|
|
|
|
u = ((int64_t)u * gain) >> 30;
|
|
|
|
int32_t v = Sin::compute10((phase << 6) + (1 << 28)); // quarter cycle
|
|
|
|
v = ((int64_t)v * gain) >> 30;
|
|
|
|
int32_t s = Sin::compute10(freq << 6);
|
|
|
|
int32_t c = Sin::compute10((freq << 6) + (1 << 28));
|
|
|
|
#else
|
|
|
|
int32_t u = Sin::compute(phase);
|
|
|
|
u = ((int64_t)u * gain) >> 24;
|
|
|
|
int32_t v = Sin::compute(phase + (1 << 22)); // quarter cycle
|
|
|
|
v = ((int64_t)v * gain) >> 24;
|
|
|
|
int32_t s = Sin::compute(freq) << 6;
|
|
|
|
int32_t c = Sin::compute(freq + (1 << 22)) << 6;
|
|
|
|
#endif
|
|
|
|
for (int i = 0; i < _N_; i++) {
|
|
|
|
output[i] = u;
|
|
|
|
int32_t t = ((int64_t)v * (int64_t)c - (int64_t)u * (int64_t)s) >> 30;
|
|
|
|
u = ((int64_t)u * (int64_t)c + (int64_t)v * (int64_t)s) >> 30;
|
|
|
|
v = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
// Results: accuracy 392.3 mean, 15190 worst case (near freq = 0.5)
|
|
|
|
// for freq < 0.25, 275.2 mean, 716 worst
|
|
|
|
// high accuracy: 57.4 mean, 7559 worst
|
|
|
|
// freq < 0.25: 17.9 mean, 78 worst
|
|
|
|
void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
|
|
|
|
int32_t gain1, int32_t gain2, bool add) {
|
|
|
|
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
|
|
|
|
int32_t gain = gain1;
|
|
|
|
int32_t phase = phase0;
|
|
|
|
#ifdef HIGH_ACCURACY
|
|
|
|
int32_t u = floor(gain * sin(phase * (M_PI / (1 << 23))) + 0.5);
|
|
|
|
int32_t v = floor(gain * cos((phase - freq * 0.5) * (M_PI / (1 << 23))) + 0.5);
|
|
|
|
int32_t a = floor((1 << 25) * sin(freq * (M_PI / (1 << 24))) + 0.5);
|
|
|
|
#else
|
|
|
|
int32_t u = Sin::compute(phase);
|
|
|
|
u = ((int64_t)u * gain) >> 24;
|
|
|
|
int32_t v = Sin::compute(phase + (1 << 22) - (freq >> 1));
|
|
|
|
v = ((int64_t)v * gain) >> 24;
|
|
|
|
int32_t a = Sin::compute(freq >> 1) << 1;
|
|
|
|
#endif
|
|
|
|
for (int i = 0; i < _N_; i++) {
|
|
|
|
output[i] = u;
|
|
|
|
v -= ((int64_t)a * (int64_t)u) >> 24;
|
|
|
|
u += ((int64_t)a * (int64_t)v) >> 24;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
// Results: accuracy 370.0 mean, 15480 worst case (near freq = 0.5)
|
|
|
|
// with FRAC_NUM accuracy initialization: mean 1.55, worst 58 (near freq = 0)
|
|
|
|
// with high accuracy: mean 4.2, worst 292 (near freq = 0.5)
|
|
|
|
void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
|
|
|
|
int32_t gain1, int32_t gain2, bool add) {
|
|
|
|
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
|
|
|
|
int32_t gain = gain1;
|
|
|
|
int32_t phase = phase0;
|
|
|
|
#ifdef DOUBLE_ACCURACY
|
|
|
|
int32_t u = floor((1 << 30) * sin(phase * (M_PI / (1 << 23))) + 0.5);
|
|
|
|
FRAC_NUM a_d = sin(freq * (M_PI / (1 << 24)));
|
|
|
|
int32_t v = floor((1LL << 31) * a_d * cos((phase - freq * 0.5) *
|
|
|
|
(M_PI / (1 << 23))) + 0.5);
|
|
|
|
int32_t aa = floor((1LL << 31) * a_d * a_d + 0.5);
|
|
|
|
#else
|
|
|
|
#ifdef HIGH_ACCURACY
|
|
|
|
int32_t u = Sin::compute10(phase << 6);
|
|
|
|
int32_t v = Sin::compute10((phase << 6) + (1 << 28) - (freq << 5));
|
|
|
|
int32_t a = Sin::compute10(freq << 5);
|
|
|
|
v = ((int64_t)v * (int64_t)a) >> 29;
|
|
|
|
int32_t aa = ((int64_t)a * (int64_t)a) >> 29;
|
|
|
|
#else
|
|
|
|
int32_t u = Sin::compute(phase) << 6;
|
|
|
|
int32_t v = Sin::compute(phase + (1 << 22) - (freq >> 1));
|
|
|
|
int32_t a = Sin::compute(freq >> 1);
|
|
|
|
v = ((int64_t)v * (int64_t)a) >> 17;
|
|
|
|
int32_t aa = ((int64_t)a * (int64_t)a) >> 17;
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (aa < 0) aa = (1 << 31) - 1;
|
|
|
|
for (int i = 0; i < _N_; i++) {
|
|
|
|
gain += dgain;
|
|
|
|
output[i] = ((int64_t)u * (int64_t)gain) >> 30;
|
|
|
|
v -= ((int64_t)aa * (int64_t)u) >> 29;
|
|
|
|
u += v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
// Results:: accuracy 112.3 mean, 4262 worst (near freq = 0.5)
|
|
|
|
// high accuracy 2.9 mean, 143 worst
|
|
|
|
void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq,
|
|
|
|
int32_t gain1, int32_t gain2, bool add) {
|
|
|
|
int32_t dgain = (gain2 - gain1 + (_N_ >> 1)) >> LG_N;
|
|
|
|
int32_t gain = gain1;
|
|
|
|
int32_t phase = phase0;
|
|
|
|
#ifdef HIGH_ACCURACY
|
|
|
|
int32_t u = Sin::compute10(phase << 6);
|
|
|
|
int32_t lastu = Sin::compute10((phase - freq) << 6);
|
|
|
|
int32_t a = Sin::compute10((freq << 6) + (1 << 28)) << 1;
|
|
|
|
#else
|
|
|
|
int32_t u = Sin::compute(phase) << 6;
|
|
|
|
int32_t lastu = Sin::compute(phase - freq) << 6;
|
|
|
|
int32_t a = Sin::compute(freq + (1 << 22)) << 7;
|
|
|
|
#endif
|
|
|
|
if (a < 0 && freq < 256) a = (1 << 31) - 1;
|
|
|
|
if (a > 0 && freq > 0x7fff00) a = -(1 << 31);
|
|
|
|
for (int i = 0; i < _N_; i++) {
|
|
|
|
gain += dgain;
|
|
|
|
output[i] = ((int64_t)u * (int64_t)gain) >> 30;
|
|
|
|
//output[i] = u;
|
|
|
|
int32_t newu = (((int64_t)u * (int64_t)a) >> 30) - lastu;
|
|
|
|
lastu = u;
|
|
|
|
u = newu;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
|
|
Copyright 2012 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Resolve frequency signal (1.0 in Q24 format = 1 octave) to phase delta.
|
|
|
|
|
|
|
|
// The LUT is just a global, and we'll need the init function to be called before
|
|
|
|
// use.
|
|
|
|
|
|
|
|
#define LG_N_SAMPLES 10
|
|
|
|
#define N_SAMPLES (1 << LG_N_SAMPLES)
|
|
|
|
#define SAMPLE_SHIFT (24 - LG_N_SAMPLES)
|
|
|
|
|
|
|
|
#define MAX_LOGFREQ_INT 20
|
|
|
|
|
|
|
|
int32_t lut[N_SAMPLES + 1];
|
|
|
|
|
|
|
|
void Freqlut::init(FRAC_NUM sample_rate) {
|
|
|
|
FRAC_NUM y = (1LL << (24 + MAX_LOGFREQ_INT)) / sample_rate;
|
|
|
|
FRAC_NUM inc = pow(2, 1.0 / N_SAMPLES);
|
|
|
|
for (int i = 0; i < N_SAMPLES + 1; i++) {
|
|
|
|
lut[i] = (int32_t)floor(y + 0.5);
|
|
|
|
y *= inc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: if logfreq is more than 20.0, the results will be inaccurate. However,
|
|
|
|
// that will be many times the Nyquist rate.
|
|
|
|
int32_t Freqlut::lookup(int32_t logfreq) {
|
|
|
|
int ix = (logfreq & 0xffffff) >> SAMPLE_SHIFT;
|
|
|
|
|
|
|
|
int32_t y0 = lut[ix];
|
|
|
|
int32_t y1 = lut[ix + 1];
|
|
|
|
int lowbits = logfreq & ((1 << SAMPLE_SHIFT) - 1);
|
|
|
|
int32_t y = y0 + ((((int64_t)(y1 - y0) * (int64_t)lowbits)) >> SAMPLE_SHIFT);
|
|
|
|
int hibits = logfreq >> 24;
|
|
|
|
return y >> (MAX_LOGFREQ_INT - hibits);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
Copyright 2013 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Low frequency oscillator, compatible with DX7
|
|
|
|
|
|
|
|
uint32_t Lfo::unit_;
|
|
|
|
|
|
|
|
void Lfo::init(FRAC_NUM sample_rate) {
|
|
|
|
// constant is 1 << 32 / 15.5s / 11
|
|
|
|
Lfo::unit_ = (int32_t)(_N_ * 25190424 / sample_rate + 0.5);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Lfo::reset(const uint8_t params[6]) {
|
|
|
|
int rate = params[0]; // 0..99
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t Lfo::getsample() {
|
|
|
|
phase_ += delta_;
|
|
|
|
int32_t x;
|
|
|
|
switch (waveform_) {
|
|
|
|
case 0: // triangle
|
|
|
|
x = phase_ >> 7;
|
|
|
|
x ^= -(phase_ >> 31);
|
|
|
|
x &= (1 << 24) - 1;
|
|
|
|
return x;
|
|
|
|
case 1: // sawtooth down
|
|
|
|
return (~phase_ ^ (1U << 31)) >> 8;
|
|
|
|
case 2: // sawtooth up
|
|
|
|
return (phase_ ^ (1U << 31)) >> 8;
|
|
|
|
case 3: // square
|
|
|
|
return ((~phase_) >> 7) & (1 << 24);
|
|
|
|
case 4: // sine
|
|
|
|
return (1 << 23) + (Sin::lookup(phase_ >> 8) >> 1);
|
|
|
|
case 5: // s&h
|
|
|
|
if (phase_ < delta_) {
|
|
|
|
randstate_ = (randstate_ * 179 + 17) & 0xff;
|
|
|
|
}
|
|
|
|
x = randstate_ ^ 0x80;
|
|
|
|
return (x + 1) << 16;
|
|
|
|
}
|
|
|
|
return 1 << 23;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t Lfo::getdelay() {
|
|
|
|
uint32_t delta = delaystate_ < (1U << 31) ? delayinc_ : delayinc2_;
|
|
|
|
uint64_t d = ((uint64_t)delaystate_) + delta;
|
|
|
|
if (d > ~0u) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
Copyright 2012 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int PitchEnv::unit_;
|
|
|
|
|
|
|
|
void PitchEnv::init(FRAC_NUM sample_rate) {
|
|
|
|
unit_ = _N_ * (1 << 24) / (21.3 * sample_rate) + 0.5;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint8_t pitchenv_rate[] = {
|
|
|
|
1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12,
|
|
|
|
12, 13, 13, 14, 14, 15, 16, 16, 17, 18, 18, 19, 20, 21, 22, 23, 24,
|
|
|
|
25, 26, 27, 28, 30, 31, 33, 34, 36, 37, 38, 39, 41, 42, 44, 46, 47,
|
|
|
|
49, 51, 53, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 79, 82,
|
|
|
|
85, 88, 91, 94, 98, 102, 106, 110, 115, 120, 125, 130, 135, 141, 147,
|
|
|
|
153, 159, 165, 171, 178, 185, 193, 202, 211, 232, 243, 254, 255
|
|
|
|
};
|
|
|
|
|
|
|
|
const int8_t pitchenv_tab[] = {
|
|
|
|
-128, -116, -104, -95, -85, -76, -68, -61, -56, -52, -49, -46, -43,
|
|
|
|
-41, -39, -37, -35, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24,
|
|
|
|
-23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10,
|
|
|
|
-9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
|
|
|
|
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
|
|
|
|
28, 29, 30, 31, 32, 33, 34, 35, 38, 40, 43, 46, 49, 53, 58, 65, 73,
|
|
|
|
82, 92, 103, 115, 127
|
|
|
|
};
|
|
|
|
|
|
|
|
void PitchEnv::set(const int r[4], const int l[4]) {
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
rates_[i] = r[i];
|
|
|
|
levels_[i] = l[i];
|
|
|
|
}
|
|
|
|
level_ = pitchenv_tab[l[3]] << 19;
|
|
|
|
down_ = true;
|
|
|
|
advance(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t PitchEnv::getsample() {
|
|
|
|
if (ix_ < 3 || ((ix_ < 4) && !down_)) {
|
|
|
|
if (rising_) {
|
|
|
|
level_ += inc_;
|
|
|
|
if (level_ >= targetlevel_) {
|
|
|
|
level_ = targetlevel_;
|
|
|
|
advance(ix_ + 1);
|
|
|
|
}
|
|
|
|
} else { // !rising
|
|
|
|
level_ -= inc_;
|
|
|
|
if (level_ <= targetlevel_) {
|
|
|
|
level_ = targetlevel_;
|
|
|
|
advance(ix_ + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return level_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PitchEnv::keydown(bool d) {
|
|
|
|
if (down_ != d) {
|
|
|
|
down_ = d;
|
|
|
|
advance(d ? 0 : 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PitchEnv::advance(int newix) {
|
|
|
|
ix_ = newix;
|
|
|
|
if (ix_ < 4) {
|
|
|
|
int newlevel = levels_[ix_];
|
|
|
|
targetlevel_ = pitchenv_tab[newlevel] << 19;
|
|
|
|
rising_ = (targetlevel_ > level_);
|
|
|
|
inc_ = pitchenv_rate[rates_[ix_]] * unit_;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PitchEnv::getPosition(char *step) {
|
|
|
|
*step = ix_;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
Copyright 2019 Jean Pierre Cimalando.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void Porta::init_sr(double sampleRate)
|
|
|
|
{
|
|
|
|
// compute portamento for CC 7-bit range
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < 128; ++i) {
|
|
|
|
// number of semitones travelled
|
|
|
|
double sps = 350.0 * pow(2.0, -0.062 * i); // per second
|
|
|
|
double spf = sps / sampleRate; // per frame
|
|
|
|
double spp = spf * _N_; // per period
|
|
|
|
const int step = (1 << 24) / 12;
|
|
|
|
rates[i] = (int32_t)(0.5f + step * spp); // to pitch units
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t Porta::rates[128];
|
|
|
|
/*
|
|
|
|
Copyright 2012 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define R (1 << 29)
|
|
|
|
|
|
|
|
#ifdef SIN_DELTA
|
|
|
|
int32_t sintab[SIN_N_SAMPLES << 1];
|
|
|
|
#else
|
|
|
|
int32_t sintab[SIN_N_SAMPLES + 1];
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void Sin::init() {
|
|
|
|
FRAC_NUM dphase = 2 * M_PI / SIN_N_SAMPLES;
|
|
|
|
//int32_t c = (int32_t)floor(cos(dphase) * (1 << 30) + 0.5);
|
|
|
|
int32_t c = (int32_t)floor(COS_FUNC(dphase) * (1 << 30) + 0.5);
|
|
|
|
//int32_t s = (int32_t)floor(sin(dphase) * (1 << 30) + 0.5);
|
|
|
|
int32_t s = (int32_t)floor(SIN_FUNC(dphase) * (1 << 30) + 0.5);
|
|
|
|
int32_t u = 1 << 30;
|
|
|
|
int32_t v = 0;
|
|
|
|
for (int i = 0; i < SIN_N_SAMPLES / 2; i++) {
|
|
|
|
#ifdef SIN_DELTA
|
|
|
|
sintab[(i << 1) + 1] = (v + 32) >> 6;
|
|
|
|
sintab[((i + SIN_N_SAMPLES / 2) << 1) + 1] = -((v + 32) >> 6);
|
|
|
|
#else
|
|
|
|
sintab[i] = (v + 32) >> 6;
|
|
|
|
sintab[i + SIN_N_SAMPLES / 2] = -((v + 32) >> 6);
|
|
|
|
#endif
|
|
|
|
int32_t t = ((int64_t)u * (int64_t)s + (int64_t)v * (int64_t)c + R) >> 30;
|
|
|
|
u = ((int64_t)u * (int64_t)c - (int64_t)v * (int64_t)s + R) >> 30;
|
|
|
|
v = t;
|
|
|
|
}
|
|
|
|
#ifdef SIN_DELTA
|
|
|
|
for (int i = 0; i < SIN_N_SAMPLES - 1; i++) {
|
|
|
|
sintab[i << 1] = sintab[(i << 1) + 3] - sintab[(i << 1) + 1];
|
|
|
|
}
|
|
|
|
sintab[(SIN_N_SAMPLES << 1) - 2] = -sintab[(SIN_N_SAMPLES << 1) - 1];
|
|
|
|
#else
|
|
|
|
sintab[SIN_N_SAMPLES] = 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef SIN_INLINE
|
|
|
|
int32_t Sin::lookup(int32_t phase) {
|
|
|
|
const int SHIFT = 24 - SIN_LG_N_SAMPLES;
|
|
|
|
int lowbits = phase & ((1 << SHIFT) - 1);
|
|
|
|
#ifdef SIN_DELTA
|
|
|
|
int phase_int = (phase >> (SHIFT - 1)) & ((SIN_N_SAMPLES - 1) << 1);
|
|
|
|
int dy = sintab[phase_int];
|
|
|
|
int y0 = sintab[phase_int + 1];
|
|
|
|
|
|
|
|
return y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT);
|
|
|
|
#else
|
|
|
|
int phase_int = (phase >> SHIFT) & (SIN_N_SAMPLES - 1);
|
|
|
|
int y0 = sintab[phase_int];
|
|
|
|
int y1 = sintab[phase_int + 1];
|
|
|
|
|
|
|
|
return y0 + (((int64_t)(y1 - y0) * (int64_t)lowbits) >> SHIFT);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
// The following is an implementation designed not to use any lookup tables,
|
|
|
|
// based on the following implementation by Basile Graf:
|
|
|
|
// http://www.rossbencina.com/static/code/sinusoids/even_polynomial_sin_approximation.txt
|
|
|
|
|
|
|
|
#define C0 (1 << 24)
|
|
|
|
#define C1 (331121857 >> 2)
|
|
|
|
#define C2 (1084885537 >> 4)
|
|
|
|
#define C3 (1310449902 >> 6)
|
|
|
|
|
|
|
|
int32_t Sin::compute(int32_t phase) {
|
|
|
|
int32_t x = (phase & ((1 << 23) - 1)) - (1 << 22);
|
|
|
|
int32_t x2 = ((int64_t)x * (int64_t)x) >> 22;
|
|
|
|
int32_t x4 = ((int64_t)x2 * (int64_t)x2) >> 24;
|
|
|
|
int32_t x6 = ((int64_t)x2 * (int64_t)x4) >> 24;
|
|
|
|
int32_t y = C0 -
|
|
|
|
(((int64_t)C1 * (int64_t)x2) >> 24) +
|
|
|
|
(((int64_t)C2 * (int64_t)x4) >> 24) -
|
|
|
|
(((int64_t)C3 * (int64_t)x6) >> 24);
|
|
|
|
y ^= -((phase >> 23) & 1);
|
|
|
|
return y;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if 1
|
|
|
|
// coefficients are Chebyshev polynomial, computed by compute_cos_poly.py
|
|
|
|
#define C8_0 16777216
|
|
|
|
#define C8_2 -331168742
|
|
|
|
#define C8_4 1089453524
|
|
|
|
#define C8_6 -1430910663
|
|
|
|
#define C8_8 950108533
|
|
|
|
|
|
|
|
int32_t Sin::compute(int32_t phase) {
|
|
|
|
int32_t x = (phase & ((1 << 23) - 1)) - (1 << 22);
|
|
|
|
int32_t x2 = ((int64_t)x * (int64_t)x) >> 16;
|
|
|
|
int32_t y = (((((((((((((int64_t)C8_8
|
|
|
|
* (int64_t)x2) >> 32) + C8_6)
|
|
|
|
* (int64_t)x2) >> 32) + C8_4)
|
|
|
|
* (int64_t)x2) >> 32) + C8_2)
|
|
|
|
* (int64_t)x2) >> 32) + C8_0);
|
|
|
|
y ^= -((phase >> 23) & 1);
|
|
|
|
return y;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define C10_0 (1 << 30)
|
|
|
|
#define C10_2 -1324675874 // scaled * 4
|
|
|
|
#define C10_4 1089501821
|
|
|
|
#define C10_6 -1433689867
|
|
|
|
#define C10_8 1009356886
|
|
|
|
#define C10_10 -421101352
|
|
|
|
int32_t Sin::compute10(int32_t phase) {
|
|
|
|
int32_t x = (phase & ((1 << 29) - 1)) - (1 << 28);
|
|
|
|
int32_t x2 = ((int64_t)x * (int64_t)x) >> 26;
|
|
|
|
int32_t y = ((((((((((((((((int64_t)C10_10
|
|
|
|
* (int64_t)x2) >> 34) + C10_8)
|
|
|
|
* (int64_t)x2) >> 34) + C10_6)
|
|
|
|
* (int64_t)x2) >> 34) + C10_4)
|
|
|
|
* (int64_t)x2) >> 32) + C10_2)
|
|
|
|
* (int64_t)x2) >> 30) + C10_0);
|
|
|
|
y ^= -((phase >> 29) & 1);
|
|
|
|
return y;
|
|
|
|
}
|