diff --git a/EngineMkI.cpp b/EngineMkI.cpp deleted file mode 100644 index 49c8f74..0000000 --- a/EngineMkI.cpp +++ /dev/null @@ -1,353 +0,0 @@ -/* - Copyright (C) 2015-2017 Pascal Gauthier. - - 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 - - The code is based on ppplay https://github.com/stohrendorf/ppplay and opl3 - math documentation : - https://github.com/gtaylormb/opl3_fpga/blob/master/docs/opl3math/opl3math.pdf - -*/ - -#include "EngineMkI.h" -#define _USE_MATH_DEFINES -#include -#include - -#include "sin.h" -#include "exp2.h" - -#ifdef DEBUG -#include "time.h" -//#define MKIDEBUG -#endif - -#ifdef _WIN32 -#if _MSC_VER < 1800 -FRAC_NUM log2(FRAC_NUM n) { - //return log(n) / log(2.0); - return LOG_FUNC(n) / LOG_FUNC(2.0); -} -FRAC_NUM round(FRAC_NUM n) { - return n < 0.0 ? ceil(n - 0.5) : floor(n + 0.5); -} -#endif -__declspec(align(16)) const int zeros[N] = {0}; -#else -const int32_t __attribute__ ((aligned(16))) zeros[_N_] = {0}; -#endif - -static const uint16_t NEGATIVE_BIT = 0x8000; -static const uint16_t ENV_BITDEPTH = 14; - -static const uint16_t SINLOG_BITDEPTH = 10; -static const uint16_t SINLOG_TABLESIZE = 1 << SINLOG_BITDEPTH; -static uint16_t sinLogTable[SINLOG_TABLESIZE]; - -static const uint16_t SINEXP_BITDEPTH = 10; -static const uint16_t SINEXP_TABLESIZE = 1 << SINEXP_BITDEPTH; -static uint16_t sinExpTable[SINEXP_TABLESIZE]; - -const uint16_t ENV_MAX = 1 << ENV_BITDEPTH; - -static inline uint16_t sinLog(uint16_t phi) { - const uint16_t SINLOG_TABLEFILTER = SINLOG_TABLESIZE - 1; - const uint16_t index = (phi & SINLOG_TABLEFILTER); - - switch ( ( phi & (SINLOG_TABLESIZE * 3) ) ) { - case 0: - return sinLogTable[index]; - case SINLOG_TABLESIZE: - return sinLogTable[index ^ SINLOG_TABLEFILTER]; - case SINLOG_TABLESIZE * 2 : - return sinLogTable[index] | NEGATIVE_BIT; - default: - return sinLogTable[index ^ SINLOG_TABLEFILTER] | NEGATIVE_BIT; - } -} - -EngineMkI::EngineMkI() { - float bitReso = SINLOG_TABLESIZE; - - for (int i = 0; i < SINLOG_TABLESIZE; i++) { - //float x1 = sin(((0.5+i)/bitReso) * M_PI/2.0); - float x1 = SIN_FUNC(((0.5 + i) / bitReso) * M_PI / 2.0); - sinLogTable[i] = round(-1024 * log2(x1)); - } - - bitReso = SINEXP_TABLESIZE; - for (int i = 0; i < SINEXP_TABLESIZE; i++) { - float x1 = (pow(2, float(i) / bitReso) - 1) * 4096; - sinExpTable[i] = round(x1); - } - -#ifdef MKIDEBUG - char buffer[4096]; - int pos = 0; - - for (int i = 0; i < SINLOG_TABLESIZE; i++) { - pos += sprintf(buffer + pos, "%d ", sinLogTable[i]); - if ( pos > 90 ) { - buffer[0] = 0; - pos = 0; - } - } - buffer[0] = 0; - pos = 0; - for (int i = 0; i < SINEXP_TABLESIZE; i++) { - pos += sprintf(buffer + pos, "%d ", sinExpTable[i]); - if ( pos > 90 ) { - buffer[0] = 0; - pos = 0; - } - } -#endif -} - -inline int32_t mkiSin(int32_t phase, uint16_t env) { - uint16_t expVal = sinLog(phase >> (22 - SINLOG_BITDEPTH)) + (env); - //int16_t expValShow = expVal; - - const bool isSigned = expVal & NEGATIVE_BIT; - expVal &= ~NEGATIVE_BIT; - - const uint16_t SINEXP_FILTER = 0x3FF; - uint16_t result = 4096 + sinExpTable[( expVal & SINEXP_FILTER ) ^ SINEXP_FILTER]; - - //uint16_t resultB4 = result; - result >>= ( expVal >> 10 ); // exp - -#ifdef MKIDEBUG - if ( ( time(NULL) % 5 ) == 0 ) { - if ( expValShow < 0 ) { - expValShow = (expValShow + 0x7FFF) * -1; - } - } -#endif - - if ( isSigned ) - return (-result - 1) << 13; - else - return result << 13; -} - -void EngineMkI::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; - const int32_t *adder = add ? output : zeros; - - for (int i = 0; i < _N_; i++) { - gain += dgain; - int32_t y = mkiSin((phase + input[i]), gain); - output[i] = y + adder[i]; - phase += freq; - } - -} - -void EngineMkI::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; - const int32_t *adder = add ? output : zeros; - - for (int i = 0; i < _N_; i++) { - gain += dgain; - int32_t y = mkiSin(phase , gain); - output[i] = y + adder[i]; - phase += freq; - } -} - -void EngineMkI::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; - const int32_t *adder = add ? output : zeros; - int32_t y0 = fb_buf[0]; - int32_t y = fb_buf[1]; - - for (int i = 0; i < _N_; i++) { - gain += dgain; - int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); - y0 = y; - y = mkiSin((phase + scaled_fb), gain); - output[i] = y + adder[i]; - phase += freq; - } - - fb_buf[0] = y0; - fb_buf[1] = y; -} - -// exclusively used for ALGO 6 with feedback -void EngineMkI::compute_fb2(int32_t *output, FmOpParams *parms, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift) { - int32_t dgain[2]; - int32_t gain[2]; - int32_t phase[2]; - int32_t y0 = fb_buf[0]; - int32_t y = fb_buf[1]; - - phase[0] = parms[0].phase; - phase[1] = parms[1].phase; - - parms[1].gain_out = (ENV_MAX - (parms[1].level_in >> (28 - ENV_BITDEPTH))); - - gain[0] = gain01; - gain[1] = parms[1].gain_out == 0 ? (ENV_MAX - 1) : parms[1].gain_out; - - dgain[0] = (gain02 - gain01 + (_N_ >> 1)) >> LG_N; - dgain[1] = (parms[1].gain_out - (parms[1].gain_out == 0 ? (ENV_MAX - 1) : parms[1].gain_out)); - - for (int i = 0; i < _N_; i++) { - int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); - - // op 0 - gain[0] += dgain[0]; - y0 = y; - y = mkiSin(phase[0] + scaled_fb, gain[0]); - phase[0] += parms[0].freq; - - // op 1 - gain[1] += dgain[1]; - y = mkiSin(phase[1] + y, gain[1]); - phase[1] += parms[1].freq; - - output[i] = y; - } - fb_buf[0] = y0; - fb_buf[1] = y; -} - -// exclusively used for ALGO 4 with feedback -void EngineMkI::compute_fb3(int32_t *output, FmOpParams *parms, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift) { - int32_t dgain[3]; - int32_t gain[3]; - int32_t phase[3]; - int32_t y0 = fb_buf[0]; - int32_t y = fb_buf[1]; - - phase[0] = parms[0].phase; - phase[1] = parms[1].phase; - phase[2] = parms[2].phase; - - parms[1].gain_out = (ENV_MAX - (parms[1].level_in >> (28 - ENV_BITDEPTH))); - parms[2].gain_out = (ENV_MAX - (parms[2].level_in >> (28 - ENV_BITDEPTH))); - - gain[0] = gain01; - gain[1] = parms[1].gain_out == 0 ? (ENV_MAX - 1) : parms[1].gain_out; - gain[2] = parms[2].gain_out == 0 ? (ENV_MAX - 1) : parms[2].gain_out; - - dgain[0] = (gain02 - gain01 + (_N_ >> 1)) >> LG_N; - dgain[1] = (parms[1].gain_out - (parms[1].gain_out == 0 ? (ENV_MAX - 1) : parms[1].gain_out)); - dgain[2] = (parms[2].gain_out - (parms[2].gain_out == 0 ? (ENV_MAX - 1) : parms[2].gain_out)); - - - for (int i = 0; i < _N_; i++) { - int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); - - // op 0 - gain[0] += dgain[0]; - y0 = y; - y = mkiSin(phase[0] + scaled_fb, gain[0]); - phase[0] += parms[0].freq; - - // op 1 - gain[1] += dgain[1]; - y = mkiSin(phase[1] + y, gain[1]); - phase[1] += parms[1].freq; - - // op 2 - gain[2] += dgain[2]; - y = mkiSin(phase[2] + y, gain[2]); - phase[2] += parms[2].freq; - - output[i] = y; - } - fb_buf[0] = y0; - fb_buf[1] = y; -} - -void EngineMkI::render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int32_t feedback_shift) { - const int kLevelThresh = ENV_MAX - 100; - FmAlgorithm alg = algorithms[algorithm]; - bool has_contents[3] = { true, false, false }; - bool fb_on = feedback_shift < 16; - - switch (algorithm) { - case 3 : case 5 : - if ( fb_on ) - alg.ops[0] = 0xc4; - } - - 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 == 0 ? (ENV_MAX - 1) : param.gain_out; - int32_t gain2 = ENV_MAX - (param.level_in >> (28 - ENV_BITDEPTH)); - param.gain_out = gain2; - - if (gain1 <= kLevelThresh || gain2 <= kLevelThresh) { - - if (!has_contents[outbus]) { - add = false; - } - - if (inbus == 0 || !has_contents[inbus]) { - // PG: this is my 'dirty' implementation of FB for 2 and 3 operators... - if ((flags & 0xc0) == 0xc0 && fb_on) { - switch ( algorithm ) { - // three operator feedback, process exception for ALGO 4 - case 3 : - compute_fb3(outptr, params, gain1, gain2, fb_buf, min((feedback_shift + 2), 16)); - params[1].phase += params[1].freq << LG_N; // hack, we already processed op-5 - op-4 - params[2].phase += params[2].freq << LG_N; // yuk yuk - op += 2; // ignore the 2 other operators - break; - // two operator feedback, process exception for ALGO 6 - case 5 : - compute_fb2(outptr, params, gain1, gain2, fb_buf, min((feedback_shift + 2), 16)); - params[1].phase += params[1].freq << LG_N; // yuk, hack, we already processed op-5 - op++; // ignore next operator; - break; - default: - // one operator feedback, normal proces - compute_fb(outptr, param.phase, param.freq, gain1, gain2, fb_buf, feedback_shift, add); - break; - } - } else { - compute_pure(outptr, param.phase, param.freq, gain1, gain2, add); - } - } else { - 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; - } -} diff --git a/EngineMkI.h b/EngineMkI.h deleted file mode 100644 index 9caa102..0000000 --- a/EngineMkI.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copyright 2014 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. -*/ - -#ifndef ENGINEMKI_H_INCLUDED -#define ENGINEMKI_H_INCLUDED - -#include -#include "synth.h" -#include "aligned_buf.h" -#include "fm_op_kernel.h" -#include "controllers.h" -#include "fm_core.h" - - -class EngineMkI : public FmCore { - public: - EngineMkI(); - - void render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int32_t feedback_shift); - - void compute(int32_t *output, const int32_t *input, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, - bool add); - - void compute_pure(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, - bool add); - - void compute_fb(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, - int32_t *fb_buf, int fb_gain, bool add); - - void compute_fb2(int32_t *output, FmOpParams *params, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift); - - void compute_fb3(int32_t *output, FmOpParams *params, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift); -}; - - -#endif // ENGINEMKI_H_INCLUDED diff --git a/EngineOpl.cpp b/EngineOpl.cpp deleted file mode 100644 index 861137e..0000000 --- a/EngineOpl.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/* - Copyright (C) 2014 Pascal Gauthier. - Copyright (C) 2012 Steffen Ohrendorf - - 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 - - Original Java Code: Copyright (C) 2008 Robson Cozendey - - Some code based on forum posts in: http://forums.submarine.org.uk/phpBB/viewforum.php?f=9, - Copyright (C) 2010-2013 by carbon14 and opl3 - -*/ - -#include "EngineOpl.h" - -#ifdef _WIN32 -__declspec(align(16)) const int zeros[N] = {0}; -#else -const int32_t __attribute__ ((aligned(16))) zeros[_N_] = {0}; -#endif - -uint16_t SignBit = 0x8000; - -uint16_t sinLogTable[256] = { - 2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137, 1091, 1050, 1013, 979, 949, 920, 894, 869, - 846, 825, 804, 785, 767, 749, 732, 717, 701, 687, 672, 659, 646, 633, 621, 609, - 598, 587, 576, 566, 556, 546, 536, 527, 518, 509, 501, 492, 484, 476, 468, 461, - 453, 446, 439, 432, 425, 418, 411, 405, 399, 392, 386, 380, 375, 369, 363, 358, - 352, 347, 341, 336, 331, 326, 321, 316, 311, 307, 302, 297, 293, 289, 284, 280, - 276, 271, 267, 263, 259, 255, 251, 248, 244, 240, 236, 233, 229, 226, 222, 219, - 215, 212, 209, 205, 202, 199, 196, 193, 190, 187, 184, 181, 178, 175, 172, 169, - 167, 164, 161, 159, 156, 153, 151, 148, 146, 143, 141, 138, 136, 134, 131, 129, - 127, 125, 122, 120, 118, 116, 114, 112, 110, 108, 106, 104, 102, 100, 98, 96, - 94, 92, 91, 89, 87, 85, 83, 82, 80, 78, 77, 75, 74, 72, 70, 69, - 67, 66, 64, 63, 62, 60, 59, 57, 56, 55, 53, 52, 51, 49, 48, 47, - 46, 45, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, - 29, 28, 27, 26, 25, 24, 23, 23, 22, 21, 20, 20, 19, 18, 17, 17, - 16, 15, 15, 14, 13, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7, - 7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, - 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -uint16_t sinExpTable[256] = { - 0, 3, 6, 8, 11, 14, 17, 20, 22, 25, 28, 31, 34, 37, 40, 42, - 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, - 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 123, 126, 130, 133, 136, 139, - 142, 145, 148, 152, 155, 158, 161, 164, 168, 171, 174, 177, 181, 184, 187, 190, - 194, 197, 200, 204, 207, 210, 214, 217, 220, 224, 227, 231, 234, 237, 241, 244, - 248, 251, 255, 258, 262, 265, 268, 272, 276, 279, 283, 286, 290, 293, 297, 300, - 304, 308, 311, 315, 318, 322, 326, 329, 333, 337, 340, 344, 348, 352, 355, 359, - 363, 367, 370, 374, 378, 382, 385, 389, 393, 397, 401, 405, 409, 412, 416, 420, - 424, 428, 432, 436, 440, 444, 448, 452, 456, 460, 464, 468, 472, 476, 480, 484, - 488, 492, 496, 501, 505, 509, 513, 517, 521, 526, 530, 534, 538, 542, 547, 551, - 555, 560, 564, 568, 572, 577, 581, 585, 590, 594, 599, 603, 607, 612, 616, 621, - 625, 630, 634, 639, 643, 648, 652, 657, 661, 666, 670, 675, 680, 684, 689, 693, - 698, 703, 708, 712, 717, 722, 726, 731, 736, 741, 745, 750, 755, 760, 765, 770, - 774, 779, 784, 789, 794, 799, 804, 809, 814, 819, 824, 829, 834, 839, 844, 849, - 854, 859, 864, 869, 874, 880, 885, 890, 895, 900, 906, 911, 916, 921, 927, 932, - 937, 942, 948, 953, 959, 964, 969, 975, 980, 986, 991, 996, 1002, 1007, 1013, 1018 -}; - -inline uint16_t sinLog(uint16_t phi) { - const uint8_t index = (phi & 0xff); - - switch ( ( phi & 0x0300 ) ) { - case 0x0000: - // rising quarter wave Shape A - return sinLogTable[index]; - case 0x0100: - // falling quarter wave Shape B - return sinLogTable[index ^ 0xFF]; - case 0x0200: - // rising quarter wave -ve Shape C - return sinLogTable[index] | SignBit; - default: - // falling quarter wave -ve Shape D - return sinLogTable[index ^ 0xFF] | SignBit; - } -} - -// 16 env units are ~3dB and halve the output -/** - @brief OPL Sine Wave calculation - @param[in] phase Wave phase (0..1023) - @param[in] env Envelope value (0..511) - @warning @a env will not be checked for correct values. -*/ -inline int16_t oplSin( uint16_t phase, uint16_t env ) { - uint16_t expVal = sinLog(phase) + (env << 3); - const bool isSigned = expVal & SignBit; - - expVal &= ~SignBit; - // expVal: 0..2137+511*8 = 0..6225 - // result: 0..1018+1024 - uint32_t result = 0x0400 + sinExpTable[( expVal & 0xff ) ^ 0xFF]; - result <<= 1; - result >>= ( expVal >> 8 ); // exp - - if ( isSigned ) { - // -1 for one's complement - return -result - 1; - } else { - return result; - } -} - -void EngineOpl::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; - const int32_t *adder = add ? output : zeros; - - for (int i = 0; i < _N_; i++) { - gain += dgain; - int32_t y = oplSin((phase + input[i]) >> 14, gain); - output[i] = (y << 14) + adder[i]; - phase += freq; - } - -} -void EngineOpl::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; - const int32_t *adder = add ? output : zeros; - - for (int i = 0; i < _N_; i++) { - gain += dgain; - int32_t y = oplSin(phase >> 14, gain); - output[i] = (y << 14) + adder[i]; - phase += freq; - } -} - -void EngineOpl::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; - const int32_t *adder = add ? output : zeros; - int32_t y0 = fb_buf[0]; - int32_t y = fb_buf[1]; - - for (int i = 0; i < _N_; i++) { - gain += dgain; - int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); - y0 = y; - y = oplSin((phase + scaled_fb) >> 14, gain) << 14; - output[i] = y + adder[i]; - phase += freq; - } - - fb_buf[0] = y0; - fb_buf[1] = y; -} - - -void EngineOpl::render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int32_t feedback_shift) { - const int kLevelThresh = 507; // really ???? - 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 == 0 ? 511 : param.gain_out; - int32_t gain2 = 512 - (param.level_in >> 19); - 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; - compute_fb(outptr, param.phase, param.freq, - gain1, gain2, - fb_buf, feedback_shift, add); - } else { - // cout << op << " pure " << inbus << outbus << add << endl; - compute_pure(outptr, param.phase, param.freq, - gain1, gain2, add); - } - } else { - // cout << op << " normal " << inbus << outbus << " " << param.freq << add << endl; - 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; - } -} diff --git a/EngineOpl.h b/EngineOpl.h deleted file mode 100644 index 0a04edc..0000000 --- a/EngineOpl.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - - Copyright (c) 2014 Pascal Gauthier. - - 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 - -*/ - -#ifndef ENGINEOPL_H_INCLUDED -#define ENGINEOPL_H_INCLUDED -#include "Arduino.h" -#include "synth.h" -#include "aligned_buf.h" -#include "fm_op_kernel.h" -#include "controllers.h" -#include "fm_core.h" - - -class EngineOpl : public FmCore { - public: - virtual void render(int32_t *output, FmOpParams *params, int algorithm, - int32_t *fb_buf, int32_t feedback_shift); - void compute(int32_t *output, const int32_t *input, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, bool add); - void compute_pure(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, bool add); - void compute_fb(int32_t *output, int32_t phase0, int32_t freq, - int32_t gain1, int32_t gain2, - int32_t *fb_buf, int fb_gain, bool add); -}; - - - -#endif // ENGINEOPL_H_INCLUDED diff --git a/MicroDexed.ino b/MicroDexed.ino index b5c2c0e..38069a8 100644 --- a/MicroDexed.ino +++ b/MicroDexed.ino @@ -30,7 +30,7 @@ #include #include #include "midi_devices.hpp" -#include "dexed.h" +#include "synth_dexed.h" #include "dexed_sd.h" #include "effect_modulated_delay.h" #include "effect_stereo_mono.h" @@ -40,9 +40,7 @@ #else #include "effect_freeverbf.h" #endif -#include "PluginFx.h" #include "UI.hpp" -#include "source_microdexed.h" // Audio engines AudioSourceMicroDexed* MicroDexed[NUM_DEXED]; @@ -175,7 +173,7 @@ AudioConnection * dynamicConnections[NUM_DEXED * 4]; #endif void create_audio_engine_chain(uint8_t instance_id) { - MicroDexed[instance_id] = new AudioSourceMicroDexed(SAMPLE_RATE, instance_id); + MicroDexed[instance_id] = new AudioSourceMicroDexed(SAMPLE_RATE); mono2stereo[instance_id] = new AudioEffectMonoStereo(); #if defined(USE_FX) chorus_modulator[instance_id] = new AudioSynthWaveform(); diff --git a/PluginFx.cpp b/PluginFx.cpp deleted file mode 100644 index b3887e4..0000000 --- a/PluginFx.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/** - - 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 - -*/ - -#define _USE_MATH_DEFINES -#include "config.h" -#include -#include "PluginFx.h" -#include "synth.h" - -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); -} diff --git a/PluginFx.h b/PluginFx.h deleted file mode 100644 index 94016a1..0000000 --- a/PluginFx.h +++ /dev/null @@ -1,73 +0,0 @@ -/** - - Copyright (c) 2013 Pascal Gauthier. - - 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 - -*/ - -#ifndef PLUGINFX_H_INCLUDED -#define PLUGINFX_H_INCLUDED - -class PluginFx { - float s1, s2, s3, s4; - float sampleRate; - float sampleRateInv; - float d, c; - float R24; - float rcor24, rcor24Inv; - float bright; - - // 24 db multimode - float mm; - float mmt; - int mmch; - inline float NR24(float sample, float g, float lpc); - - // preprocess values taken the UI - float rCutoff; - float rReso; - float rGain; - - // thread values; if these are different from the UI, - // it needs to be recalculated. - float pReso; - float pCutoff; - float pGain; - - // I am still keeping the 2pole w/multimode filter - inline float NR(float sample, float g); - bool bandPassSw; - float rcor, rcorInv; - int R; - - float dc_id; - float dc_od; - float dc_r; - - public: - PluginFx(); - - // this is set directly by the ui / parameter - float Cutoff; - float Reso; - float Gain; - - void init(int sampleRate); - void process(float *work, int sampleSize); - float getGain(void); -}; - -#endif // PLUGINFX_H_INCLUDED diff --git a/UI.hpp b/UI.hpp index d403d43..582c367 100644 --- a/UI.hpp +++ b/UI.hpp @@ -35,7 +35,7 @@ #else #include "effect_freeverbf.h" #endif -#include "dexed.h" +#include "synth_dexed.h" #include //#include @@ -69,6 +69,7 @@ extern char receive_bank_filename[FILENAME_LEN]; extern void change_disp_sd(bool d); #endif +extern AudioControlSGTL5000 sgtl5000_1; extern AudioSourceMicroDexed* MicroDexed[NUM_DEXED]; #if defined(USE_FX) extern AudioSynthWaveform* chorus_modulator[NUM_DEXED]; @@ -2314,7 +2315,7 @@ void UI_func_engine(uint8_t param) #endif } - MicroDexed[selected_instance_id]->setEngineType(configuration.dexed[selected_instance_id].engine); + //MicroDexed[selected_instance_id]->setEngineType(configuration.dexed[selected_instance_id].engine); lcd.setCursor(0, 1); switch (configuration.dexed[selected_instance_id].engine) diff --git a/aligned_buf.h b/aligned_buf.h deleted file mode 100644 index b6942ec..0000000 --- a/aligned_buf.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - 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. -*/ - -// A convenient wrapper for buffers with alignment constraints - -// Note that if we were on C++11, we'd use aligned_storage or somesuch. - -#ifndef __ALIGNED_BUF_H -#define __ALIGNED_BUF_H - -#include - -template -class AlignedBuf { - public: - T *get() { - return (T *)((((intptr_t)storage_) + alignment - 1) & -alignment); - } - private: - unsigned char storage_[size * sizeof(T) + alignment]; -}; - -#endif // __ALIGNED_BUF_H diff --git a/controllers.h b/controllers.h deleted file mode 100644 index 32bc303..0000000 --- a/controllers.h +++ /dev/null @@ -1,145 +0,0 @@ -/* - 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. -*/ - -#ifndef __CONTROLLERS_H -#define __CONTROLLERS_H - -#include -#include "synth.h" -#include -#include -#include -#include "config.h" - -// State of MIDI controllers -const int kControllerPitch = 0; -const int kControllerPitchRange = 1; -const int kControllerPitchStep = 2; -const int kControllerPortamentoGlissando = 3; - -class FmCore; - -class FmMod { - public: - uint8_t range; - bool pitch; - bool amp; - bool eg; - uint8_t ctrl_mode; - uint8_t _dummy_; - - FmMod() - { - range = 0; - ctrl_mode = 0; - pitch = false; - amp = false; - eg = false; - } - - void setRange(uint8_t r) - { - range = r < 0 || r > 99 ? 0 : r; - } - - void setTarget(uint8_t assign) - { - assign = assign < 0 || assign > 7 ? 0 : assign; - pitch = assign & 1; // PITCH - amp = assign & 2; // AMP - eg = assign & 4; // EG - } - - void setMode(uint8_t m) - { - ctrl_mode = m > MIDI_CONTROLLER_MODE_MAX ? 0 : m; - } -}; - -class Controllers { - void applyMod(int cc, FmMod &mod) - { - uint8_t total = 0; - float range = mod.range / 100.0; - - switch (mod.ctrl_mode) - { - case 0: - total = uint8_t(float(cc) * range); // LINEAR mode - break; - case 1: - total = uint8_t(127.0 * range - (float(cc) * range)); // REVERSE mode - break; - case 2: - total = uint8_t(range * float(cc) + (1.0 - range) * 127.0); // DIRECT BC mode by Thierry (opus.quatre) - break; - } - - if (mod.amp) - amp_mod = max(amp_mod, total); - - if (mod.pitch) - pitch_mod = max(pitch_mod, total); - - if (mod.eg) - eg_mod = max(eg_mod, total); - } - - public: - int32_t values_[4]; - - uint8_t amp_mod; - uint8_t pitch_mod; - uint8_t eg_mod; - - uint8_t aftertouch_cc; - uint8_t breath_cc; - uint8_t foot_cc; - uint8_t modwheel_cc; - bool portamento_enable_cc; - int portamento_cc; - bool portamento_gliss_cc; - int masterTune; - - uint8_t opSwitch; - - FmMod wheel; - FmMod foot; - FmMod breath; - FmMod at; - - Controllers() { - amp_mod = 0; - pitch_mod = 0; - eg_mod = 0; - } - - void refresh() { - amp_mod = pitch_mod = eg_mod = 0; - - applyMod(modwheel_cc, wheel); - applyMod(breath_cc, breath); - applyMod(foot_cc, foot); - applyMod(aftertouch_cc, at); - - if ( ! ((wheel.eg || foot.eg) || (breath.eg || at.eg)) ) - eg_mod = 127; - } - - FmCore *core; -}; - -#endif // __CONTROLLERS_H diff --git a/dexed.cpp b/dexed.cpp deleted file mode 100644 index bf7263b..0000000 --- a/dexed.cpp +++ /dev/null @@ -1,934 +0,0 @@ -/* - 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 - - 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 - -*/ - -#include "config.h" -#include "synth.h" -#include "dexed.h" -#include "EngineMkI.h" -#include "EngineOpl.h" -#include "fm_core.h" -#include "exp2.h" -#include "sin.h" -#include "freqlut.h" -#include "controllers.h" -#include "PluginFx.h" -#include -#include -#include "porta.h" -#ifdef USE_TEENSY_DSP -#include -#endif - -extern config_t configuration; - -Dexed::Dexed(uint16_t rate, uint8_t instance_id) -{ - uint8_t i; - - max_notes = MAX_NOTES; - setMaxNotes(max_notes); - - 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); - - engineMkI = new EngineMkI; - engineOpl = new EngineOpl; - engineMsfa = new FmCore; - - 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; - } - - currentNote = 0; - resetControllers(); - controllers.masterTune = 0; - controllers.opSwitch = 0x3f; // enable all operators - //controllers.opSwitch=0x00; - lastKeyDown = -1; - vuSignal = 0.0; - - lfo.reset(data + 137); - - setMonoMode(false); - - sustain = false; - - setEngineType(DEXED_ENGINE); - - id = instance_id; -} - -Dexed::~Dexed() -{ - currentNote = -1; - - for (uint8_t note = 0; note < max_notes; note++) - delete voices[note].dx7_note; - - delete(engineMsfa); - delete(engineOpl); - delete(engineMkI); -} - -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 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) - vuSignal *= decayFactor; - else - vuSignal = 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++; -#ifdef DEBUG - Serial.print(F("Start voice: ")); - Serial.print(note, DEC); - Serial.print(F(" pitch: ")); - Serial.println(pitch, DEC); -#endif - 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 += 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; - /* - #ifdef DEBUG - Serial.print(F("Stop voice: ")); - Serial.print(note, DEC); - Serial.print(F(" pitch: ")); - Serial.println(pitch, DEC); - Serial.print(F(" id: ")); - Serial.println(id, DEC); - #endif - */ - break; - } - } - - // note not found ? - if ( note >= max_notes ) { -#ifdef DEBUG - Serial.print(F("Note not found: ")); - Serial.println(pitch, DEC); -#endif - 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::setOPs(uint8_t ops) -{ - controllers.opSwitch = ops; -} - -uint8_t Dexed::getEngineType() { - return engineType; -} - -void Dexed::setEngineType(uint8_t tp) { - if (engineType == tp) - return; - - switch (tp) { - case DEXED_ENGINE_MARKI: - controllers.core = engineMkI; - break; - case DEXED_ENGINE_OPL: - controllers.core = engineOpl; - break; - default: - controllers.core = engineMsfa; - tp = DEXED_ENGINE_MODERN; - break; - } - engineType = tp; - panic(); - controllers.refresh(); -} - -bool Dexed::isMonoMode(void) { - return monoMode; -} - -void Dexed::setMonoMode(bool mode) { - if (monoMode == mode) - return; - - //panic(); - 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++) - 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; - } - } -} - -/*void Dexed::setMaxNotes(uint8_t n) { - if (n <= max_notes) - { - notesOff(); - max_notes = n; - //panic(); - controllers.refresh(); - } - }*/ - -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; - VoiceStatus voiceStatus; - - 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); - /* - #ifdef DEBUG - Serial.println(i); - Serial.print("ALG: "); - Serial.println(data[134]+1); - #endif - */ - for (uint8_t op = 0; op < 6; op++) - { - if ((op_carrier & (1 << op))) - { - // this voice is a carrier! - op_carrier_num++; - /* - #ifdef DEBUG - Serial.print("OP: "); - Serial.print(op); - Serial.print(" AMP_STEP: "); - Serial.print(voiceStatus.ampStep[op],DEC); - Serial.print(" AMP: "); - Serial.println(voiceStatus.amp[op],DEC); - #endif - */ - if (voiceStatus.amp[op] < VOICE_SILENCE_LEVEL && voiceStatus.ampStep[op] >= 3) - { - // this voice produces no audio output - op_amp++; - } - } - } - - /* - Serial.print(F("ID=")); - Serial.print(id, DEC); - Serial.print(F(" OP carrier=")); - Serial.print(op_carrier_num, DEC); - Serial.print(F(" OP amp=")); - Serial.println(op_amp, DEC); - */ - - /* - #ifdef DEBUG - Serial.print("OP_AMP: "); - Serial.print(op_amp); - Serial.print(" OP_CARRIER_NUM: "); - Serial.println(op_carrier_num); - #endif - */ - 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* encoded_data, uint8_t* new_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); -} - -bool Dexed::loadVoiceParameters(uint8_t* new_data) -{ - char dexed_voice_name[11]; - - panic(); - - memcpy(&data, new_data, 155); - - doRefreshVoice(); - //activate(); - - strncpy(dexed_voice_name, (char *)&new_data[145], sizeof(dexed_voice_name) - 1); - dexed_voice_name[10] = '\0'; -#ifdef DEBUG - Serial.print(F("Voice [")); - Serial.print(dexed_voice_name); - Serial.println(F("] loaded.")); -#endif - - return (true); -} - -void Dexed::setPBController(uint8_t pb_range, uint8_t pb_step) -{ -#ifdef DEBUG - Serial.println(F("Dexed::setPBController")); -#endif - - pb_range = constrain(pb_range, PB_RANGE_MIN, PB_RANGE_MAX); - pb_step = constrain(pb_step, PB_STEP_MIN, PB_STEP_MAX); - - 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, MW_RANGE_MIN, MW_RANGE_MAX); - mw_assign = constrain(mw_assign, MW_ASSIGN_MIN, MW_ASSIGN_MAX); - mw_mode = constrain(mw_mode, MW_MODE_MIN, MW_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, FC_RANGE_MIN, FC_RANGE_MAX); - fc_assign = constrain(fc_assign, FC_ASSIGN_MIN, FC_ASSIGN_MAX); - fc_mode = constrain(fc_mode, FC_MODE_MIN, FC_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, BC_RANGE_MIN, BC_RANGE_MAX); - bc_assign = constrain(bc_assign, BC_ASSIGN_MIN, BC_ASSIGN_MAX); - bc_mode = constrain(bc_mode, BC_MODE_MIN, BC_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, AT_RANGE_MIN, AT_RANGE_MAX); - at_assign = constrain(at_assign, AT_ASSIGN_MIN, AT_ASSIGN_MAX); - at_mode = constrain(at_mode, AT_MODE_MIN, AT_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) -{ - 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(); -} - -void Dexed::setMaxNotes(uint8_t num_voices) -{ - if (voices) - { - panic(); - for (uint8_t i = 0; i < max_notes; i++) - { - if (voices[i].dx7_note) - delete voices[i].dx7_note; - voices[i].keydown = false; - voices[i].sustained = false; - voices[i].live = false; - voices[i].key_pressed_timer = 0; - } - delete(voices); - } - voices = new ProcessorVoice[num_voices]; - max_notes = num_voices; - - for (uint8_t 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; - } - controllers.refresh(); -} - -/* - // 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; - } - } -*/ diff --git a/dexed.h b/dexed.h deleted file mode 100644 index 176b470..0000000 --- a/dexed.h +++ /dev/null @@ -1,221 +0,0 @@ -/* - 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 - - 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 - -*/ - -#ifndef DEXED_H_INCLUDED -#define DEXED_H_INCLUDED - -#include -#include "controllers.h" -#include "dx7note.h" -#include "lfo.h" -#include "synth.h" -#include "fm_core.h" -#include "EngineMkI.h" -#include "EngineOpl.h" -#include "PluginFx.h" -#include -#include "config.h" - -extern bool load_sysex(uint8_t bank, uint8_t voice_number); -extern AudioControlSGTL5000 sgtl5000_1; -extern float vol; -extern float vol_right; -extern float vol_left; - -struct ProcessorVoice { - int16_t midi_note; - uint8_t velocity; - int16_t porta; - bool keydown; - bool sustained; - bool live; - uint32_t key_pressed_timer; - Dx7Note *dx7_note; -}; - -enum DexedEngineResolution { - DEXED_ENGINE_MODERN, // 0 - DEXED_ENGINE_MARKI, // 1 - DEXED_ENGINE_OPL // 2 -}; - -enum DexedVoiceOPParameters { - 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 - DEXED_OP_SCL_LEFT_CURVE, // 11 - DEXED_OP_SCL_RGHT_CURVE, // 12 - DEXED_OP_OSC_RATE_SCALE, // 13 - DEXED_OP_AMP_MOD_SENS, // 14 - DEXED_OP_KEY_VEL_SENS, // 15 - DEXED_OP_OUTPUT_LEV, // 16 - DEXED_OP_OSC_MODE, // 17 - DEXED_OP_FREQ_COARSE, // 18 - DEXED_OP_FREQ_FINE, // 19 - DEXED_OP_OSC_DETUNE // 20 -}; - -#define DEXED_VOICE_OFFSET 126 -enum DexedVoiceParameters { - 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 - DEXED_ALGORITHM, // 8 - DEXED_FEEDBACK, // 9 - DEXED_OSC_KEY_SYNC, // 10 - DEXED_LFO_SPEED, // 11 - DEXED_LFO_DELAY, // 12 - DEXED_LFO_PITCH_MOD_DEP, // 13 - DEXED_LFO_AMP_MOD_DEP, // 14 - DEXED_LFO_SYNC, // 15 - DEXED_LFO_WAVE, // 16 - DEXED_LFO_PITCH_MOD_SENS, // 17 - DEXED_TRANSPOSE, // 18 - DEXED_NAME // 19 -}; - -/* #define DEXED_GLOBAL_PARAMETER_OFFSET 155 - enum DexedGlobalParameters { - DEXED_PITCHBEND_RANGE, // 0 - DEXED_PITCHBEND_STEP, // 1 - DEXED_MODWHEEL_RANGE, // 2 - DEXED_MODWHEEL_ASSIGN, // 3 - DEXED_FOOTCTRL_RANGE, // 4 - DEXED_FOOTCTRL_ASSIGN, // 5 - DEXED_BREATHCTRL_RANGE, // 6 - DEXED_BREATHCTRL_ASSIGN, // 7 - DEXED_AT_RANGE, // 8 - DEXED_AT_ASSIGN, // 9 - DEXED_MASTER_TUNE, // 10 - DEXED_OP1_ENABLE, // 11 - DEXED_OP2_ENABLE, // 12 - DEXED_OP3_ENABLE, // 13 - DEXED_OP4_ENABLE, // 14 - DEXED_OP5_ENABLE, // 15 - DEXED_OP6_ENABLE, // 16 - DEXED_MAX_NOTES, // 17 - DEXED_PORTAMENTO_MODE, // 18 - DEXED_PORTAMENTO_GLISSANDO, // 19 - DEXED_PORTAMENTO_TIME, // 20 - }; */ - -// GLOBALS - -//============================================================================== - -class Dexed -{ - public: - Dexed(uint16_t rate, uint8_t id); - ~Dexed(); - void activate(void); - void deactivate(void); - uint8_t getEngineType(); - void setEngineType(uint8_t tp); - bool isMonoMode(void); - void setMonoMode(bool mode); - void setRefreshMode(bool mode); - void getSamples(uint16_t n_samples, int16_t* buffer); - void panic(void); - void notesOff(void); - void resetControllers(void); - void setMaxNotes(uint8_t n); - uint8_t getMaxNotes(void); - void doRefreshVoice(void); - void setOPs(uint8_t ops); - bool decodeVoice(uint8_t* encoded_data, uint8_t* data); - bool encodeVoice(uint8_t* encoded_data); - bool getVoiceData(uint8_t* data_copy); - bool loadVoiceParameters(uint8_t* data); - bool loadGlobalParameters(uint8_t* data); - bool initGlobalParameters(void); - void keyup(int16_t pitch); - void keydown(int16_t pitch, uint8_t velo); - void setSustain(bool sustain); - bool getSustain(void); - uint8_t getNumNotesPlaying(void); - void setPBController(uint8_t pb_range, uint8_t pb_step); - void setMWController(uint8_t mw_range, uint8_t mw_assign, uint8_t mw_mode); - void setFCController(uint8_t fc_range, uint8_t fc_assign, uint8_t fc_mode); - void setBCController(uint8_t bc_range, uint8_t bc_assign, uint8_t bc_mode); - void setATController(uint8_t at_range, uint8_t at_assign, uint8_t at_mode); - void setPortamentoMode(uint8_t portamento_mode, uint8_t portamento_glissando, uint8_t portamento_time); - void setMaxVoices(uint8_t num_voices); - - //ProcessorVoice voices[MAX_NOTES]; - ProcessorVoice* voices = NULL; - - Controllers controllers; - PluginFx fx; - - uint8_t data[156] = { - 95, 29, 20, 50, 99, 95, 00, 00, 41, 00, 19, 00, 00, 03, 00, 06, 79, 00, 01, 00, 14, // OP6 eg_rate_1-4, level_1-4, kbd_lev_scl_brk_pt, kbd_lev_scl_lft_depth, kbd_lev_scl_rht_depth, kbd_lev_scl_lft_curve, kbd_lev_scl_rht_curve, kbd_rate_scaling, amp_mod_sensitivity, key_vel_sensitivity, operator_output_level, osc_mode, osc_freq_coarse, osc_freq_fine, osc_detune - 95, 20, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 00, 99, 00, 01, 00, 00, // OP5 - 95, 29, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 06, 89, 00, 01, 00, 07, // OP4 - 95, 20, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 02, 99, 00, 01, 00, 07, // OP3 - 95, 50, 35, 78, 99, 75, 00, 00, 00, 00, 00, 00, 00, 03, 00, 07, 58, 00, 14, 00, 07, // OP2 - 96, 25, 25, 67, 99, 75, 00, 00, 00, 00, 00, 00, 00, 03, 00, 02, 99, 00, 01, 00, 10, // OP1 - 94, 67, 95, 60, 50, 50, 50, 50, // 4 * pitch EG rates, 4 * pitch EG level - 04, 06, 00, // algorithm, feedback, osc sync - 34, 33, 00, 00, 00, 04, // lfo speed, lfo delay, lfo pitch_mod_depth, lfo_amp_mod_depth, lfo_sync, lfo_waveform - 03, 24, // pitch_mod_sensitivity, transpose - 69, 68, 80, 56, 85, 76, 84, 00, 00, 00 // 10 * char for name ("DEFAULT ") - }; // FM-Piano - - int lastKeyDown; - - protected: - //static const uint8_t MAX_ACTIVE_NOTES = MAX_NOTES; - //uint8_t max_notes = MAX_ACTIVE_NOTES; - uint8_t id; - uint8_t max_notes = 0; - int16_t currentNote; - bool sustain; - float vuSignal; - bool monoMode; - bool refreshMode; - bool refreshVoice; - uint8_t engineType; - //VoiceStatus voiceStatus; - Lfo lfo; - FmCore* engineMsfa; - EngineMkI* engineMkI; - EngineOpl* engineOpl; -}; - -#endif // PLUGINPROCESSOR_H_INCLUDED diff --git a/dexed_sd.cpp b/dexed_sd.cpp index b8586e5..4bde5ef 100644 --- a/dexed_sd.cpp +++ b/dexed_sd.cpp @@ -27,7 +27,7 @@ #include "config.h" #include #include -#include "dexed.h" +#include "synth_dexed.h" #include "dexed_sd.h" extern void init_MIDI_send_CC(void); diff --git a/dexed_sd.h b/dexed_sd.h index 52fe9af..f2ead00 100644 --- a/dexed_sd.h +++ b/dexed_sd.h @@ -24,7 +24,6 @@ */ #include "config.h" -#include "source_microdexed.h" #ifndef DEXED_SYSEX_H_INCLUDED #define DEXED_SYSEX_H_INCLUDED diff --git a/dx7note.cpp b/dx7note.cpp deleted file mode 100644 index 44a680e..0000000 --- a/dx7note.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/* - 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. -*/ - -#include -#include -#include "synth.h" -#include "freqlut.h" -#include "porta.h" -#include "exp2.h" -#include "controllers.h" -#include "dx7note.h" -#include "dexed.h" - -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; - } -} diff --git a/dx7note.h b/dx7note.h deleted file mode 100644 index 9a3355a..0000000 --- a/dx7note.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - 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. -*/ - -#ifndef SYNTH_DX7NOTE_H_ -#define SYNTH_DX7NOTE_H_ - -// This is the logic to put together a note from the MIDI description -// and run the low-level modules. - -// It will continue to evolve a bit, as note-stealing logic, scaling, -// and real-time control of parameters live here. - -#include -#include "env.h" -#include "pitchenv.h" -#include "fm_core.h" - -struct VoiceStatus { - uint32_t amp[6]; - char ampStep[6]; - char pitchStep; -}; - -class Dx7Note { - public: - Dx7Note(); - void init(const uint8_t patch[156], int midinote, int velocity, int srcnote, int porta, const Controllers *ctrls); - - // Note: this _adds_ to the buffer. Interesting question whether it's - // worth it... - void compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Controllers *ctrls); - - void keyup(); - - // TODO: some way of indicating end-of-note. Maybe should be a return - // value from the compute method? (Having a count return from keyup - // is also tempting, but if there's a dynamic parameter change after - // keyup, that won't work. - - // PG:add the update - void update(const uint8_t patch[156], int midinote, int velocity, int porta, const Controllers *ctrls); - void peekVoiceStatus(VoiceStatus &status); - void transferState(Dx7Note& src); - void transferSignal(Dx7Note &src); - void transferPortamento(Dx7Note &src); - void oscSync(); - - private: - Env env_[6]; - FmOpParams params_[6]; - PitchEnv pitchenv_; - int32_t basepitch_[6]; - int32_t fb_buf_[2]; - int32_t fb_shift_; - int32_t ampmodsens_[6]; - int32_t opMode[6]; - - int ampmoddepth_; - int algorithm_; - int pitchmoddepth_; - int pitchmodsens_; - - int porta_rateindex_; - int porta_gliss_; - int32_t porta_curpitch_[6]; -}; - -#endif // SYNTH_DX7NOTE_H_ diff --git a/env.cpp b/env.cpp deleted file mode 100644 index 5306c39..0000000 --- a/env.cpp +++ /dev/null @@ -1,190 +0,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. -*/ - -#include - -#include "synth.h" -#include "env.h" - -//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_; -} diff --git a/env.h b/env.h deleted file mode 100644 index 30bbfec..0000000 --- a/env.h +++ /dev/null @@ -1,81 +0,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. -*/ - -#ifndef __ENV_H -#define __ENV_H - -#include "synth.h" - -// DX7 envelope generation - -#define ACCURATE_ENVELOPE - -class Env { - public: - - // The rates and levels arrays are calibrated to match the Dx7 parameters - // (ie, value 0..99). The outlevel parameter is calibrated in microsteps - // (ie units of approx .023 dB), with 99 * 32 = nominal full scale. The - // rate_scaling parameter is in qRate units (ie 0..63). - void init(const int rates[4], const int levels[4], int outlevel, - int rate_scaling); - - void update(const int rates[4], const int levels[4], int outlevel, - int rate_scaling); - // Result is in Q24/doubling log format. Also, result is subsampled - // for every N samples. - // A couple more things need to happen for this to be used as a gain - // value. First, the # of outputs scaling needs to be applied. Also, - // modulation. - // Then, of course, log to linear. - int32_t getsample(); - - void keydown(bool down); - static int scaleoutlevel(int outlevel); - void getPosition(char *step); - - static void init_sr(double sample_rate); - void transfer(Env &src); - - private: - - // PG: This code is normalized to 44100, need to put a multiplier - // if we are not using 44100. - static uint32_t sr_multiplier; - - int rates_[4]; - int levels_[4]; - int outlevel_; - int rate_scaling_; - // Level is stored so that 2^24 is one doubling, ie 16 more bits than - // the DX7 itself (fraction is stored in level rather than separate - // counter) - int32_t level_; - int targetlevel_; - bool rising_; - int ix_; - int inc_; -#ifdef ACCURATE_ENVELOPE - int staticcount_; -#endif - - bool down_; - - void advance(int newix); -}; - -#endif // __ENV_H diff --git a/exp2.cpp b/exp2.cpp deleted file mode 100644 index 5c93b3b..0000000 --- a/exp2.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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. - */ - -#define _USE_MATH_DEFINES -#include - -#include "synth.h" -#include "exp2.h" - -#include - -#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]; -} diff --git a/exp2.h b/exp2.h deleted file mode 100644 index 29dc298..0000000 --- a/exp2.h +++ /dev/null @@ -1,80 +0,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. -*/ - -class Exp2 { - public: - Exp2(); - - static void init(); - - // Q24 in, Q24 out - static int32_t lookup(int32_t x); -}; - -#define EXP2_LG_N_SAMPLES 10 -#define EXP2_N_SAMPLES (1 << EXP2_LG_N_SAMPLES) - -#define EXP2_INLINE - -extern int32_t exp2tab[EXP2_N_SAMPLES << 1]; - -#ifdef EXP2_INLINE -inline -int32_t Exp2::lookup(int32_t x) { - const int SHIFT = 24 - EXP2_LG_N_SAMPLES; - int lowbits = x & ((1 << SHIFT) - 1); - int x_int = (x >> (SHIFT - 1)) & ((EXP2_N_SAMPLES - 1) << 1); - int dy = exp2tab[x_int]; - int y0 = exp2tab[x_int + 1]; - - int y = y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); - return y >> (6 - (x >> 24)); -} -#endif - -class Tanh { - public: - static void init(); - - // Q24 in, Q24 out - static int32_t lookup(int32_t x); -}; - -#define TANH_LG_N_SAMPLES 10 -#define TANH_N_SAMPLES (1 << TANH_LG_N_SAMPLES) - -extern int32_t tanhtab[TANH_N_SAMPLES << 1]; - -inline -int32_t Tanh::lookup(int32_t x) { - int32_t signum = x >> 31; - x ^= signum; - if (x >= (4 << 24)) { - if (x >= (17 << 23)) { - return signum ^ (1 << 24); - } - int32_t sx = ((int64_t) - 48408812 * (int64_t)x) >> 24; - return signum ^ ((1 << 24) - 2 * Exp2::lookup(sx)); - } else { - const int SHIFT = 26 - TANH_LG_N_SAMPLES; - int lowbits = x & ((1 << SHIFT) - 1); - int x_int = (x >> (SHIFT - 1)) & ((TANH_N_SAMPLES - 1) << 1); - int dy = tanhtab[x_int]; - int y0 = tanhtab[x_int + 1]; - int y = y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); - return y ^ signum; - } -} diff --git a/fast_log.h b/fast_log.h deleted file mode 100644 index 3e2c24b..0000000 --- a/fast_log.h +++ /dev/null @@ -1,50 +0,0 @@ -/* ---------------------------------------------------------------------- -* https://community.arm.com/tools/f/discussions/4292/cmsis-dsp-new-functionality-proposal/22621#22621 -* Fast approximation to the log2() function. It uses a two step -* process. First, it decomposes the floating-point number into -* a fractional component F and an exponent E. The fraction component -* is used in a polynomial approximation and then the exponent added -* to the result. A 3rd order polynomial is used and the result -* when computing db20() is accurate to 7.984884e-003 dB. -** ------------------------------------------------------------------- */ - -float log2f_approx_coeff[4] = {1.23149591368684f, -4.11852516267426f, 6.02197014179219f, -3.13396450166353f}; - -float log2f_approx(float X) -{ - float *C = &log2f_approx_coeff[0]; - float Y; - float F; - int E; - - // This is the approximation to log2() - F = frexpf(fabsf(X), &E); - - // Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E; - Y = *C++; - Y *= F; - Y += (*C++); - Y *= F; - Y += (*C++); - Y *= F; - Y += (*C++); - Y += E; - return(Y); -} - -// https://codingforspeed.com/using-faster-exponential-approximation/ -inline float expf_approx(float x) { - x = 1.0f + x / 1024; - x *= x; x *= x; x *= x; x *= x; - x *= x; x *= x; x *= x; x *= x; - x *= x; x *= x; - return x; -} - -inline float unitToDb(float unit) { - return 6.02f * log2f_approx(unit); -} - -inline float dbToUnit(float db) { - return expf_approx(db * 2.302585092994046f * 0.05f); -} \ No newline at end of file diff --git a/fm_core.cpp b/fm_core.cpp deleted file mode 100644 index 131f38a..0000000 --- a/fm_core.cpp +++ /dev/null @@ -1,158 +0,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. -*/ - -#ifdef VERBOSE -#include -#endif - -#include "synth.h" -#include "exp2.h" -#include "fm_op_kernel.h" -#include "fm_core.h" - - -//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] & 7) == OUT_BUS_ADD) - op_out |= 1 << i; - } - - return op_out; -} - -void FmCore::dump(void) -{ -#ifdef DEBUG - for (uint8_t i = 0; i < 32; i++) - { - Serial.print(i + 1); - Serial.print(":"); - const FmAlgorithm &alg = algorithms[i]; - for (uint8_t j = 0; j < 6; j++) - { - uint8_t flags = alg.ops[j]; - Serial.print(" "); - if (flags & FB_IN) - Serial.print("["); - Serial.print(flags & IN_BUS_ONE ? "1" : flags & IN_BUS_TWO ? "2" : "0"); - Serial.print("->"); - Serial.print(flags & OUT_BUS_ONE ? "1" : flags & OUT_BUS_TWO ? "2" : "0"); - if (flags & OUT_BUS_ADD) - Serial.print("+"); - if (flags & FB_OUT) - Serial.print("]"); - } - Serial.print(" "); - Serial.println(n_out(alg)); - } -#else - ; -#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; - } -} diff --git a/fm_core.h b/fm_core.h deleted file mode 100644 index 653b634..0000000 --- a/fm_core.h +++ /dev/null @@ -1,59 +0,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. -*/ - -#ifndef __FM_CORE_H -#define __FM_CORE_H - -#include -#include "aligned_buf.h" -#include "fm_op_kernel.h" -#include "synth.h" -#include "controllers.h" - - -class FmOperatorInfo { - public: - int in; - int out; -}; - -enum FmOperatorFlags { - OUT_BUS_ONE = 1 << 0, - OUT_BUS_TWO = 1 << 1, - OUT_BUS_ADD = 1 << 2, - IN_BUS_ONE = 1 << 4, - IN_BUS_TWO = 1 << 5, - FB_IN = 1 << 6, - FB_OUT = 1 << 7 -}; - -class FmAlgorithm { - public: - int ops[6]; -}; - -class FmCore { - public: - virtual ~FmCore() {}; - static void dump(); - uint8_t get_carrier_operators(uint8_t algorithm); - virtual void render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int feedback_gain); - protected: - AlignedBufbuf_[2]; - const static FmAlgorithm algorithms[32]; -}; - -#endif // __FM_CORE_H diff --git a/fm_op_kernel.cpp b/fm_op_kernel.cpp deleted file mode 100644 index df30e4b..0000000 --- a/fm_op_kernel.cpp +++ /dev/null @@ -1,283 +0,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. -*/ - -#include "config.h" -#include - -#include - -#ifdef HAVE_NEON -#include -#endif - -#include "synth.h" -#include "sin.h" -#include "fm_op_kernel.h" - -#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); - -const int32_t __attribute__ ((aligned(16))) zeros[_N_] = {0}; - -#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 diff --git a/fm_op_kernel.h b/fm_op_kernel.h deleted file mode 100644 index d164bc2..0000000 --- a/fm_op_kernel.h +++ /dev/null @@ -1,47 +0,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. -*/ - -#ifndef __FM_OP_KERNEL_H -#define __FM_OP_KERNEL_H - -struct FmOpParams { - int32_t level_in; // value to be computed (from level to gain[0]) - int32_t gain_out; // computed value (gain[1] to gain[0]) - int32_t freq; - int32_t phase; -}; - -class FmOpKernel { - public: - // gain1 and gain2 represent linear step: gain for sample i is - // gain1 + (1 + i) / 64 * (gain2 - gain1) - - // This is the basic FM operator. No feedback. - static void compute(int32_t *output, const int32_t *input, - int32_t phase0, int32_t freq, - int32_t gain1, int32_t gain2, bool add); - - // This is a sine generator, no feedback. - static void compute_pure(int32_t *output, int32_t phase0, int32_t freq, - int32_t gain1, int32_t gain2, bool add); - - // One op with feedback, no add. - static void compute_fb(int32_t *output, int32_t phase0, int32_t freq, - int32_t gain1, int32_t gain2, - int32_t *fb_buf, int fb_gain, bool add); -}; - -#endif diff --git a/freqlut.cpp b/freqlut.cpp deleted file mode 100644 index 6ef1c7c..0000000 --- a/freqlut.cpp +++ /dev/null @@ -1,55 +0,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. -*/ - -// 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. - -#include -#include - -#include "freqlut.h" - -#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); -} diff --git a/freqlut.h b/freqlut.h deleted file mode 100644 index 83ad3c7..0000000 --- a/freqlut.h +++ /dev/null @@ -1,23 +0,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. -*/ - -#include "synth.h" - -class Freqlut { - public: - static void init(FRAC_NUM sample_rate); - static int32_t lookup(int32_t logfreq); -}; diff --git a/lfo.cpp b/lfo.cpp deleted file mode 100644 index 33f1aa3..0000000 --- a/lfo.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - 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 - -#include "synth.h" - -#include "sin.h" -#include "lfo.h" - -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; -} diff --git a/lfo.h b/lfo.h deleted file mode 100644 index 3a063c1..0000000 --- a/lfo.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - 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 - -class Lfo { - public: - static void init(FRAC_NUM sample_rate); - void reset(const uint8_t params[6]); - - // result is 0..1 in Q24 - int32_t getsample(); - - // result is 0..1 in Q24 - int32_t getdelay(); - - void keydown(); - private: - static uint32_t unit_; - - uint32_t phase_; // Q32 - uint32_t delta_; - uint8_t waveform_; - uint8_t randstate_; - bool sync_; - - uint32_t delaystate_; - uint32_t delayinc_; - uint32_t delayinc2_; -}; diff --git a/module.h b/module.h deleted file mode 100644 index b835a53..0000000 --- a/module.h +++ /dev/null @@ -1,30 +0,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. -*/ - -#ifndef SYNTH_MODULE_H -#define SYNTH_MODULE_H - -#include - -class Module { - public: - static const int lg_n = 6; - static const int n = 1 << lg_n; - virtual void process(const int32_t **inbufs, const int32_t *control_in, - const int32_t *control_last, int32_t **outbufs) = 0; -}; - -#endif // SYNTH_MODULE_H diff --git a/pitchenv.cpp b/pitchenv.cpp deleted file mode 100644 index d7f53bd..0000000 --- a/pitchenv.cpp +++ /dev/null @@ -1,93 +0,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. -*/ - -#include "synth.h" -#include "pitchenv.h" - -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_; -} diff --git a/pitchenv.h b/pitchenv.h deleted file mode 100644 index 50b5134..0000000 --- a/pitchenv.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - 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. -*/ - -#ifndef __PITCHENV_H -#define __PITCHENV_H - -// Computation of the DX7 pitch envelope - -class PitchEnv { - public: - static void init(FRAC_NUM sample_rate); - - // The rates and levels arrays are calibrated to match the Dx7 parameters - // (ie, value 0..99). - void set(const int rates[4], const int levels[4]); - - // Result is in Q24/octave - int32_t getsample(); - void keydown(bool down); - void getPosition(char *step); - private: - static int unit_; - int rates_[4]; - int levels_[4]; - int32_t level_; - int targetlevel_; - bool rising_; - int ix_; - int inc_; - - bool down_; - - void advance(int newix); -}; - -extern const uint8_t pitchenv_rate[]; -extern const int8_t pitchenv_tab[]; - -#endif // __PITCHENV_H diff --git a/porta.cpp b/porta.cpp deleted file mode 100644 index 059c06b..0000000 --- a/porta.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - 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. -*/ - -#include -#include "porta.h" -#include "synth.h" - -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]; diff --git a/porta.h b/porta.h deleted file mode 100644 index d8a6b8c..0000000 --- a/porta.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - 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. -*/ - -#ifndef SYNTH_PORTA_H_ -#define SYNTH_PORTA_H_ - -#include - -struct Porta { - public: - static void init_sr(double sampleRate); - static int32_t rates[128]; -}; - -#endif // SYNTH_PORTA_H_ diff --git a/sin.cpp b/sin.cpp deleted file mode 100644 index 1949cd5..0000000 --- a/sin.cpp +++ /dev/null @@ -1,144 +0,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. - */ - -#define _USE_MATH_DEFINES -#include - -#include "synth.h" -#include "sin.h" - -#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; -} diff --git a/sin.h b/sin.h deleted file mode 100644 index 85eccd1..0000000 --- a/sin.h +++ /dev/null @@ -1,62 +0,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. -*/ - -class Sin { - public: - Sin(); - - static void init(); - static int32_t lookup(int32_t phase); - static int32_t compute(int32_t phase); - - // A more accurate sine, both input and output Q30 - static int32_t compute10(int32_t phase); -}; - -#define SIN_LG_N_SAMPLES 10 -#define SIN_N_SAMPLES (1 << SIN_LG_N_SAMPLES) - -#define SIN_INLINE - -// Use twice as much RAM for the LUT but avoid a little computation -#define SIN_DELTA - -#ifdef SIN_DELTA -extern int32_t sintab[SIN_N_SAMPLES << 1]; -#else -extern int32_t sintab[SIN_N_SAMPLES + 1]; -#endif - -#ifdef SIN_INLINE -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 diff --git a/source_microdexed.h b/source_microdexed.h deleted file mode 100644 index f324807..0000000 --- a/source_microdexed.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once -#include "config.h" -#include "dexed.h" - -#include - -class AudioSourceMicroDexed : public AudioStream, public Dexed { - public: - const uint16_t audio_block_time_us = 1000000 / (SAMPLE_RATE / AUDIO_BLOCK_SAMPLES); - uint32_t xrun = 0; - uint16_t render_time_max = 0; - - AudioSourceMicroDexed(uint16_t sample_rate, uint8_t id) : AudioStream(0, NULL), Dexed(sample_rate,id) { }; - - void 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; - }; - - private: - volatile bool in_update = false; -}; diff --git a/synth.h b/synth.h deleted file mode 100644 index f74f98a..0000000 --- a/synth.h +++ /dev/null @@ -1,76 +0,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. -*/ - -#ifndef __SYNTH_H -#define __SYNTH_H - -//#include -//#define SUPER_PRECISE -#include - -// This IS not be present on MSVC. -// See http://stackoverflow.com/questions/126279/c99-stdint-h-header-and-ms-visual-studio -#include -#ifdef _MSC_VER -typedef __int32 int32_t; -typedef unsigned __int32 uint32_t; -typedef __int16 SInt16; -#endif - -#define LG_N 6 -#define _N_ (1 << LG_N) - -#if defined(__APPLE__) -#include -#define SynthMemoryBarrier() OSMemoryBarrier() -#elif defined(__GNUC__) -#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) -#define SynthMemoryBarrier() __sync_synchronize() -#endif -#endif - - -// #undef SynthMemoryBarrier() - -#ifndef SynthMemoryBarrier -// need to understand why this must be defined -// #warning Memory barrier is not enabled -#define SynthMemoryBarrier() -#endif - -template -inline static T min(const T& a, const T& b) { - return a < b ? a : b; -} - -template -inline static T max(const T& a, const T& b) { - return a > b ? a : b; -} - -#define QER(n,b) ( ((float)n)/(1< + + 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(int rate) +{ + uint8_t i; + + 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; + + for (i = 0; i < MAX_ACTIVE_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; + } + + max_notes = MAX_NOTES; + currentNote = 0; + resetControllers(); + controllers.masterTune = 0; + controllers.opSwitch = 0x3f; // enable all operators + //controllers.opSwitch=0x00; + lastKeyDown = -1; + vuSignal = 0.0; + controllers.core = engineMsfa; + + lfo.reset(data + 137); + + setMonoMode(false); + + sustain = false; +} + +Dexed::~Dexed() +{ + currentNote = -1; + + for (uint8_t note = 0; note < MAX_ACTIVE_NOTES; note++) + delete voices[note].dx7_note; + + delete(engineMsfa); +} + +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 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) + vuSignal *= decayFactor; + else + vuSignal = 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 += 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::setOPs(uint8_t ops) +{ + controllers.opSwitch = ops; +} + +bool Dexed::isMonoMode(void) { + return monoMode; +} + +void Dexed::setMonoMode(bool mode) { + if (monoMode == mode) + return; + + //panic(); + 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_ACTIVE_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_ACTIVE_NOTES; i++) { + if (voices[i].live == true) { + voices[i].keydown = false; + voices[i].live = false; + } + } +} + +void Dexed::setMaxNotes(uint8_t n) { + if (n <= MAX_ACTIVE_NOTES) + { + notesOff(); + max_notes = n; + //panic(); + controllers.refresh(); + } +} + +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* encoded_data, uint8_t* new_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); +} + +bool Dexed::loadVoiceParameters(uint8_t* new_data) +{ + char dexed_voice_name[11]; + + panic(); + + memcpy(&data, new_data, 155); + + doRefreshVoice(); + //activate(); + + strncpy(dexed_voice_name, (char *)&new_data[145], sizeof(dexed_voice_name) - 1); + dexed_voice_name[10] = '\0'; +#ifdef DEBUG + Serial.print(F("Voice [")); + Serial.print(dexed_voice_name); + Serial.println(F("] loaded.")); +#endif + + return (true); +} + +void Dexed::setPBController(uint8_t pb_range, uint8_t pb_step) +{ +#ifdef DEBUG + Serial.println(F("Dexed::setPBController")); +#endif + + pb_range = constrain(pb_range, PB_RANGE_MIN, PB_RANGE_MAX); + pb_step = constrain(pb_step, PB_STEP_MIN, PB_STEP_MAX); + + 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, MW_RANGE_MIN, MW_RANGE_MAX); + mw_assign = constrain(mw_assign, MW_ASSIGN_MIN, MW_ASSIGN_MAX); + mw_mode = constrain(mw_mode, MW_MODE_MIN, MW_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, FC_RANGE_MIN, FC_RANGE_MAX); + fc_assign = constrain(fc_assign, FC_ASSIGN_MIN, FC_ASSIGN_MAX); + fc_mode = constrain(fc_mode, FC_MODE_MIN, FC_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, BC_RANGE_MIN, BC_RANGE_MAX); + bc_assign = constrain(bc_assign, BC_ASSIGN_MIN, BC_ASSIGN_MAX); + bc_mode = constrain(bc_mode, BC_MODE_MIN, BC_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, AT_RANGE_MIN, AT_RANGE_MAX); + at_assign = constrain(at_assign, AT_ASSIGN_MIN, AT_ASSIGN_MAX); + at_mode = constrain(at_mode, AT_MODE_MIN, AT_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) +{ + 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(); +} + +/* + // 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; +} + +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; +} diff --git a/synth_dexed.h b/synth_dexed.h new file mode 100644 index 0000000..57dd096 --- /dev/null +++ b/synth_dexed.h @@ -0,0 +1,1247 @@ +#include +#include +#include "config.h" +/***************************************************** + * CODE; orig_code/synth.h + *****************************************************/ +/* + 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 SUPER_PRECISE + +// This IS not be present on MSVC. +// See http://stackoverflow.com/questions/126279/c99-stdint-h-header-and-ms-visual-studio +#ifdef _MSC_VER +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int16 SInt16; +#endif + +#define LG_N 6 +#define _N_ (1 << LG_N) + +#if defined(__APPLE__) +#include +#define SynthMemoryBarrier() OSMemoryBarrier() +#elif defined(__GNUC__) +#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) +#define SynthMemoryBarrier() __sync_synchronize() +#endif +#endif + + +// #undef SynthMemoryBarrier() + +#ifndef SynthMemoryBarrier +// need to understand why this must be defined +// #warning Memory barrier is not enabled +#define SynthMemoryBarrier() +#endif + +template +inline static T min(const T& a, const T& b) { + return a < b ? a : b; +} + +template +inline static T max(const T& a, const T& b) { + return a > b ? a : b; +} + +#define QER(n,b) ( ((float)n)/(1< +class AlignedBuf { + public: + T *get() { + return (T *)((((intptr_t)storage_) + alignment - 1) & -alignment); + } + private: + unsigned char storage_[size * sizeof(T) + alignment]; +}; + + +//===================================================== +/***************************************************** + * CODE; orig_code/sin.h + *****************************************************/ +/* + 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. +*/ + +class Sin { + public: + Sin(); + + static void init(); + static int32_t lookup(int32_t phase); + static int32_t compute(int32_t phase); + + // A more accurate sine, both input and output Q30 + static int32_t compute10(int32_t phase); +}; + +#define SIN_LG_N_SAMPLES 10 +#define SIN_N_SAMPLES (1 << SIN_LG_N_SAMPLES) + +#define SIN_INLINE + +// Use twice as much RAM for the LUT but avoid a little computation +#define SIN_DELTA + +#ifdef SIN_DELTA +extern int32_t sintab[SIN_N_SAMPLES << 1]; +#else +extern int32_t sintab[SIN_N_SAMPLES + 1]; +#endif + +#ifdef SIN_INLINE +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 + +//===================================================== +/***************************************************** + * CODE; orig_code/exp2.h + *****************************************************/ +/* + 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. +*/ + +class Exp2 { + public: + Exp2(); + + static void init(); + + // Q24 in, Q24 out + static int32_t lookup(int32_t x); +}; + +#define EXP2_LG_N_SAMPLES 10 +#define EXP2_N_SAMPLES (1 << EXP2_LG_N_SAMPLES) + +#define EXP2_INLINE + +extern int32_t exp2tab[EXP2_N_SAMPLES << 1]; + +#ifdef EXP2_INLINE +inline +int32_t Exp2::lookup(int32_t x) { + const int SHIFT = 24 - EXP2_LG_N_SAMPLES; + int lowbits = x & ((1 << SHIFT) - 1); + int x_int = (x >> (SHIFT - 1)) & ((EXP2_N_SAMPLES - 1) << 1); + int dy = exp2tab[x_int]; + int y0 = exp2tab[x_int + 1]; + + int y = y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); + return y >> (6 - (x >> 24)); +} +#endif + +class Tanh { + public: + static void init(); + + // Q24 in, Q24 out + static int32_t lookup(int32_t x); +}; + +#define TANH_LG_N_SAMPLES 10 +#define TANH_N_SAMPLES (1 << TANH_LG_N_SAMPLES) + +extern int32_t tanhtab[TANH_N_SAMPLES << 1]; + +inline +int32_t Tanh::lookup(int32_t x) { + int32_t signum = x >> 31; + x ^= signum; + if (x >= (4 << 24)) { + if (x >= (17 << 23)) { + return signum ^ (1 << 24); + } + int32_t sx = ((int64_t) - 48408812 * (int64_t)x) >> 24; + return signum ^ ((1 << 24) - 2 * Exp2::lookup(sx)); + } else { + const int SHIFT = 26 - TANH_LG_N_SAMPLES; + int lowbits = x & ((1 << SHIFT) - 1); + int x_int = (x >> (SHIFT - 1)) & ((TANH_N_SAMPLES - 1) << 1); + int dy = tanhtab[x_int]; + int y0 = tanhtab[x_int + 1]; + int y = y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); + return y ^ signum; + } +} + +//===================================================== +/***************************************************** + * CODE; orig_code/fast_log.h + *****************************************************/ +/* ---------------------------------------------------------------------- +* https://community.arm.com/tools/f/discussions/4292/cmsis-dsp-new-functionality-proposal/22621#22621 +* Fast approximation to the log2() function. It uses a two step +* process. First, it decomposes the floating-point number into +* a fractional component F and an exponent E. The fraction component +* is used in a polynomial approximation and then the exponent added +* to the result. A 3rd order polynomial is used and the result +* when computing db20() is accurate to 7.984884e-003 dB. +** ------------------------------------------------------------------- */ + +static float log2f_approx_coeff[4] = {1.23149591368684f, -4.11852516267426f, 6.02197014179219f, -3.13396450166353f}; + +static float log2f_approx(float X) +{ + float *C = &log2f_approx_coeff[0]; + float Y; + float F; + int E; + + // This is the approximation to log2() + F = frexpf(fabsf(X), &E); + + // Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E; + Y = *C++; + Y *= F; + Y += (*C++); + Y *= F; + Y += (*C++); + Y *= F; + Y += (*C++); + Y += E; + return(Y); +} + +// https://codingforspeed.com/using-faster-exponential-approximation/ +inline float expf_approx(float x) { + x = 1.0f + x / 1024; + x *= x; x *= x; x *= x; x *= x; + x *= x; x *= x; x *= x; x *= x; + x *= x; x *= x; + return x; +} + +inline float unitToDb(float unit) { + return 6.02f * log2f_approx(unit); +} + +inline float dbToUnit(float db) { + return expf_approx(db * 2.302585092994046f * 0.05f); +} + +//===================================================== +/***************************************************** + * CODE; orig_code/freqlut.h + *****************************************************/ +/* + 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. +*/ + +class Freqlut { + public: + static void init(FRAC_NUM sample_rate); + static int32_t lookup(int32_t logfreq); +}; + +//===================================================== +/***************************************************** + * CODE; orig_code/lfo.h + *****************************************************/ +/* + 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 + +class Lfo { + public: + static void init(FRAC_NUM sample_rate); + void reset(const uint8_t params[6]); + + // result is 0..1 in Q24 + int32_t getsample(); + + // result is 0..1 in Q24 + int32_t getdelay(); + + void keydown(); + private: + static uint32_t unit_; + + uint32_t phase_; // Q32 + uint32_t delta_; + uint8_t waveform_; + uint8_t randstate_; + bool sync_; + + uint32_t delaystate_; + uint32_t delayinc_; + uint32_t delayinc2_; +}; + +//===================================================== +/***************************************************** + * CODE; orig_code/env.h + *****************************************************/ +/* + 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. +*/ + +// DX7 envelope generation + +#define ACCURATE_ENVELOPE + +class Env { + public: + + // The rates and levels arrays are calibrated to match the Dx7 parameters + // (ie, value 0..99). The outlevel parameter is calibrated in microsteps + // (ie units of approx .023 dB), with 99 * 32 = nominal full scale. The + // rate_scaling parameter is in qRate units (ie 0..63). + void init(const int rates[4], const int levels[4], int outlevel, + int rate_scaling); + + void update(const int rates[4], const int levels[4], int outlevel, + int rate_scaling); + // Result is in Q24/doubling log format. Also, result is subsampled + // for every N samples. + // A couple more things need to happen for this to be used as a gain + // value. First, the # of outputs scaling needs to be applied. Also, + // modulation. + // Then, of course, log to linear. + int32_t getsample(); + + void keydown(bool down); + static int scaleoutlevel(int outlevel); + void getPosition(char *step); + + static void init_sr(double sample_rate); + void transfer(Env &src); + + private: + + // PG: This code is normalized to 44100, need to put a multiplier + // if we are not using 44100. + static uint32_t sr_multiplier; + + int rates_[4]; + int levels_[4]; + int outlevel_; + int rate_scaling_; + // Level is stored so that 2^24 is one doubling, ie 16 more bits than + // the DX7 itself (fraction is stored in level rather than separate + // counter) + int32_t level_; + int targetlevel_; + bool rising_; + int ix_; + int inc_; +#ifdef ACCURATE_ENVELOPE + int staticcount_; +#endif + + bool down_; + + void advance(int newix); +}; + + +//===================================================== +/***************************************************** + * CODE; orig_code/pitchenv.h + *****************************************************/ +/* + 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. +*/ + +// Computation of the DX7 pitch envelope + +class PitchEnv { + public: + static void init(FRAC_NUM sample_rate); + + // The rates and levels arrays are calibrated to match the Dx7 parameters + // (ie, value 0..99). + void set(const int rates[4], const int levels[4]); + + // Result is in Q24/octave + int32_t getsample(); + void keydown(bool down); + void getPosition(char *step); + private: + static int unit_; + int rates_[4]; + int levels_[4]; + int32_t level_; + int targetlevel_; + bool rising_; + int ix_; + int inc_; + + bool down_; + + void advance(int newix); +}; + +extern const uint8_t pitchenv_rate[]; +extern const int8_t pitchenv_tab[]; + + +//===================================================== +/***************************************************** + * CODE; orig_code/controllers.h + *****************************************************/ +/* + 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. +*/ + +// State of MIDI controllers +const int kControllerPitch = 0; +const int kControllerPitchRange = 1; +const int kControllerPitchStep = 2; +const int kControllerPortamentoGlissando = 3; + +class FmCore; + +class FmMod { + public: + uint8_t range; + bool pitch; + bool amp; + bool eg; + uint8_t ctrl_mode; + uint8_t _dummy_; + + FmMod() + { + range = 0; + ctrl_mode = 0; + pitch = false; + amp = false; + eg = false; + } + + void setRange(uint8_t r) + { + range = r < 0 || r > 99 ? 0 : r; + } + + void setTarget(uint8_t assign) + { + assign = assign < 0 || assign > 7 ? 0 : assign; + pitch = assign & 1; // PITCH + amp = assign & 2; // AMP + eg = assign & 4; // EG + } + + void setMode(uint8_t m) + { + ctrl_mode = m > MIDI_CONTROLLER_MODE_MAX ? 0 : m; + } +}; + +class Controllers { + void applyMod(int cc, FmMod &mod) + { + uint8_t total = 0; + float range = mod.range / 100.0; + + switch (mod.ctrl_mode) + { + case 0: + total = uint8_t(float(cc) * range); // LINEAR mode + break; + case 1: + total = uint8_t(127.0 * range - (float(cc) * range)); // REVERSE mode + break; + case 2: + total = uint8_t(range * float(cc) + (1.0 - range) * 127.0); // DIRECT BC mode by Thierry (opus.quatre) + break; + } + + if (mod.amp) + amp_mod = max(amp_mod, total); + + if (mod.pitch) + pitch_mod = max(pitch_mod, total); + + if (mod.eg) + eg_mod = max(eg_mod, total); + } + + public: + int32_t values_[4]; + + uint8_t amp_mod; + uint8_t pitch_mod; + uint8_t eg_mod; + + uint8_t aftertouch_cc; + uint8_t breath_cc; + uint8_t foot_cc; + uint8_t modwheel_cc; + bool portamento_enable_cc; + int portamento_cc; + bool portamento_gliss_cc; + int masterTune; + + uint8_t opSwitch; + + FmMod wheel; + FmMod foot; + FmMod breath; + FmMod at; + + Controllers() { + amp_mod = 0; + pitch_mod = 0; + eg_mod = 0; + } + + void refresh() { + amp_mod = pitch_mod = eg_mod = 0; + + applyMod(modwheel_cc, wheel); + applyMod(breath_cc, breath); + applyMod(foot_cc, foot); + applyMod(aftertouch_cc, at); + + if ( ! ((wheel.eg || foot.eg) || (breath.eg || at.eg)) ) + eg_mod = 127; + } + + FmCore *core; +}; + + +//===================================================== +/***************************************************** + * CODE; orig_code/PluginFx.h + *****************************************************/ +/** + + Copyright (c) 2013 Pascal Gauthier. + + 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 + +*/ + +class PluginFx { + float s1, s2, s3, s4; + float sampleRate; + float sampleRateInv; + float d, c; + float R24; + float rcor24, rcor24Inv; + float bright; + + // 24 db multimode + float mm; + float mmt; + int mmch; + inline float NR24(float sample, float g, float lpc); + + // preprocess values taken the UI + float rCutoff; + float rReso; + float rGain; + + // thread values; if these are different from the UI, + // it needs to be recalculated. + float pReso; + float pCutoff; + float pGain; + + // I am still keeping the 2pole w/multimode filter + inline float NR(float sample, float g); + bool bandPassSw; + float rcor, rcorInv; + int R; + + float dc_id; + float dc_od; + float dc_r; + + public: + PluginFx(); + + // this is set directly by the ui / parameter + float Cutoff; + float Reso; + float Gain; + + void init(int sampleRate); + void process(float *work, int sampleSize); + float getGain(void); +}; + + +//===================================================== +/***************************************************** + * CODE; orig_code/fm_op_kernel.h + *****************************************************/ +/* + 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. +*/ + +struct FmOpParams { + int32_t level_in; // value to be computed (from level to gain[0]) + int32_t gain_out; // computed value (gain[1] to gain[0]) + int32_t freq; + int32_t phase; +}; + +class FmOpKernel { + public: + // gain1 and gain2 represent linear step: gain for sample i is + // gain1 + (1 + i) / 64 * (gain2 - gain1) + + // This is the basic FM operator. No feedback. + static void compute(int32_t *output, const int32_t *input, + int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, bool add); + + // This is a sine generator, no feedback. + static void compute_pure(int32_t *output, int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, bool add); + + // One op with feedback, no add. + static void compute_fb(int32_t *output, int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, + int32_t *fb_buf, int fb_gain, bool add); +}; + + +//===================================================== +/***************************************************** + * CODE; orig_code/fm_core.h + *****************************************************/ +/* + 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. +*/ + +class FmOperatorInfo { + public: + int in; + int out; +}; + +enum FmOperatorFlags { + OUT_BUS_ONE = 1 << 0, + OUT_BUS_TWO = 1 << 1, + OUT_BUS_ADD = 1 << 2, + IN_BUS_ONE = 1 << 4, + IN_BUS_TWO = 1 << 5, + FB_IN = 1 << 6, + FB_OUT = 1 << 7 +}; + +class FmAlgorithm { + public: + int ops[6]; +}; + +class FmCore { + public: + virtual ~FmCore() {}; + static void dump(); + uint8_t get_carrier_operators(uint8_t algorithm); + virtual void render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int feedback_gain); + protected: + AlignedBufbuf_[2]; + const static FmAlgorithm algorithms[32]; +}; + + +//===================================================== +/***************************************************** + * CODE; orig_code/dx7note.h + *****************************************************/ +/* + 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. +*/ + +// This is the logic to put together a note from the MIDI description +// and run the low-level modules. + +// It will continue to evolve a bit, as note-stealing logic, scaling, +// and real-time control of parameters live here. + +#pragma once + +struct VoiceStatus { + uint32_t amp[6]; + char ampStep[6]; + char pitchStep; +}; + +class Dx7Note { + public: + Dx7Note(); + void init(const uint8_t patch[156], int midinote, int velocity, int srcnote, int porta, const Controllers *ctrls); + + // Note: this _adds_ to the buffer. Interesting question whether it's + // worth it... + void compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Controllers *ctrls); + + void keyup(); + + // TODO: some way of indicating end-of-note. Maybe should be a return + // value from the compute method? (Having a count return from keyup + // is also tempting, but if there's a dynamic parameter change after + // keyup, that won't work. + + // PG:add the update + void update(const uint8_t patch[156], int midinote, int velocity, int porta, const Controllers *ctrls); + void peekVoiceStatus(VoiceStatus &status); + void transferState(Dx7Note& src); + void transferSignal(Dx7Note &src); + void transferPortamento(Dx7Note &src); + void oscSync(); + + private: + Env env_[6]; + FmOpParams params_[6]; + PitchEnv pitchenv_; + int32_t basepitch_[6]; + int32_t fb_buf_[2]; + int32_t fb_shift_; + int32_t ampmodsens_[6]; + int32_t opMode[6]; + + int ampmoddepth_; + int algorithm_; + int pitchmoddepth_; + int pitchmodsens_; + + int porta_rateindex_; + int porta_gliss_; + int32_t porta_curpitch_[6]; +}; + +//===================================================== +/***************************************************** + * CODE; orig_code/dexed.h + *****************************************************/ +/* + 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 + + 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 + +*/ + +struct ProcessorVoice { + int16_t midi_note; + uint8_t velocity; + int16_t porta; + bool keydown; + bool sustained; + bool live; + uint32_t key_pressed_timer; + Dx7Note *dx7_note; +}; + +enum DexedVoiceOPParameters { + 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 + DEXED_OP_SCL_LEFT_CURVE, // 11 + DEXED_OP_SCL_RGHT_CURVE, // 12 + DEXED_OP_OSC_RATE_SCALE, // 13 + DEXED_OP_AMP_MOD_SENS, // 14 + DEXED_OP_KEY_VEL_SENS, // 15 + DEXED_OP_OUTPUT_LEV, // 16 + DEXED_OP_OSC_MODE, // 17 + DEXED_OP_FREQ_COARSE, // 18 + DEXED_OP_FREQ_FINE, // 19 + DEXED_OP_OSC_DETUNE // 20 +}; + +#define DEXED_VOICE_OFFSET 126 +enum DexedVoiceParameters { + 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 + DEXED_ALGORITHM, // 8 + DEXED_FEEDBACK, // 9 + DEXED_OSC_KEY_SYNC, // 10 + DEXED_LFO_SPEED, // 11 + DEXED_LFO_DELAY, // 12 + DEXED_LFO_PITCH_MOD_DEP, // 13 + DEXED_LFO_AMP_MOD_DEP, // 14 + DEXED_LFO_SYNC, // 15 + DEXED_LFO_WAVE, // 16 + DEXED_LFO_PITCH_MOD_SENS, // 17 + DEXED_TRANSPOSE, // 18 + DEXED_NAME // 19 +}; + +/* #define DEXED_GLOBAL_PARAMETER_OFFSET 155 + enum DexedGlobalParameters { + DEXED_PITCHBEND_RANGE, // 0 + DEXED_PITCHBEND_STEP, // 1 + DEXED_MODWHEEL_RANGE, // 2 + DEXED_MODWHEEL_ASSIGN, // 3 + DEXED_FOOTCTRL_RANGE, // 4 + DEXED_FOOTCTRL_ASSIGN, // 5 + DEXED_BREATHCTRL_RANGE, // 6 + DEXED_BREATHCTRL_ASSIGN, // 7 + DEXED_AT_RANGE, // 8 + DEXED_AT_ASSIGN, // 9 + DEXED_MASTER_TUNE, // 10 + DEXED_OP1_ENABLE, // 11 + DEXED_OP2_ENABLE, // 12 + DEXED_OP3_ENABLE, // 13 + DEXED_OP4_ENABLE, // 14 + DEXED_OP5_ENABLE, // 15 + DEXED_OP6_ENABLE, // 16 + DEXED_MAX_NOTES, // 17 + DEXED_PORTAMENTO_MODE, // 18 + DEXED_PORTAMENTO_GLISSANDO, // 19 + DEXED_PORTAMENTO_TIME, // 20 + }; */ + +// GLOBALS + +//============================================================================== + +class Dexed +{ + public: + Dexed(int rate); + ~Dexed(); + void activate(void); + void deactivate(void); + bool isMonoMode(void); + void setMonoMode(bool mode); + void setRefreshMode(bool mode); + void getSamples(uint16_t n_samples, int16_t* buffer); + void panic(void); + void notesOff(void); + void resetControllers(void); + void setMaxNotes(uint8_t n); + uint8_t getMaxNotes(void); + void doRefreshVoice(void); + void setOPs(uint8_t ops); + bool decodeVoice(uint8_t* encoded_data, uint8_t* data); + bool encodeVoice(uint8_t* encoded_data); + bool getVoiceData(uint8_t* data_copy); + bool loadVoiceParameters(uint8_t* data); + bool loadGlobalParameters(uint8_t* data); + bool initGlobalParameters(void); + void keyup(int16_t pitch); + void keydown(int16_t pitch, uint8_t velo); + void setSustain(bool sustain); + bool getSustain(void); + uint8_t getNumNotesPlaying(void); + void setPBController(uint8_t pb_range, uint8_t pb_step); + void setMWController(uint8_t mw_range, uint8_t mw_assign, uint8_t mw_mode); + void setFCController(uint8_t fc_range, uint8_t fc_assign, uint8_t fc_mode); + void setBCController(uint8_t bc_range, uint8_t bc_assign, uint8_t bc_mode); + void setATController(uint8_t at_range, uint8_t at_assign, uint8_t at_mode); + void setPortamentoMode(uint8_t portamento_mode, uint8_t portamento_glissando, uint8_t portamento_time); + + ProcessorVoice voices[MAX_NOTES]; + Controllers controllers; + PluginFx fx; + + uint8_t data[156] = { + 95, 29, 20, 50, 99, 95, 00, 00, 41, 00, 19, 00, 00, 03, 00, 06, 79, 00, 01, 00, 14, // OP6 eg_rate_1-4, level_1-4, kbd_lev_scl_brk_pt, kbd_lev_scl_lft_depth, kbd_lev_scl_rht_depth, kbd_lev_scl_lft_curve, kbd_lev_scl_rht_curve, kbd_rate_scaling, amp_mod_sensitivity, key_vel_sensitivity, operator_output_level, osc_mode, osc_freq_coarse, osc_freq_fine, osc_detune + 95, 20, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 00, 99, 00, 01, 00, 00, // OP5 + 95, 29, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 06, 89, 00, 01, 00, 07, // OP4 + 95, 20, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 02, 99, 00, 01, 00, 07, // OP3 + 95, 50, 35, 78, 99, 75, 00, 00, 00, 00, 00, 00, 00, 03, 00, 07, 58, 00, 14, 00, 07, // OP2 + 96, 25, 25, 67, 99, 75, 00, 00, 00, 00, 00, 00, 00, 03, 00, 02, 99, 00, 01, 00, 10, // OP1 + 94, 67, 95, 60, 50, 50, 50, 50, // 4 * pitch EG rates, 4 * pitch EG level + 04, 06, 00, // algorithm, feedback, osc sync + 34, 33, 00, 00, 00, 04, // lfo speed, lfo delay, lfo pitch_mod_depth, lfo_amp_mod_depth, lfo_sync, lfo_waveform + 03, 24, // pitch_mod_sensitivity, transpose + 69, 68, 80, 56, 85, 76, 84, 00, 00, 00 // 10 * char for name ("DEFAULT ") + }; // FM-Piano + + int lastKeyDown; + + protected: + static const uint8_t MAX_ACTIVE_NOTES = MAX_NOTES; + uint8_t max_notes = MAX_ACTIVE_NOTES; + int16_t currentNote; + bool sustain; + float vuSignal; + bool monoMode; + bool refreshMode; + bool refreshVoice; + uint8_t engineType; + VoiceStatus voiceStatus; + Lfo lfo; + FmCore* engineMsfa; +}; + + +//===================================================== +/***************************************************** + * CODE; orig_code/porta.h + *****************************************************/ +/* + 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. +*/ + +struct Porta { + public: + static void init_sr(double sampleRate); + static int32_t rates[128]; +}; + + +//===================================================== +/***************************************************** + * CODE; orig_code/source_microdexed.h + *****************************************************/ + +class AudioSourceMicroDexed : public AudioStream, public Dexed { + public: + const uint16_t audio_block_time_us = 1000000 / (SAMPLE_RATE / AUDIO_BLOCK_SAMPLES); + uint32_t xrun = 0; + uint16_t render_time_max = 0; + + AudioSourceMicroDexed(int sample_rate) : AudioStream(0, NULL), Dexed(sample_rate) { }; + + void 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; + }; + + private: + volatile bool in_update = false; +}; + +//===================================================== +/***************************************************** + * CODE; orig_code/PluginFx.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/dexed.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/dx7note.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/env.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/exp2.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/fm_core.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/fm_op_kernel.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/freqlut.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/lfo.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/pitchenv.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/porta.cpp + *****************************************************/ + +//===================================================== +/***************************************************** + * CODE; orig_code/sin.cpp + *****************************************************/ + +//=====================================================