parent
1ef901f460
commit
19768a1cb6
@ -0,0 +1,51 @@ |
|||||||
|
/*
|
||||||
|
MicroMDAEPiano |
||||||
|
|
||||||
|
MicroMDAEPiano is a port of the MDA-EPiano sound engine |
||||||
|
(https://sourceforge.net/projects/mda-vst/) for the Teensy-3.5/3.6 with audio shield.
|
||||||
|
|
||||||
|
(c)2019 H. Wirtz <wirtz@parasitstudio.de> |
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation; either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, |
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
GNU General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with this program; if not, write to the Free Software Foundation, |
||||||
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||||
|
*/ |
||||||
|
|
||||||
|
// Idea from: https://playground.arduino.cc/Code/EEPROMWriteAnything/
|
||||||
|
|
||||||
|
#include <EEPROM.h> |
||||||
|
#include <Arduino.h> // for type definitions |
||||||
|
|
||||||
|
uint32_t crc32(uint8_t* calc_start, uint16_t calc_bytes); |
||||||
|
|
||||||
|
template <class T> int EEPROM_writeAnything(int ee, const T& value) |
||||||
|
{ |
||||||
|
uint8_t* p = (uint8_t*)(const void*)&value; |
||||||
|
uint16_t i; |
||||||
|
uint32_t checksum=crc32(p+4,sizeof(value)-4); |
||||||
|
|
||||||
|
*p=checksum; |
||||||
|
|
||||||
|
for (i = 0; i < sizeof(value); i++) |
||||||
|
EEPROM.update(ee++, *p++); |
||||||
|
return i; |
||||||
|
} |
||||||
|
|
||||||
|
template <class T> int EEPROM_readAnything(int ee, T& value) |
||||||
|
{ |
||||||
|
uint8_t* p = (uint8_t*)(void*)&value; |
||||||
|
unsigned int i; |
||||||
|
for (i = 0; i < sizeof(value); i++) |
||||||
|
*p++ = EEPROM.read(ee++); |
||||||
|
return i; |
||||||
|
} |
@ -1,195 +0,0 @@ |
|||||||
/*
|
|
||||||
LiquidMenuTest |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef UI_HPP_INCLUDED |
|
||||||
#define UI_HPP_INCLUDED |
|
||||||
|
|
||||||
#include <LiquidCrystal_I2C.h> |
|
||||||
#include <LiquidMenu.h> |
|
||||||
#include <Bounce.h> |
|
||||||
#include "Encoder4.h" |
|
||||||
#include "config.h" |
|
||||||
|
|
||||||
LiquidCrystal_I2C lcd(LCD_I2C_ADDRESS, LCD_CHARS, LCD_LINES); |
|
||||||
Encoder4 enc[NUM_ENCODER] = {Encoder4(ENC_L_PIN_A, ENC_L_PIN_B), Encoder4(ENC_R_PIN_A, ENC_R_PIN_B)}; |
|
||||||
int32_t encoder_value[NUM_ENCODER]; |
|
||||||
Bounce but[NUM_ENCODER] = {Bounce(BUT_L_PIN, BUT_DEBOUNCE_MS), Bounce(BUT_R_PIN, BUT_DEBOUNCE_MS)}; |
|
||||||
uint8_t main_menu_selector = 0; |
|
||||||
elapsedMillis control_rate; |
|
||||||
|
|
||||||
enum { LEFT_ENCODER, RIGHT_ENCODER }; |
|
||||||
|
|
||||||
// Recycleable objects
|
|
||||||
const char text_back[] PROGMEM = "BACK"; |
|
||||||
LiquidLine back_line(1, 1, text_back); |
|
||||||
|
|
||||||
// Main menu
|
|
||||||
const char text10[] PROGMEM = "Favorites"; |
|
||||||
const char text11[] PROGMEM = "Sound"; |
|
||||||
const char text12[] PROGMEM = "Store"; |
|
||||||
const char text13[] PROGMEM = "Info"; |
|
||||||
LiquidLine main_line1(1, 0, text10); |
|
||||||
LiquidLine main_line2(1, 1, text11); |
|
||||||
LiquidLine main_line3(1, 1, text12); |
|
||||||
LiquidLine main_line4(1, 1, text13); |
|
||||||
LiquidScreen main_screen; |
|
||||||
LiquidMenu main_menu(lcd); |
|
||||||
|
|
||||||
// Sound menu
|
|
||||||
const char text20[] PROGMEM = "Decay"; |
|
||||||
const char text21[] PROGMEM = "Release"; |
|
||||||
const char text22[] PROGMEM = "Hardness"; |
|
||||||
const char text23[] PROGMEM = "Treble"; |
|
||||||
LiquidLine sound_line1(1, 0, text20); |
|
||||||
LiquidLine sound_line2(1, 1, text21); |
|
||||||
LiquidLine sound_line3(1, 1, text22); |
|
||||||
LiquidLine sound_line4(1, 1, text23); |
|
||||||
LiquidScreen sound_screen(sound_line1, sound_line2, sound_line3, sound_line4); |
|
||||||
LiquidMenu sound_menu(lcd, sound_screen); |
|
||||||
|
|
||||||
// Info menu
|
|
||||||
const char text40[] PROGMEM = "INFO"; |
|
||||||
LiquidLine info_line1(0, 0, text40); |
|
||||||
LiquidScreen info_screen(info_line1); |
|
||||||
LiquidMenu info_menu(lcd, info_screen); |
|
||||||
|
|
||||||
// System menu
|
|
||||||
LiquidSystem menu_system(main_menu, sound_menu, info_menu); |
|
||||||
|
|
||||||
void callback_favorites_function() { |
|
||||||
Serial.println(F("callback_favorites_function")); |
|
||||||
} |
|
||||||
|
|
||||||
void callback_sound_function() { |
|
||||||
Serial.println(F("callback_sound_function")); |
|
||||||
} |
|
||||||
|
|
||||||
void callback_store_function() { |
|
||||||
Serial.println(F("callback_store_function")); |
|
||||||
} |
|
||||||
|
|
||||||
void callback_info_function() { |
|
||||||
Serial.println(F("callback_info_function")); |
|
||||||
} |
|
||||||
|
|
||||||
void callback_back_function() { |
|
||||||
Serial.println(F("callback_back_function")); |
|
||||||
} |
|
||||||
|
|
||||||
void setup(void) |
|
||||||
{ |
|
||||||
uint8_t i; |
|
||||||
|
|
||||||
Serial.begin(38400); |
|
||||||
|
|
||||||
pinMode(BUT_L_PIN, INPUT_PULLUP); |
|
||||||
pinMode(BUT_R_PIN, INPUT_PULLUP); |
|
||||||
|
|
||||||
Serial.println(F("<setup start>")); |
|
||||||
|
|
||||||
// Encoder setup
|
|
||||||
enc[0].write(INITIAL_ENC_L_VALUE); |
|
||||||
enc[1].write(INITIAL_ENC_R_VALUE); |
|
||||||
// LCD display setup
|
|
||||||
lcd.init(); |
|
||||||
lcd.blink_off(); |
|
||||||
lcd.cursor_off(); |
|
||||||
lcd.backlight(); |
|
||||||
//lcd.noAutoscroll();
|
|
||||||
|
|
||||||
main_screen.add_line(main_line1); |
|
||||||
main_screen.add_line(main_line2); |
|
||||||
main_screen.add_line(main_line3); |
|
||||||
main_screen.add_line(main_line4); |
|
||||||
main_screen.add_line(back_line); |
|
||||||
|
|
||||||
main_line1.attach_function(1, callback_favorites_function); |
|
||||||
main_line2.attach_function(2, callback_sound_function); |
|
||||||
main_line3.attach_function(3, callback_store_function); |
|
||||||
main_line4.attach_function(4, callback_info_function); |
|
||||||
back_line.attach_function(1, callback_back_function ); |
|
||||||
|
|
||||||
main_screen.set_displayLineCount(2); |
|
||||||
main_menu.add_screen(main_screen); |
|
||||||
|
|
||||||
for (i = 0; i < NUM_ENCODER; i++) |
|
||||||
but[i].update(); |
|
||||||
|
|
||||||
enc[LEFT_ENCODER].write(0, 0, 99, true); |
|
||||||
encoder_value[LEFT_ENCODER] = 0; |
|
||||||
enc[RIGHT_ENCODER].write(0, 0, 4, true); |
|
||||||
encoder_value[RIGHT_ENCODER] = 0; |
|
||||||
|
|
||||||
menu_system.update(); |
|
||||||
menu_system.set_focusPosition(Position::LEFT); |
|
||||||
menu_system.switch_focus(); |
|
||||||
} |
|
||||||
|
|
||||||
void loop(void) |
|
||||||
{ |
|
||||||
// CONTROL-RATE-EVENT-HANDLING
|
|
||||||
if (control_rate > CONTROL_RATE_MS) |
|
||||||
{ |
|
||||||
control_rate = 0; |
|
||||||
|
|
||||||
handle_ui(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void handle_ui(void) |
|
||||||
{ |
|
||||||
uint8_t i; |
|
||||||
int32_t encoder_tmp; |
|
||||||
|
|
||||||
for (i = 0; i < NUM_ENCODER; i++) |
|
||||||
{ |
|
||||||
but[i].update(); |
|
||||||
|
|
||||||
switch (i) |
|
||||||
{ |
|
||||||
case RIGHT_ENCODER: |
|
||||||
// Encoder handling
|
|
||||||
encoder_tmp = enc[RIGHT_ENCODER].read(); |
|
||||||
if (encoder_tmp > encoder_value[RIGHT_ENCODER]) |
|
||||||
{ |
|
||||||
// move down
|
|
||||||
#ifdef DEBUG |
|
||||||
Serial.println(F("ENC-R-DOWN")); |
|
||||||
#endif |
|
||||||
menu_system.switch_focus(true); |
|
||||||
if (encoder_value[RIGHT_ENCODER] == enc[RIGHT_ENCODER].read_max()) |
|
||||||
encoder_tmp = enc[RIGHT_ENCODER].read_min(); |
|
||||||
} |
|
||||||
else if (encoder_tmp < encoder_value[RIGHT_ENCODER]) |
|
||||||
{ |
|
||||||
// move up
|
|
||||||
#ifdef DEBUG |
|
||||||
Serial.println(F("ENC-R-UP")); |
|
||||||
#endif |
|
||||||
menu_system.switch_focus(false); |
|
||||||
if (encoder_value[RIGHT_ENCODER] == enc[RIGHT_ENCODER].read_min()) |
|
||||||
encoder_tmp = enc[RIGHT_ENCODER].read_max(); |
|
||||||
} |
|
||||||
encoder_value[RIGHT_ENCODER] = encoder_tmp; |
|
||||||
enc[RIGHT_ENCODER].write(encoder_value[RIGHT_ENCODER]); |
|
||||||
|
|
||||||
/*
|
|
||||||
// button handling
|
|
||||||
if (but[i].fallingEdge()) // SELECT
|
|
||||||
{ |
|
||||||
#ifdef DEBUG |
|
||||||
Serial.println(F("SELECT-R")); |
|
||||||
#endif |
|
||||||
menu_system.change_menu(sound_menu); |
|
||||||
}*/ |
|
||||||
break; |
|
||||||
case LEFT_ENCODER: |
|
||||||
// Encoder handling
|
|
||||||
|
|
||||||
// button handling
|
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
#endif |
|
@ -0,0 +1,563 @@ |
|||||||
|
/*
|
||||||
|
MicroMDAEPiano |
||||||
|
|
||||||
|
MicroMDAEPiano is a port of the MDA-EPiano sound engine |
||||||
|
(https://sourceforge.net/projects/mda-vst/) for the Teensy-3.5/3.6 with audio shield.
|
||||||
|
|
||||||
|
(c)2019 H. Wirtz <wirtz@parasitstudio.de> |
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation; either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, |
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
GNU General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with this program; if not, write to the Free Software Foundation, |
||||||
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "config.h" |
||||||
|
#include <Audio.h> |
||||||
|
#include <Wire.h> |
||||||
|
#include <SPI.h> |
||||||
|
#include <MIDI.h> |
||||||
|
#include <EEPROM.h> |
||||||
|
#include "EEPROMAnything.h" |
||||||
|
#include <limits.h> |
||||||
|
#include "mdaEPiano.h" |
||||||
|
#ifdef USE_XFADE_DATA |
||||||
|
#include "mdaEPianoDataXfade.h" |
||||||
|
#else |
||||||
|
#include "mdaEPianoData.h" |
||||||
|
#endif |
||||||
|
#include <Bounce.h> |
||||||
|
#include "Encoder4.h" |
||||||
|
#include <LiquidCrystal_I2C.h> |
||||||
|
#include <LiquidMenu.h> |
||||||
|
#include "UI.hpp" |
||||||
|
#include "midi_devices.hpp" |
||||||
|
|
||||||
|
//*************************************************************************************************
|
||||||
|
//* GLOBAL VARIABLES
|
||||||
|
//*************************************************************************************************
|
||||||
|
|
||||||
|
// Audio configuration
|
||||||
|
AudioPlayQueue queue_r; |
||||||
|
AudioPlayQueue queue_l; |
||||||
|
AudioAnalyzePeak peak_r; |
||||||
|
AudioAnalyzePeak peak_l; |
||||||
|
AudioEffectFreeverb freeverb_r; |
||||||
|
AudioEffectFreeverb freeverb_l; |
||||||
|
AudioMixer4 mixer_r; |
||||||
|
AudioMixer4 mixer_l; |
||||||
|
AudioConnection patchCord0(queue_r, peak_r); |
||||||
|
AudioConnection patchCord1(queue_l, peak_l); |
||||||
|
AudioConnection patchCord4(queue_r, freeverb_r); |
||||||
|
AudioConnection patchCord5(queue_l, freeverb_l); |
||||||
|
AudioConnection patchCord6(queue_r, 0, mixer_r, 0); |
||||||
|
AudioConnection patchCord7(queue_l, 0, mixer_l, 0); |
||||||
|
AudioConnection patchCord8(freeverb_r, 0, mixer_r, 1); |
||||||
|
AudioConnection patchCord9(freeverb_l, 0, mixer_l, 1); |
||||||
|
#if defined(TEENSY_AUDIO_BOARD) |
||||||
|
AudioOutputI2S i2s1; |
||||||
|
AudioConnection patchCord110(mixer_r, 0, i2s1, 0); |
||||||
|
AudioConnection patchCord111(mixer_l, 0, i2s1, 1); |
||||||
|
AudioControlSGTL5000 sgtl5000_1; |
||||||
|
#elif defined(TGA_AUDIO_BOARD) |
||||||
|
AudioOutputI2S i2s1; |
||||||
|
AudioAmplifier volume_r; |
||||||
|
AudioAmplifier volume_l; |
||||||
|
AudioConnection patchCord10(mixer_r, volume_r); |
||||||
|
AudioConnection patchCord11(mixer_l, volume_l); |
||||||
|
AudioConnection patchCord12(volume_r, 0, i2s1, 1); |
||||||
|
AudioConnection patchCord13(volume_l, 0, i2s1, 0); |
||||||
|
AudioControlWM8731master wm8731_1; |
||||||
|
#else |
||||||
|
AudioOutputPT8211 pt8211_1; |
||||||
|
AudioAmplifier volume_r; |
||||||
|
AudioAmplifier volume_l; |
||||||
|
AudioConnection patchCord10(mixer_r, volume_r); |
||||||
|
AudioConnection patchCord11(mixer_l, volume_l); |
||||||
|
AudioConnection patchCord12(volume_r, 0, pt8211_1, 1); |
||||||
|
AudioConnection patchCord13(volume_l, 0, pt8211_1, 0); |
||||||
|
#endif |
||||||
|
|
||||||
|
// Objects
|
||||||
|
mdaEPiano* ep; |
||||||
|
LiquidCrystal_I2C lcd(LCD_I2C_ADDRESS, LCD_CHARS, LCD_LINES); |
||||||
|
Encoder4 enc[2] = {Encoder4(ENC_L_PIN_A, ENC_L_PIN_B), Encoder4(ENC_R_PIN_A, ENC_R_PIN_B)}; |
||||||
|
Bounce but[2] = {Bounce(BUT_L_PIN, BUT_DEBOUNCE_MS), Bounce(BUT_R_PIN, BUT_DEBOUNCE_MS)}; |
||||||
|
|
||||||
|
// Variables
|
||||||
|
uint8_t midi_channel = DEFAULT_MIDI_CHANNEL; |
||||||
|
uint32_t xrun = 0; |
||||||
|
uint32_t overload = 0; |
||||||
|
uint32_t peak = 0; |
||||||
|
uint16_t render_time_max = 0; |
||||||
|
elapsedMicros fill_audio_buffer; |
||||||
|
elapsedMillis control_rate; |
||||||
|
elapsedMillis autostore; |
||||||
|
const uint16_t audio_block_time_us = 1000000 / (SAMPLE_RATE / AUDIO_BLOCK_SAMPLES); |
||||||
|
config_t configuration = {0xffff, 0, VOLUME, 0.5f, DEFAULT_MIDI_CHANNEL}; |
||||||
|
bool eeprom_update_flag = false; |
||||||
|
#ifdef SHOW_CPU_LOAD_MSEC |
||||||
|
elapsedMillis cpu_mem_millis; |
||||||
|
#endif |
||||||
|
|
||||||
|
enum MDA_EP_PARAM { DECAY, RELEASE, HARDNESS, TREBLE, PAN_TREM, LFO_RATE, VELOCITY_SENSE, STEREO, MAX_POLY, TUNE, DETUNE, OVERDRIVE }; |
||||||
|
|
||||||
|
//*************************************************************************************************
|
||||||
|
//* SETUP FUNCTION
|
||||||
|
//*************************************************************************************************
|
||||||
|
|
||||||
|
void setup() |
||||||
|
{ |
||||||
|
//while (!Serial) ; // wait for Arduino Serial Monitor
|
||||||
|
|
||||||
|
pinMode(BUT_L_PIN, INPUT_PULLUP); |
||||||
|
pinMode(BUT_R_PIN, INPUT_PULLUP); |
||||||
|
|
||||||
|
menu_init(); |
||||||
|
|
||||||
|
lcd.clear(); |
||||||
|
lcd.setCursor(1, 0); |
||||||
|
lcd.print(F("MicroMDAEpiano")); |
||||||
|
lcd.setCursor(0, 1); |
||||||
|
lcd.print(F("(c)parasiTstudio")); |
||||||
|
|
||||||
|
Serial.begin(SERIAL_SPEED); |
||||||
|
delay(500); |
||||||
|
|
||||||
|
// Encoder setup
|
||||||
|
enc[0].write(INITIAL_ENC_L_VALUE); |
||||||
|
enc[1].write(INITIAL_ENC_R_VALUE); |
||||||
|
|
||||||
|
// Debug output
|
||||||
|
Serial.println(F("MicroMDAEPiano based on https://sourceforge.net/projects/mda-vst")); |
||||||
|
Serial.println(F("(c)2018/2019 H. Wirtz <wirtz@parasitstudio.de>")); |
||||||
|
Serial.println(F("https://codeberg.org/dcoredump/MicroMDAEPiano")); |
||||||
|
Serial.print(F("Data in PROGMEM: ")); |
||||||
|
Serial.print(sizeof(epianoDataXfade), DEC); |
||||||
|
Serial.println(F(" bytes")); |
||||||
|
Serial.println(); |
||||||
|
Serial.println(F("<setup start>")); |
||||||
|
|
||||||
|
// create EPiano object
|
||||||
|
ep = new mdaEPiano(); |
||||||
|
|
||||||
|
// read initial EEPROM variables
|
||||||
|
initial_values_from_eeprom(); |
||||||
|
|
||||||
|
setup_midi_devices(); |
||||||
|
|
||||||
|
// start audio card
|
||||||
|
AudioNoInterrupts(); |
||||||
|
AudioMemory(AUDIO_MEM); |
||||||
|
|
||||||
|
#ifdef TEENSY_AUDIO_BOARD |
||||||
|
sgtl5000_1.enable(); |
||||||
|
sgtl5000_1.dacVolumeRamp(); |
||||||
|
sgtl5000_1.dacVolume(1.0); |
||||||
|
//sgtl5000_1.dacVolumeRampLinear();
|
||||||
|
sgtl5000_1.unmuteHeadphone(); |
||||||
|
sgtl5000_1.unmuteLineout(); |
||||||
|
sgtl5000_1.autoVolumeDisable(); // turn off AGC
|
||||||
|
sgtl5000_1.unmuteHeadphone(); |
||||||
|
sgtl5000_1.volume(0.5, 0.5); // Headphone volume
|
||||||
|
sgtl5000_1.lineOutLevel(SGTL5000_LINEOUT_LEVEL); |
||||||
|
sgtl5000_1.audioPostProcessorEnable(); |
||||||
|
sgtl5000_1.autoVolumeControl(1, 1, 1, 0.9, 0.01, 0.05); |
||||||
|
sgtl5000_1.autoVolumeEnable(); |
||||||
|
sgtl5000_1.surroundSoundEnable(); |
||||||
|
sgtl5000_1.surroundSound(7, 3); // Configures virtual surround width from 0 (mono) to 7 (widest). select may be set to 1 (disable), 2 (mono input) or 3 (stereo input).
|
||||||
|
sgtl5000_1.enhanceBassEnable(); |
||||||
|
sgtl5000_1.enhanceBass(1.0, 0.2, 1, 2); // Configures the bass enhancement by setting the levels of the original stereo signal and the bass-enhanced mono level which will be mixed together. The high-pass filter may be enabled (0) or bypassed (1).
|
||||||
|
/* The cutoff frequency is specified as follows:
|
||||||
|
value frequency |
||||||
|
0 80Hz |
||||||
|
1 100Hz |
||||||
|
2 125Hz |
||||||
|
3 150Hz |
||||||
|
4 175Hz |
||||||
|
5 200Hz |
||||||
|
6 225Hz |
||||||
|
*/ |
||||||
|
//sgtl5000_1.eqBands(bass, mid_bass, midrange, mid_treble, treble);
|
||||||
|
Serial.println(F("Teensy-Audio-Board enabled.")); |
||||||
|
#elif defined(TGA_AUDIO_BOARD) |
||||||
|
wm8731_1.enable(); |
||||||
|
wm8731_1.volume(1.0); |
||||||
|
Serial.println(F("TGA board enabled.")); |
||||||
|
#else |
||||||
|
Serial.println(F("PT8211 enabled.")); |
||||||
|
#endif |
||||||
|
|
||||||
|
//set_volume(vol, vol_left, vol_right);
|
||||||
|
set_volume(1.0, 0.5); |
||||||
|
|
||||||
|
#if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC) |
||||||
|
// Initialize processor and memory measurements
|
||||||
|
AudioProcessorUsageMaxReset(); |
||||||
|
AudioMemoryUsageMaxReset(); |
||||||
|
#endif |
||||||
|
|
||||||
|
Serial.print(F("AUDIO_BLOCK_SAMPLES=")); |
||||||
|
Serial.print(AUDIO_BLOCK_SAMPLES); |
||||||
|
Serial.print(F(" (Time per block=")); |
||||||
|
Serial.print(audio_block_time_us); |
||||||
|
Serial.println(F("ms)")); |
||||||
|
|
||||||
|
ep->setParameter(DECAY, 0.5); |
||||||
|
ep->setParameter(RELEASE, 0.5); |
||||||
|
ep->setParameter(HARDNESS, 0.7); |
||||||
|
ep->setParameter(TREBLE, 0.85); |
||||||
|
ep->setParameter(DETUNE, 0.1); |
||||||
|
ep->setParameter(VELOCITY_SENSE, 1.0); |
||||||
|
ep->setParameter(STEREO, 0.7); |
||||||
|
ep->setParameter(OVERDRIVE, 0.3); |
||||||
|
|
||||||
|
freeverb_r.roomsize(0.2); |
||||||
|
freeverb_l.roomsize(0.2); |
||||||
|
freeverb_r.damping(0.5); |
||||||
|
freeverb_l.damping(0.5); |
||||||
|
mixer_r.gain(0, 0.7); |
||||||
|
mixer_l.gain(0, 0.7); |
||||||
|
mixer_r.gain(1, 0.3); |
||||||
|
mixer_l.gain(1, 0.3); |
||||||
|
|
||||||
|
AudioInterrupts(); |
||||||
|
|
||||||
|
Serial.println(F("<setup end>")); |
||||||
|
|
||||||
|
menu_system.update(); |
||||||
|
menu_system.switch_focus(); |
||||||
|
|
||||||
|
#if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC) |
||||||
|
show_cpu_and_mem_usage(); |
||||||
|
cpu_mem_millis = 0; |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
//*************************************************************************************************
|
||||||
|
//* MAIN LOOP
|
||||||
|
//*************************************************************************************************
|
||||||
|
|
||||||
|
void loop() |
||||||
|
{ |
||||||
|
int16_t* audio_buffer_r; // pointer to AUDIO_BLOCK_SAMPLES * sizeof(int16_t)
|
||||||
|
int16_t* audio_buffer_l; // pointer to AUDIO_BLOCK_SAMPLES * sizeof(int16_t)
|
||||||
|
|
||||||
|
// Main sound calculation
|
||||||
|
if (queue_r.available() && queue_l.available() && fill_audio_buffer > audio_block_time_us - 10) |
||||||
|
{ |
||||||
|
fill_audio_buffer = 0; |
||||||
|
|
||||||
|
#if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC) |
||||||
|
if (cpu_mem_millis > SHOW_CPU_LOAD_MSEC) |
||||||
|
{ |
||||||
|
show_cpu_and_mem_usage(); |
||||||
|
cpu_mem_millis = 0; |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
audio_buffer_r = queue_r.getBuffer(); |
||||||
|
if (audio_buffer_r == NULL) |
||||||
|
{ |
||||||
|
Serial.println(F("E: audio_buffer_r allocation problems!")); |
||||||
|
} |
||||||
|
audio_buffer_l = queue_l.getBuffer(); |
||||||
|
if (audio_buffer_l == NULL) |
||||||
|
{ |
||||||
|
Serial.println(F("E: audio_buffer_l allocation problems!")); |
||||||
|
} |
||||||
|
|
||||||
|
elapsedMicros t1; |
||||||
|
ep->process(audio_buffer_l, audio_buffer_r); |
||||||
|
uint32_t t2 = t1; |
||||||
|
if (t2 > audio_block_time_us) // everything greater 2.9ms is a buffer underrun!
|
||||||
|
xrun++; |
||||||
|
if (t2 > render_time_max) |
||||||
|
render_time_max = t2; |
||||||
|
if (peak_r.available()) |
||||||
|
{ |
||||||
|
if (peak_r.read() > 1.00) |
||||||
|
peak++; |
||||||
|
} |
||||||
|
if (peak_l.available()) |
||||||
|
{ |
||||||
|
if (peak_l.read() > 1.00) |
||||||
|
peak++; |
||||||
|
} |
||||||
|
|
||||||
|
queue_r.playBuffer(); |
||||||
|
queue_l.playBuffer(); |
||||||
|
} |
||||||
|
|
||||||
|
check_midi_devices(); |
||||||
|
|
||||||
|
// CONTROL-RATE-EVENT-HANDLING
|
||||||
|
if (control_rate > CONTROL_RATE_MS) |
||||||
|
{ |
||||||
|
control_rate = 0; |
||||||
|
|
||||||
|
handle_ui(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//*************************************************************************************************
|
||||||
|
//* PROGRAM FUNCTIONS
|
||||||
|
//*************************************************************************************************
|
||||||
|
void handleNoteOn(byte inChannel, byte inNumber, byte inVelocity) |
||||||
|
{ |
||||||
|
if (checkMidiChannel(inChannel)) |
||||||
|
{ |
||||||
|
ep->noteOn(inNumber, inVelocity); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void handleNoteOff(byte inChannel, byte inNumber, byte inVelocity) |
||||||
|
{ |
||||||
|
if (checkMidiChannel(inChannel)) |
||||||
|
{ |
||||||
|
ep->noteOn(inNumber, 0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void handleControlChange(byte inChannel, byte inData1, byte inData2) |
||||||
|
{ |
||||||
|
if (checkMidiChannel(inChannel)) |
||||||
|
{ |
||||||
|
ep->processMidiController(inData1, inData2); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void handleAfterTouch(byte inChannel, byte inPressure) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handlePitchBend(byte inChannel, int inPitch) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleProgramChange(byte inChannel, byte inProgram) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleSystemExclusive(byte *data, uint len) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleSystemExclusiveChunk(const byte *data, uint16_t len, bool last) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleTimeCodeQuarterFrame(byte data) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleAfterTouchPoly(byte inChannel, byte inNumber, byte inVelocity) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleSongSelect(byte inSong) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleTuneRequest(void) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleClock(void) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleStart(void) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleContinue(void) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleStop(void) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleActiveSensing(void) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleSystemReset(void) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
void handleRealTimeSystem(void) |
||||||
|
{ |
||||||
|
; |
||||||
|
} |
||||||
|
|
||||||
|
bool checkMidiChannel(byte inChannel) |
||||||
|
{ |
||||||
|
// check for MIDI channel
|
||||||
|
if (midi_channel == MIDI_CHANNEL_OMNI) |
||||||
|
{ |
||||||
|
return (true); |
||||||
|
} |
||||||
|
else if (inChannel != midi_channel) |
||||||
|
{ |
||||||
|
#ifdef DEBUG |
||||||
|
Serial.print(F("Ignoring MIDI data on channel ")); |
||||||
|
Serial.print(inChannel); |
||||||
|
Serial.print(F("(listening on ")); |
||||||
|
Serial.print(midi_channel); |
||||||
|
Serial.println(F(")")); |
||||||
|
#endif |
||||||
|
return (false); |
||||||
|
} |
||||||
|
return (true); |
||||||
|
} |
||||||
|
|
||||||
|
void set_volume(float v, float p) |
||||||
|
{ |
||||||
|
configuration.vol = v; |
||||||
|
configuration.pan = p; |
||||||
|
|
||||||
|
#ifdef DEBUG |
||||||
|
Serial.print(F("Setting volume: VOL=")); |
||||||
|
Serial.print(v, DEC); |
||||||
|
Serial.print(F("[")); |
||||||
|
Serial.print(configuration.vol, DEC); |
||||||
|
Serial.print(F("] PAN=")); |
||||||
|
Serial.print(F("[")); |
||||||
|
Serial.print(configuration.pan, DEC); |
||||||
|
Serial.print(F("] ")); |
||||||
|
Serial.print(pow(configuration.vol * sinf(configuration.pan * PI / 2), VOLUME_CURVE), 3); |
||||||
|
Serial.print(F("/")); |
||||||
|
Serial.println(pow(configuration.vol * cosf( configuration.pan * PI / 2), VOLUME_CURVE), 3); |
||||||
|
#endif |
||||||
|
|
||||||
|
// http://files.csound-tutorial.net/floss_manual/Release03/Cs_FM_03_ScrapBook/b-panning-and-spatialization.html
|
||||||
|
mixer_r.gain(0, sinf(p * PI / 2)); |
||||||
|
mixer_l.gain(0, cosf(p * PI / 2)); |
||||||
|
} |
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
EEPROM HELPER |
||||||
|
******************************************************************************/ |
||||||
|
|
||||||
|
void initial_values_from_eeprom(void) |
||||||
|
{ |
||||||
|
uint32_t checksum; |
||||||
|
config_t tmp_conf; |
||||||
|
|
||||||
|
EEPROM_readAnything(EEPROM_START_ADDRESS, tmp_conf); |
||||||
|
checksum = crc32((byte*)&tmp_conf + 4, sizeof(tmp_conf) - 4); |
||||||
|
|
||||||
|
#ifdef DEBUG |
||||||
|
Serial.print(F("EEPROM checksum: 0x")); |
||||||
|
Serial.print(tmp_conf.checksum, HEX); |
||||||
|
Serial.print(F(" / 0x")); |
||||||
|
Serial.print(checksum, HEX); |
||||||
|
#endif |
||||||
|
|
||||||
|
if (checksum != tmp_conf.checksum) |
||||||
|
{ |
||||||
|
#ifdef DEBUG |
||||||
|
Serial.print(F(" - mismatch -> initializing EEPROM!")); |
||||||
|
#endif |
||||||
|
eeprom_update(); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
EEPROM_readAnything(EEPROM_START_ADDRESS, configuration); |
||||||
|
Serial.print(F(" - OK, loading!")); |
||||||
|
} |
||||||
|
#ifdef DEBUG |
||||||
|
Serial.println(); |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
void eeprom_write(void) |
||||||
|
{ |
||||||
|
autostore = 0; |
||||||
|
eeprom_update_flag = true; |
||||||
|
} |
||||||
|
|
||||||
|
void eeprom_update(void) |
||||||
|
{ |
||||||
|
eeprom_update_flag = false; |
||||||
|
configuration.checksum = crc32((byte*)&configuration + 4, sizeof(configuration) - 4); |
||||||
|
EEPROM_writeAnything(EEPROM_START_ADDRESS, configuration); |
||||||
|
Serial.println(F("Updating EEPROM with configuration data")); |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t crc32(byte * calc_start, uint16_t calc_bytes) // base code from https://www.arduino.cc/en/Tutorial/EEPROMCrc
|
||||||
|
{ |
||||||
|
const uint32_t crc_table[16] = |
||||||
|
{ |
||||||
|
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, |
||||||
|
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, |
||||||
|
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, |
||||||
|
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c |
||||||
|
}; |
||||||
|
uint32_t crc = ~0L; |
||||||
|
|
||||||
|
for (byte* index = calc_start ; index < (calc_start + calc_bytes) ; ++index) |
||||||
|
{ |
||||||
|
crc = crc_table[(crc ^ *index) & 0x0f] ^ (crc >> 4); |
||||||
|
crc = crc_table[(crc ^ (*index >> 4)) & 0x0f] ^ (crc >> 4); |
||||||
|
crc = ~crc; |
||||||
|
} |
||||||
|
|
||||||
|
return (crc); |
||||||
|
} |
||||||
|
|
||||||
|
//*************************************************************************************************
|
||||||
|
//* DEBUG FUNCTIONS
|
||||||
|
//*************************************************************************************************
|
||||||
|
|
||||||
|
#if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC) |
||||||
|
void show_cpu_and_mem_usage(void) |
||||||
|
{ |
||||||
|
Serial.print(F("CPU: ")); |
||||||
|
Serial.print(AudioProcessorUsage(), DEC); |
||||||
|
Serial.print(F(" CPU MAX: ")); |
||||||
|
Serial.print(AudioProcessorUsageMax(), DEC); |
||||||
|
Serial.print(F(" MEM: ")); |
||||||
|
Serial.print(AudioMemoryUsage(), DEC); |
||||||
|
Serial.print(F(" MEM MAX: ")); |
||||||
|
Serial.print(AudioMemoryUsageMax(), DEC); |
||||||
|
Serial.print(F(" RENDER_TIME_MAX: ")); |
||||||
|
Serial.print(render_time_max, DEC); |
||||||
|
Serial.print(F(" XRUN: ")); |
||||||
|
Serial.print(xrun, DEC); |
||||||
|
Serial.print(F(" OVERLOAD: ")); |
||||||
|
Serial.print(overload, DEC); |
||||||
|
Serial.print(F(" PEAK: ")); |
||||||
|
Serial.print(peak, DEC); |
||||||
|
Serial.println(); |
||||||
|
AudioProcessorUsageMaxReset(); |
||||||
|
AudioMemoryUsageMaxReset(); |
||||||
|
render_time_max = 0; |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,145 @@ |
|||||||
|
/*
|
||||||
|
MicroMDAEPiano |
||||||
|
|
||||||
|
MicroMDAEPiano is a port of the MDA-EPiano sound engine |
||||||
|
(https://sourceforge.net/projects/mda-vst/) for the Teensy-3.5/3.6 with audio shield.
|
||||||
|
|
||||||
|
(c)2019 H. Wirtz <wirtz@parasitstudio.de> |
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation; either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, |
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
GNU General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with this program; if not, write to the Free Software Foundation, |
||||||
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef UI_HPP_INCLUDED |
||||||
|
#define UI_HPP_INCLUDED |
||||||
|
|
||||||
|
#include <LiquidCrystal_I2C.h> |
||||||
|
#include <LiquidMenu.h> |
||||||
|
#include <Bounce.h> |
||||||
|
#include "Encoder4.h" |
||||||
|
|
||||||
|
extern LiquidCrystal_I2C lcd; |
||||||
|
extern Encoder4 enc[2]; |
||||||
|
extern Bounce but[2]; |
||||||
|
|
||||||
|
// Global vars
|
||||||
|
uint8_t main_menu_selector = 0; |
||||||
|
|
||||||
|
enum { BUT_L, BUT_R }; |
||||||
|
|
||||||
|
// Main menu
|
||||||
|
const char text10[] PROGMEM = "Favorites "; |
||||||
|
const char text11[] PROGMEM = "Sound "; |
||||||
|
const char text12[] PROGMEM = "Store "; |
||||||
|
const char text13[] PROGMEM = "Info "; |
||||||
|
LiquidLine main_line1(0, 0, text10); |
||||||
|
LiquidLine main_line2(0, 1, text11); |
||||||
|
LiquidLine main_line3(0, 1, text12); |
||||||
|
LiquidLine main_line4(0, 1, text13); |
||||||
|
LiquidScreen main_screen; |
||||||
|
LiquidMenu main_menu(lcd); |
||||||
|
|
||||||
|
// Sound menu
|
||||||
|
const char text20[] PROGMEM = "Decay "; |
||||||
|
const char text21[] PROGMEM = "Release "; |
||||||
|
const char text22[] PROGMEM = "Hardness "; |
||||||
|
const char text23[] PROGMEM = "Treble "; |
||||||
|
LiquidLine sound_line1(0, 0, text20); |
||||||
|
LiquidLine sound_line2(0, 1, text21); |
||||||
|
LiquidLine sound_line3(0, 1, text22); |
||||||
|
LiquidLine sound_line4(0, 1, text23); |
||||||
|
LiquidScreen sound_screen(sound_line1, sound_line2, sound_line3, sound_line4); |
||||||
|
LiquidMenu sound_menu(lcd, sound_screen); |
||||||
|
|
||||||
|
// Info menu
|
||||||
|
const char text40[] PROGMEM = "INFO "; |
||||||
|
LiquidLine info_line1(0, 0, text40); |
||||||
|
LiquidScreen info_screen(info_line1); |
||||||
|
LiquidMenu info_menu(lcd, info_screen); |
||||||
|
|
||||||
|
// System menu
|
||||||
|
LiquidSystem menu_system(main_menu, sound_menu, info_menu); |
||||||
|
|
||||||
|
void callback_favorites_function() { |
||||||
|
Serial.println(F("callback_favorites_function")); |
||||||
|
} |
||||||
|
|
||||||
|
void callback_sound_function() { |
||||||
|
Serial.println(F("callback_sound_function")); |
||||||
|
} |
||||||
|
|
||||||
|
void callback_store_function() { |
||||||
|
Serial.println(F("callback_store_function")); |
||||||
|
} |
||||||
|
|
||||||
|
void callback_info_function() { |
||||||
|
Serial.println(F("callback_info_function")); |
||||||
|
} |
||||||
|
|
||||||
|
void menu_init(void) |
||||||
|
{ |
||||||
|
uint8_t i; |
||||||
|
|
||||||
|
// LCD display setup
|
||||||
|
lcd.init(); |
||||||
|
lcd.blink_off(); |
||||||
|
lcd.cursor_off(); |
||||||
|
lcd.backlight(); |
||||||
|
//lcd.noAutoscroll();
|
||||||
|
|
||||||
|
main_screen.add_line(main_line1); |
||||||
|
main_screen.add_line(main_line2); |
||||||
|
main_screen.add_line(main_line3); |
||||||
|
main_screen.add_line(main_line4); |
||||||
|
|
||||||
|
main_line1.attach_function(1, callback_favorites_function); |
||||||
|
main_line2.attach_function(2, callback_sound_function); |
||||||
|
main_line3.attach_function(3, callback_store_function); |
||||||
|
main_line4.attach_function(4, callback_info_function); |
||||||
|
|
||||||
|
main_screen.set_displayLineCount(2); |
||||||
|
main_menu.add_screen(main_screen); |
||||||
|
|
||||||
|
menu_system.update(); |
||||||
|
|
||||||
|
for (i = 0; i < NUM_ENCODER; i++) |
||||||
|
but[i].update(); |
||||||
|
|
||||||
|
enc[1].write(0, 0, 99); |
||||||
|
enc[1].write(1, 0, 4); |
||||||
|
} |
||||||
|
|
||||||
|
void handle_ui(void) |
||||||
|
{ |
||||||
|
uint8_t i; |
||||||
|
|
||||||
|
for (i = 0; i < NUM_ENCODER; i++) |
||||||
|
{ |
||||||
|
but[i].update(); |
||||||
|
|
||||||
|
switch (i) |
||||||
|
{ |
||||||
|
case BUT_R: // SELECT
|
||||||
|
if (but[i].fallingEdge()) |
||||||
|
{ |
||||||
|
#ifdef DEBUG |
||||||
|
Serial.println(F("SELECT-R")); |
||||||
|
#endif |
||||||
|
menu_system.next_screen(); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,50 @@ |
|||||||
|
/* ----------------------------------------------------------------------
|
||||||
|
* 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); |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,370 @@ |
|||||||
|
/*
|
||||||
|
MicroMDAEPiano |
||||||
|
|
||||||
|
MicroMDAEPiano is a port of the MDA-EPiano sound engine |
||||||
|
(https://sourceforge.net/projects/mda-vst/) for the Teensy-3.5/3.6 with audio shield.
|
||||||
|
|
||||||
|
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" |
||||||
|
#ifdef USE_XFADE_DATA |
||||||
|
#include "mdaEPianoDataXfade.h" |
||||||
|
#else |
||||||
|
#include "mdaEPianoData.h" |
||||||
|
#endif |
||||||
|
#include "mdaEPiano.h" |
||||||
|
#include <stdio.h> |
||||||
|
#include <math.h> |
||||||
|
|
||||||
|
mdaEPiano::mdaEPiano() // mdaEPiano::mdaEPiano(audioMasterCallback audioMaster) : AudioEffectX(audioMaster, NPROGS, NPARAMS)
|
||||||
|
{ |
||||||
|
Fs = SAMPLE_RATE; iFs = 1.0f / Fs; //just in case...
|
||||||
|
|
||||||
|
programs = new mdaEPianoProgram[NPROGS]; |
||||||
|
if (programs) |
||||||
|
{ |
||||||
|
//fill patches...
|
||||||
|
int32_t i = 0; |
||||||
|
fillpatch(i++, "Default", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f); |
||||||
|
fillpatch(i++, "Bright", 0.500f, 0.500f, 1.000f, 0.800f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.500f); |
||||||
|
fillpatch(i++, "Mellow", 0.500f, 0.500f, 0.000f, 0.000f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.246f, 0.000f); |
||||||
|
fillpatch(i++, "Autopan", 0.500f, 0.500f, 0.500f, 0.500f, 0.250f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.246f, 0.000f); |
||||||
|
fillpatch(i++, "Tremolo", 0.500f, 0.500f, 0.500f, 0.500f, 0.750f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.246f, 0.000f); |
||||||
|
fillpatch(i++, "(default)", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f); |
||||||
|
fillpatch(i++, "(default)", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f); |
||||||
|
fillpatch(i++, "(default)", 0.500f, 0.500f, 0.500f, 0.500f, 0.500f, 0.650f, 0.250f, 0.500f, 1.0f, 0.500f, 0.146f, 0.000f); |
||||||
|
setProgram(0); |
||||||
|
} |
||||||
|
|
||||||
|
waves = (short*)epianoDataXfade; |
||||||
|
|
||||||
|
//Waveform data and keymapping
|
||||||
|
kgrp[ 0].root = 36; kgrp[ 0].high = 39; //C1
|
||||||
|
kgrp[ 3].root = 43; kgrp[ 3].high = 45; //G1
|
||||||
|
kgrp[ 6].root = 48; kgrp[ 6].high = 51; //C2
|
||||||
|
kgrp[ 9].root = 55; kgrp[ 9].high = 57; //G2
|
||||||
|
kgrp[12].root = 60; kgrp[12].high = 63; //C3
|
||||||
|
kgrp[15].root = 67; kgrp[15].high = 69; //G3
|
||||||
|
kgrp[18].root = 72; kgrp[18].high = 75; //C4
|
||||||
|
kgrp[21].root = 79; kgrp[21].high = 81; //G4
|
||||||
|
kgrp[24].root = 84; kgrp[24].high = 87; //C5
|
||||||
|
kgrp[27].root = 91; kgrp[27].high = 93; //G5
|
||||||
|
kgrp[30].root = 96; kgrp[30].high = 999; //C6
|
||||||
|
|
||||||
|
kgrp[0].pos = 0; kgrp[0].end = 8476; kgrp[0].loop = 4400; |
||||||
|
kgrp[1].pos = 8477; kgrp[1].end = 16248; kgrp[1].loop = 4903; |
||||||
|
kgrp[2].pos = 16249; kgrp[2].end = 34565; kgrp[2].loop = 6398; |
||||||
|
kgrp[3].pos = 34566; kgrp[3].end = 41384; kgrp[3].loop = 3938; |
||||||
|
kgrp[4].pos = 41385; kgrp[4].end = 45760; kgrp[4].loop = 1633; //was 1636;
|
||||||
|
kgrp[5].pos = 45761; kgrp[5].end = 65211; kgrp[5].loop = 5245; |
||||||
|
kgrp[6].pos = 65212; kgrp[6].end = 72897; kgrp[6].loop = 2937; |
||||||
|
kgrp[7].pos = 72898; kgrp[7].end = 78626; kgrp[7].loop = 2203; //was 2204;
|
||||||
|
kgrp[8].pos = 78627; kgrp[8].end = 100387; kgrp[8].loop = 6368; |
||||||
|
kgrp[9].pos = 100388; kgrp[9].end = 116297; kgrp[9].loop = 10452; |
||||||
|
kgrp[10].pos = 116298; kgrp[10].end = 127661; kgrp[10].loop = 5217; //was 5220;
|
||||||
|
kgrp[11].pos = 127662; kgrp[11].end = 144113; kgrp[11].loop = 3099; |
||||||
|
kgrp[12].pos = 144114; kgrp[12].end = 152863; kgrp[12].loop = 4284; |
||||||
|
kgrp[13].pos = 152864; kgrp[13].end = 173107; kgrp[13].loop = 3916; |
||||||
|
kgrp[14].pos = 173108; kgrp[14].end = 192734; kgrp[14].loop = 2937; |
||||||
|
kgrp[15].pos = 192735; kgrp[15].end = 204598; kgrp[15].loop = 4732; |
||||||
|
kgrp[16].pos = 204599; kgrp[16].end = 218995; kgrp[16].loop = 4733; |
||||||
|
kgrp[17].pos = 218996; kgrp[17].end = 233801; kgrp[17].loop = 2285; |
||||||
|
kgrp[18].pos = 233802; kgrp[18].end = 248011; kgrp[18].loop = 4098; |
||||||
|
kgrp[19].pos = 248012; kgrp[19].end = 265287; kgrp[19].loop = 4099; |
||||||
|
kgrp[20].pos = 265288; kgrp[20].end = 282255; kgrp[20].loop = 3609; |
||||||
|
kgrp[21].pos = 282256; kgrp[21].end = 293776; kgrp[21].loop = 2446; |
||||||
|
kgrp[22].pos = 293777; kgrp[22].end = 312566; kgrp[22].loop = 6278; |
||||||
|
kgrp[23].pos = 312567; kgrp[23].end = 330200; kgrp[23].loop = 2283; |
||||||
|
kgrp[24].pos = 330201; kgrp[24].end = 348889; kgrp[24].loop = 2689; |
||||||
|
kgrp[25].pos = 348890; kgrp[25].end = 365675; kgrp[25].loop = 4370; |
||||||
|
kgrp[26].pos = 365676; kgrp[26].end = 383661; kgrp[26].loop = 5225; |
||||||
|
kgrp[27].pos = 383662; kgrp[27].end = 393372; kgrp[27].loop = 2811; |
||||||
|
kgrp[28].pos = 383662; kgrp[28].end = 393372; kgrp[28].loop = 2811; //ghost
|
||||||
|
kgrp[29].pos = 393373; kgrp[29].end = 406045; kgrp[29].loop = 4522; |
||||||
|
kgrp[30].pos = 406046; kgrp[30].end = 414486; kgrp[30].loop = 2306; |
||||||
|
kgrp[31].pos = 406046; kgrp[31].end = 414486; kgrp[31].loop = 2306; //ghost
|
||||||
|
kgrp[32].pos = 414487; kgrp[32].end = 422408; kgrp[32].loop = 2169; |
||||||
|
|
||||||
|
//initialise...
|
||||||
|
for (int32_t v = 0; v < NVOICES; v++) |
||||||
|
{ |
||||||
|
voice[v].env = 0.0f; |
||||||
|
voice[v].dec = 0.99f; //all notes off
|
||||||
|
} |
||||||
|
volume = 0.2f; |
||||||
|
muff = 160.0f; |
||||||
|
sustain = activevoices = 0; |
||||||
|
tl = tr = lfo0 = dlfo = 0.0f; |
||||||
|
lfo1 = 1.0f; |
||||||
|
|
||||||
|
guiUpdate = 0; |
||||||
|
|
||||||
|
vol = VOLUME; |
||||||
|
update(); |
||||||
|
// suspend();
|
||||||
|
} |
||||||
|
|
||||||
|
void mdaEPiano::update() //parameter change
|
||||||
|
{ |
||||||
|
float * param = programs[curProgram].param; |
||||||
|
size = (int32_t)(12.0f * param[2] - 6.0f); |
||||||
|
|
||||||
|
treb = 4.0f * param[3] * param[3] - 1.0f; //treble gain
|
||||||
|
if (param[3] > 0.5f) tfrq = 14000.0f; else tfrq = 5000.0f; //treble freq
|
||||||
|
tfrq = 1.0f - (float)exp(-iFs * tfrq); |
||||||
|
|
||||||
|
rmod = lmod = param[4] + param[4] - 1.0f; //lfo depth
|
||||||
|
if (param[4] < 0.5f) rmod = -rmod; |
||||||
|
|
||||||
|
dlfo = 6.283f * iFs * (float)exp(6.22f * param[5] - 2.61f); //lfo rate
|
||||||
|
|
||||||
|
velsens = 1.0f + param[6] + param[6]; |
||||||
|
if (param[6] < 0.25f) velsens -= 0.75f - 3.0f * param[6]; |
||||||
|
|
||||||
|
width = 0.03f * param[7]; |
||||||
|
fine = param[9] - 0.5f; |
||||||
|
random = 0.077f * param[10] * param[10]; |
||||||
|
stretch = 0.0f; //0.000434f * (param[11] - 0.5f); parameter re-used for overdrive!
|
||||||
|
overdrive = 1.8f * param[11]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void mdaEPiano::resume() |
||||||
|
{ |
||||||
|
Fs = SAMPLE_RATE; |
||||||
|
iFs = 1.0f / Fs; |
||||||
|
dlfo = 6.283f * iFs * (float)exp(6.22f * programs[curProgram].param[5] - 2.61f); //lfo rate
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
mdaEPiano::~mdaEPiano () //destroy any buffers...
|
||||||
|
{ |
||||||
|
if (programs) delete [] programs; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void mdaEPiano::setProgram(int32_t program) |
||||||
|
{ |
||||||
|
curProgram = program; |
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void mdaEPiano::setParameter(int32_t index, float value) |
||||||
|
{ |
||||||
|
programs[curProgram].param[index] = value; |
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void mdaEPiano::fillpatch(int32_t p, char *name, float p0, float p1, float p2, float p3, float p4, |
||||||
|
float p5, float p6, float p7, float p8, float p9, float p10, float p11) |
||||||
|
{ |
||||||
|
strcpy(programs[p].name, name); |
||||||
|
programs[p].param[0] = p0; programs[p].param[1] = p1; |
||||||
|
programs[p].param[2] = p2; programs[p].param[3] = p3; |
||||||
|
programs[p].param[4] = p4; programs[p].param[5] = p5; |
||||||
|
programs[p].param[6] = p6; programs[p].param[7] = p7; |
||||||
|
programs[p].param[8] = p8; programs[p].param[9] = p9; |
||||||
|
programs[p].param[10] = p10; programs[p].param[11] = p11; |
||||||
|
} |
||||||
|
|
||||||
|
float mdaEPiano::getParameter(int32_t index) { |
||||||
|
return programs[curProgram].param[index]; |
||||||
|
} |
||||||
|
|
||||||
|
void mdaEPiano::process(int16_t* outputs_r, int16_t* outputs_l) |
||||||
|
{ |
||||||
|
int16_t v; |
||||||
|
float x, l, r, od = overdrive; |
||||||
|
int32_t i; |
||||||
|
int16_t frame; |
||||||
|
|
||||||
|
for (frame = 0; frame < AUDIO_BLOCK_SAMPLES; frame++) |
||||||
|
{ |
||||||
|
VOICE *V = voice; |
||||||
|
l = r = 0.0f; |
||||||
|
for (v = 0; v < activevoices; v++) |
||||||
|
{ |
||||||
|
V->frac += V->delta; //integer-based linear interpolation
|
||||||
|
V->pos += V->frac >> 16; |
||||||
|
V->frac &= 0xFFFF; |
||||||
|
if (V->pos > V->end) |
||||||
|
V->pos -= V->loop; |
||||||
|
i = waves[V->pos] + ((V->frac * (waves[V->pos + 1] - waves[V->pos])) >> 16); |
||||||
|
x = V->env * static_cast<float>(i) / 32768.0f; |
||||||
|
|
||||||
|
V->env = V->env * V->dec; //envelope
|
||||||
|
if (x > 0.0f) |
||||||
|
{ |
||||||
|
x -= od * x * x; //overdrive
|
||||||
|
if (x < -V->env) x = -V->env; |
||||||
|
} |
||||||
|
l += V->outl * x; |
||||||
|
r += V->outr * x; |
||||||
|
|
||||||
|
V++; |
||||||
|
} |
||||||
|
|
||||||
|
tl += tfrq * (l - tl); //treble boost
|
||||||
|
tr += tfrq * (r - tr); |
||||||
|
r += treb * (r - tr); |
||||||
|
l += treb * (l - tl); |
||||||
|
|
||||||
|
lfo0 += dlfo * lfo1; //LFO for tremolo and autopan
|
||||||
|
lfo1 -= dlfo * lfo0; |
||||||
|
l += l * lmod * lfo1; |
||||||
|
r += r * rmod * lfo1; //worth making all these local variables?
|
||||||
|
|
||||||
|
r *= 0.5; |
||||||
|
l *= 0.5; |
||||||
|
|
||||||
|
if (r > 1.0) |
||||||
|
r = 1.0; |
||||||
|
else if (r < -1.0) |
||||||
|
r = -1.0; |
||||||
|
if (l > 1.0) |
||||||
|
l = 1.0; |
||||||
|
else if (l < -1.0) |
||||||
|
l = -1.0; |
||||||
|
outputs_l[frame] = static_cast<int16_t>(l * configuration.vol * 0x7fff) >> REDUCE_LOUDNESS; |
||||||
|
outputs_r[frame] = static_cast<int16_t>(r * configuration.vol * 0x7fff) >> REDUCE_LOUDNESS; |
||||||
|
} |
||||||
|
|
||||||
|
if (fabs(tl) < 1.0e-10) tl = 0.0f; //anti-denormal
|
||||||
|
if (fabs(tr) < 1.0e-10) tr = 0.0f; |
||||||
|
|
||||||
|
for (v = 0; v < activevoices; v++) |
||||||
|
if (voice[v].env < SILENCE) |
||||||
|
voice[v] = voice[--activevoices]; |
||||||
|
} |
||||||
|
|
||||||
|
void mdaEPiano::noteOn(int32_t note, int32_t velocity) |
||||||
|
{ |
||||||
|
float * param = programs[curProgram].param; |
||||||
|
float l = 99.0f; |
||||||
|
int32_t v, vl = 0, k, s; |
||||||
|
|
||||||
|
if (velocity > 0) |
||||||
|
{ |
||||||
|
if (activevoices < NVOICES) //add a note
|
||||||
|
{ |
||||||
|
vl = activevoices; |
||||||
|
activevoices++; |
||||||
|
voice[vl].f0 = voice[vl].f1 = 0.0f; |
||||||
|
} |
||||||
|
else //steal a note
|
||||||
|
{ |
||||||
|
for (v = 0; v < NVOICES; v++) //find quietest voice
|
||||||
|
{ |
||||||
|
if (voice[v].env < l) { |
||||||
|
l = voice[v].env; |
||||||
|
vl = v; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
k = (note - 60) * (note - 60); |
||||||
|
l = fine + random * ((float)(k % 13) - 6.5f); //random & fine tune
|
||||||
|
if (note > 60) l += stretch * (float)k; //stretch
|
||||||
|
|
||||||
|
s = size; |
||||||
|
//if(velocity > 40) s += (int32_t)(sizevel * (float)(velocity - 40)); - no velocity to hardness in ePiano
|
||||||
|
|
||||||
|
k = 0; |
||||||
|
while (note > (kgrp[k].high + s)) k += 3; //find keygroup
|
||||||
|
l += (float)(note - kgrp[k].root); //pitch
|
||||||
|
l = 32000.0f * iFs * (float)exp(0.05776226505 * l); |
||||||
|
voice[vl].delta = (int32_t)(65536.0f * l); |
||||||
|
voice[vl].frac = 0; |
||||||
|
|
||||||
|
if (velocity > 48) k++; //mid velocity sample
|
||||||
|
if (velocity > 80) k++; //high velocity sample
|
||||||
|
voice[vl].pos = kgrp[k].pos; |
||||||
|
voice[vl].end = kgrp[k].end - 1; |
||||||
|
voice[vl].loop = kgrp[k].loop; |
||||||
|
|
||||||
|
voice[vl].env = (3.0f + 2.0f * velsens) * (float)pow(0.0078f * velocity, velsens); //velocity
|
||||||
|
|
||||||
|
if (note > 60) voice[vl].env *= (float)exp(0.01f * (float)(60 - note)); //new! high notes quieter
|
||||||
|
|
||||||
|
l = 50.0f + param[4] * param[4] * muff + muffvel * (float)(velocity - 64); //muffle
|
||||||
|
if (l < (55.0f + 0.4f * (float)note)) l = 55.0f + 0.4f * (float)note; |
||||||
|
if (l > 210.0f) l = 210.0f; |
||||||
|
voice[vl].ff = l * l * iFs; |
||||||
|
|
||||||
|
voice[vl].note = note; //note->pan
|
||||||
|
if (note < 12) note = 12; |
||||||
|
if (note > 108) note = 108; |
||||||
|
l = volume; |
||||||
|
voice[vl].outr = l + l * width * (float)(note - 60); |
||||||
|
voice[vl].outl = l + l - voice[vl].outr; |
||||||
|
|
||||||
|
if (note < 44) note = 44; //limit max decay length
|
||||||
|
voice[vl].dec = (float)exp(-iFs * exp(-1.0 + 0.03 * (double)note - 2.0f * param[0])); |
||||||
|
} |
||||||
|
else //note off
|
||||||
|
{ |
||||||
|
for (v = 0; v < NVOICES; v++) if (voice[v].note == note) //any voices playing that note?
|
||||||
|
{ |
||||||
|
if (sustain == 0) |
||||||
|
{ |
||||||
|
voice[v].dec = (float)exp(-iFs * exp(6.0 + 0.01 * (double)note - 5.0 * param[1])); |
||||||
|
} |
||||||
|
else voice[v].note = SUSTAIN; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bool mdaEPiano::processMidiController(uint8_t data1, uint8_t data2) |
||||||
|
{ |
||||||
|
float* param = programs[curProgram].param; |
||||||
|
|
||||||
|
switch (data1) |
||||||
|
{ |
||||||
|
case 0x01: //mod wheel
|
||||||
|
modwhl = 0.0078f * (float)(data2); |
||||||
|
if (modwhl > 0.05f) //over-ride pan/trem depth
|
||||||
|
{ |
||||||
|
rmod = lmod = modwhl; //lfo depth
|
||||||
|
if (param[4] < 0.5f) rmod = -rmod; |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case 0x07: //volume
|
||||||
|
volume = 0.00002f * (float)(data2 * data2); |
||||||
|
break; |
||||||
|
|
||||||
|
case 0x40: //sustain pedal
|
||||||
|
case 0x42: //sustenuto pedal
|
||||||
|
sustain = data2 & 0x40; |
||||||
|
if (sustain == 0) |
||||||
|
{ |
||||||
|
noteOn(SUSTAIN, 0); //end all sustained notes
|
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
default: //all notes off
|
||||||
|
if (data1 > 0x7A) |
||||||
|
{ |
||||||
|
for (int32_t v = 0; v < NVOICES; v++) voice[v].dec = 0.99f; |
||||||
|
sustain = 0; |
||||||
|
muff = 160.0f; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
return (true); |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
/*
|
||||||
|
MicroMDAEPiano |
||||||
|
|
||||||
|
MicroMDAEPiano is a port of the MDA-EPiano sound engine |
||||||
|
(https://sourceforge.net/projects/mda-vst/) for the Teensy-3.5/3.6 with audio shield.
|
||||||
|
|
||||||
|
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 __mdaEPiano__ |
||||||
|
#define __mdaEPiano__ |
||||||
|
|
||||||
|
#include <Audio.h> |
||||||
|
#include <Arduino.h> |
||||||
|
#include <string.h> |
||||||
|
#include "config.h" |
||||||
|
|
||||||
|
#define NPARAMS 12 //number of parameters
|
||||||
|
#define NPROGS 8 //number of programs
|
||||||
|
#define NOUTS 2 //number of outputs
|
||||||
|
#define SUSTAIN 128 |
||||||
|
#define SILENCE 0.0001f //voice choking
|
||||||
|
#define WAVELEN 422414 //wave data bytes
|
||||||
|
|
||||||
|
extern config_t configuration; |
||||||
|
|
||||||
|
class mdaEPianoProgram |
||||||
|
{ |
||||||
|
friend class mdaEPiano; |
||||||
|
private: |
||||||
|
float param[NPARAMS]; |
||||||
|
char name[24]; |
||||||
|
}; |
||||||
|
|
||||||
|
struct VOICE //voice state
|
||||||
|
{ |
||||||
|
int32_t delta; //sample playback
|
||||||
|
int32_t frac; |
||||||
|
int32_t pos; |
||||||
|
int32_t end; |
||||||
|
int32_t loop; |
||||||
|
|
||||||
|
float env; //envelope
|
||||||
|
float dec; |
||||||
|
|
||||||
|
float f0; //first-order LPF
|
||||||
|
float f1; |
||||||
|
float ff; |
||||||
|
|
||||||
|
float outl; |
||||||
|
float outr; |
||||||
|
int32_t note; //remember what note triggered this
|
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
struct KGRP //keygroup
|
||||||
|
{ |
||||||
|
int32_t root; //MIDI root note
|
||||||
|
int32_t high; //highest note
|
||||||
|
int32_t pos; |
||||||
|
int32_t end; |
||||||
|
int32_t loop; |
||||||
|
}; |
||||||
|
|
||||||
|
class mdaEPiano |
||||||
|
{ |
||||||
|
public: |
||||||
|
mdaEPiano(); |
||||||
|
~mdaEPiano(); |
||||||
|
|
||||||
|
virtual void process(int16_t *outputs_r, int16_t *outputs_l); |
||||||
|
void noteOn(int32_t note, int32_t velocity); |
||||||
|
virtual bool processMidiController(uint8_t data1, uint8_t data2); |
||||||
|
virtual void setProgram(int32_t program); |
||||||
|
virtual void setParameter(int32_t index, float value); |
||||||
|
virtual float getParameter(int32_t index); |
||||||
|
virtual void resume(); |
||||||
|
|
||||||
|
int32_t guiUpdate; |
||||||
|
void guiGetDisplay(int32_t index, char *label); |
||||||
|
|
||||||
|
private: |
||||||
|
void update(); //my parameter update
|
||||||
|
void fillpatch(int32_t p, char *name, float p0, float p1, float p2, float p3, float p4, |
||||||
|
float p5, float p6, float p7, float p8, float p9, float p10, float p11); |
||||||
|
|
||||||
|
mdaEPianoProgram* programs; |
||||||
|
float Fs, iFs; |
||||||
|
|
||||||
|
///global internal variables
|
||||||
|
KGRP kgrp[34]; |
||||||
|
VOICE voice[NVOICES]; |
||||||
|
int32_t activevoices; |
||||||
|
short *waves; |
||||||
|
float width; |
||||||
|
int32_t size, sustain; |
||||||
|
float lfo0, lfo1, dlfo, lmod, rmod; |
||||||
|
float treb, tfrq, tl, tr; |
||||||
|
float tune, fine, random, stretch, overdrive; |
||||||
|
float muff, muffvel, sizevel, velsens, volume, modwhl; |
||||||
|
float vol; |
||||||
|
uint8_t curProgram; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,120 @@ |
|||||||
|
/*
|
||||||
|
MicroMDAEPiano |
||||||
|
|
||||||
|
MicroMDAEPiano is a port of the MDA-EPiano sound engine |
||||||
|
(https://sourceforge.net/projects/mda-vst/) for the Teensy-3.5/3.6 with audio shield.
|
||||||
|
|
||||||
|
(c)2019 H. Wirtz <wirtz@parasitstudio.de> |
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation; either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, |
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
GNU General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with this program; if not, write to the Free Software Foundation, |
||||||
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||||
|
*/ |
||||||
|
|
||||||
|
/*************************************************
|
||||||
|
* MIDI note values |
||||||
|
*************************************************/ |
||||||
|
|
||||||
|
#ifndef _MIDINOTES_H |
||||||
|
#define _MIDINOTES_H |
||||||
|
|
||||||
|
#define MIDI_A0 21 |
||||||
|
#define MIDI_AIS0 22 |
||||||
|
#define MIDI_B0 23 |
||||||
|
#define MIDI_C1 24 |
||||||
|
#define MIDI_CIS1 25 |
||||||
|
#define MIDI_D1 26 |
||||||
|
#define MIDI_DIS1 27 |
||||||
|
#define MIDI_E1 28 |
||||||
|
#define MIDI_F1 29 |
||||||
|
#define MIDI_FIS1 30 |
||||||
|
#define MIDI_G1 31 |
||||||
|
#define MIDI_GIS1 32 |
||||||
|
#define MIDI_A1 33 |
||||||
|
#define MIDI_AIS1 34 |
||||||
|
#define MIDI_B1 35 |
||||||
|
#define MIDI_C2 36 |
||||||
|
#define MIDI_CIS2 37 |
||||||
|
#define MIDI_D2 38 |
||||||
|
#define MIDI_DIS2 39 |
||||||
|
#define MIDI_E2 40 |
||||||
|
#define MIDI_F2 41 |
||||||
|
#define MIDI_FIS2 42 |
||||||
|
#define MIDI_G2 43 |
||||||
|
#define MIDI_GIS2 44 |
||||||
|
#define MIDI_A2 45 |
||||||
|
#define MIDI_AIS2 46 |
||||||
|
#define MIDI_B2 47 |
||||||
|
#define MIDI_C3 48 |
||||||
|
#define MIDI_CIS3 49 |
||||||
|
#define MIDI_D3 50 |
||||||
|
#define MIDI_DIS3 51 |
||||||
|
#define MIDI_E3 52 |
||||||
|
#define MIDI_F3 53 |
||||||
|
#define MIDI_FIS3 54 |
||||||
|
#define MIDI_G3 55 |
||||||
|
#define MIDI_GIS3 56 |
||||||
|
#define MIDI_A3 57 |
||||||
|
#define MIDI_AIS3 58 |
||||||
|
#define MIDI_B3 59 |
||||||
|
#define MIDI_C4 60 |
||||||
|
#define MIDI_CIS4 61 |
||||||
|
#define MIDI_D4 62 |
||||||
|
#define MIDI_DIS4 63 |
||||||
|
#define MIDI_E4 64 |
||||||
|
#define MIDI_F4 65 |
||||||
|
#define MIDI_FIS4 66 |
||||||
|
#define MIDI_G4 67 |
||||||
|
#define MIDI_GIS4 68 |
||||||
|
#define MIDI_A4 69 |
||||||
|
#define MIDI_AIS4 70 |
||||||
|
#define MIDI_B4 71 |
||||||
|
#define MIDI_C5 72 |
||||||
|
#define MIDI_CIS5 73 |
||||||
|
#define MIDI_D5 74 |
||||||
|
#define MIDI_DIS5 75 |
||||||
|
#define MIDI_E5 76 |
||||||
|
#define MIDI_F5 77 |
||||||
|
#define MIDI_FIS5 78 |
||||||
|
#define MIDI_G5 79 |
||||||
|
#define MIDI_GIS5 80 |
||||||
|
#define MIDI_A5 81 |
||||||
|
#define MIDI_AIS5 82 |
||||||
|
#define MIDI_B5 83 |
||||||
|
#define MIDI_C6 84 |
||||||
|
#define MIDI_CIS6 85 |
||||||
|
#define MIDI_D6 86 |
||||||
|
#define MIDI_DIS6 87 |
||||||
|
#define MIDI_E6 88 |
||||||
|
#define MIDI_F6 89 |
||||||
|
#define MIDI_FIS6 90 |
||||||
|
#define MIDI_G6 91 |
||||||
|
#define MIDI_GIS6 92 |
||||||
|
#define MIDI_A6 93 |
||||||
|
#define MIDI_AIS6 94 |
||||||
|
#define MIDI_B6 95 |
||||||
|
#define MIDI_C7 96 |
||||||
|
#define MIDI_CIS7 97 |
||||||
|
#define MIDI_D7 98 |
||||||
|
#define MIDI_DIS7 99 |
||||||
|
#define MIDI_E7 100 |
||||||
|
#define MIDI_F7 101 |
||||||
|
#define MIDI_FIS7 102 |
||||||
|
#define MIDI_G7 103 |
||||||
|
#define MIDI_GIS7 104 |
||||||
|
#define MIDI_A7 105 |
||||||
|
#define MIDI_AIS7 106 |
||||||
|
#define MIDI_B7 107 |
||||||
|
#define MIDI_C8 108 |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,11 @@ |
|||||||
|
#include "usb_names.h" |
||||||
|
#define MIDI_NAME {'M','i','c','r','o','M','D','A','E','p','i','a','n','o'} |
||||||
|
#define MIDI_NAME_LEN 14 |
||||||
|
|
||||||
|
// Do not change this part. This exact format is required by USB.
|
||||||
|
|
||||||
|
struct usb_string_descriptor_struct usb_string_product_name = { |
||||||
|
2 + MIDI_NAME_LEN * 2, |
||||||
|
3, |
||||||
|
MIDI_NAME |
||||||
|
}; |
@ -0,0 +1,9 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
sed -e 's/^.\+Arduino\.h.\+$/#include <stdint.h>/' ../mdaEPianoData.h >mdaEPianoData.h |
||||||
|
sed -i 's/PROGMEM//' mdaEPianoData.h |
||||||
|
sed -i 's/const//' mdaEPianoData.h |
||||||
|
gcc -o xfade_generator xfade_generator.c |
||||||
|
rm mdaEPianoData.h |
||||||
|
./xfade_generator >../mdaEPianoData_xfade.h |
||||||
|
rm xfade_generator |
@ -0,0 +1,100 @@ |
|||||||
|
#include "mdaEPianoData.h" |
||||||
|
#include <stdio.h> |
||||||
|
#include <math.h> |
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
struct KGRP //keygroup
|
||||||
|
{ |
||||||
|
int32_t root; //MIDI root note
|
||||||
|
int32_t high; //highest note
|
||||||
|
int32_t pos; |
||||||
|
int32_t end; |
||||||
|
int32_t loop; |
||||||
|
}; |
||||||
|
|
||||||
|
void main(void) |
||||||
|
{ |
||||||
|
struct KGRP kgrp[34]; |
||||||
|
int16_t *waves = (int16_t*)epianoData; |
||||||
|
|
||||||
|
//Waveform data and keymapping
|
||||||
|
kgrp[ 0].root = 36; kgrp[ 0].high = 39; //C1
|
||||||
|
kgrp[ 3].root = 43; kgrp[ 3].high = 45; //G1
|
||||||
|
kgrp[ 6].root = 48; kgrp[ 6].high = 51; //C2
|
||||||
|
kgrp[ 9].root = 55; kgrp[ 9].high = 57; //G2
|
||||||
|
kgrp[12].root = 60; kgrp[12].high = 63; //C3
|
||||||
|
kgrp[15].root = 67; kgrp[15].high = 69; //G3
|
||||||
|
kgrp[18].root = 72; kgrp[18].high = 75; //C4
|
||||||
|
kgrp[21].root = 79; kgrp[21].high = 81; //G4
|
||||||
|
kgrp[24].root = 84; kgrp[24].high = 87; //C5
|
||||||
|
kgrp[27].root = 91; kgrp[27].high = 93; //G5
|
||||||
|
kgrp[30].root = 96; kgrp[30].high = 999; //C6
|
||||||
|
|
||||||
|
kgrp[0].pos = 0; kgrp[0].end = 8476; kgrp[0].loop = 4400; |
||||||
|
kgrp[1].pos = 8477; kgrp[1].end = 16248; kgrp[1].loop = 4903; |
||||||
|
kgrp[2].pos = 16249; kgrp[2].end = 34565; kgrp[2].loop = 6398; |
||||||
|
kgrp[3].pos = 34566; kgrp[3].end = 41384; kgrp[3].loop = 3938; |
||||||
|
kgrp[4].pos = 41385; kgrp[4].end = 45760; kgrp[4].loop = 1633; //was 1636;
|
||||||
|
kgrp[5].pos = 45761; kgrp[5].end = 65211; kgrp[5].loop = 5245; |
||||||
|
kgrp[6].pos = 65212; kgrp[6].end = 72897; kgrp[6].loop = 2937; |
||||||
|
kgrp[7].pos = 72898; kgrp[7].end = 78626; kgrp[7].loop = 2203; //was 2204;
|
||||||
|
kgrp[8].pos = 78627; kgrp[8].end = 100387; kgrp[8].loop = 6368; |
||||||
|
kgrp[9].pos = 100388; kgrp[9].end = 116297; kgrp[9].loop = 10452; |
||||||
|
kgrp[10].pos = 116298; kgrp[10].end = 127661; kgrp[10].loop = 5217; //was 5220;
|
||||||
|
kgrp[11].pos = 127662; kgrp[11].end = 144113; kgrp[11].loop = 3099; |
||||||
|
kgrp[12].pos = 144114; kgrp[12].end = 152863; kgrp[12].loop = 4284; |
||||||
|
kgrp[13].pos = 152864; kgrp[13].end = 173107; kgrp[13].loop = 3916; |
||||||
|
kgrp[14].pos = 173108; kgrp[14].end = 192734; kgrp[14].loop = 2937; |
||||||
|
kgrp[15].pos = 192735; kgrp[15].end = 204598; kgrp[15].loop = 4732; |
||||||
|
kgrp[16].pos = 204599; kgrp[16].end = 218995; kgrp[16].loop = 4733; |
||||||
|
kgrp[17].pos = 218996; kgrp[17].end = 233801; kgrp[17].loop = 2285; |
||||||
|
kgrp[18].pos = 233802; kgrp[18].end = 248011; kgrp[18].loop = 4098; |
||||||
|
kgrp[19].pos = 248012; kgrp[19].end = 265287; kgrp[19].loop = 4099; |
||||||
|
kgrp[20].pos = 265288; kgrp[20].end = 282255; kgrp[20].loop = 3609; |
||||||
|
kgrp[21].pos = 282256; kgrp[21].end = 293776; kgrp[21].loop = 2446; |
||||||
|
kgrp[22].pos = 293777; kgrp[22].end = 312566; kgrp[22].loop = 6278; |
||||||
|
kgrp[23].pos = 312567; kgrp[23].end = 330200; kgrp[23].loop = 2283; |
||||||
|
kgrp[24].pos = 330201; kgrp[24].end = 348889; kgrp[24].loop = 2689; |
||||||
|
kgrp[25].pos = 348890; kgrp[25].end = 365675; kgrp[25].loop = 4370; |
||||||
|
kgrp[26].pos = 365676; kgrp[26].end = 383661; kgrp[26].loop = 5225; |
||||||
|
kgrp[27].pos = 383662; kgrp[27].end = 393372; kgrp[27].loop = 2811; |
||||||
|
kgrp[28].pos = 383662; kgrp[28].end = 393372; kgrp[28].loop = 2811; //ghost
|
||||||
|
kgrp[29].pos = 393373; kgrp[29].end = 406045; kgrp[29].loop = 4522; |
||||||
|
kgrp[30].pos = 406046; kgrp[30].end = 414486; kgrp[30].loop = 2306; |
||||||
|
kgrp[31].pos = 406046; kgrp[31].end = 414486; kgrp[31].loop = 2306; //ghost
|
||||||
|
kgrp[32].pos = 414487; kgrp[32].end = 422408; kgrp[32].loop = 2169; |
||||||
|
|
||||||
|
//extra xfade looping...
|
||||||
|
for (int32_t k = 0; k < 28; k++) |
||||||
|
{ |
||||||
|
int32_t p0 = kgrp[k].end; |
||||||
|
int32_t p1 = kgrp[k].end - kgrp[k].loop; |
||||||
|
float xf = 1.0f; |
||||||
|
float dxf = -0.02f; |
||||||
|
|
||||||
|
while (xf > 0.0f) |
||||||
|
{ |
||||||
|
waves[p0] = (int16_t)((1.0f - xf) * (float)waves[p0] + xf * (float)waves[p1]); |
||||||
|
|
||||||
|
p0--; |
||||||
|
p1--; |
||||||
|
xf += dxf; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// output
|
||||||
|
printf("#include <Arduino.h>\n\n"); |
||||||
|
printf("const int16_t epianoDataXfade[] PROGMEM = {\n"); |
||||||
|
for(uint32_t z=0; z<(844836/sizeof(int16_t)); z=z+20) |
||||||
|
{ |
||||||
|
for(uint32_t i=0;i<20;i++) |
||||||
|
{ |
||||||
|
if(z+i>=(uint32_t)(844836/sizeof(int16_t))) |
||||||
|
break;
|
||||||
|
printf("%d,",waves[z+i]); |
||||||
|
if(i>=19) |
||||||
|
printf("\n"); |
||||||
|
} |
||||||
|
} |
||||||
|
printf("\n};\n"); |
||||||
|
} |
Loading…
Reference in new issue