/*
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 "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 ;
AudioAmplifier volume_r ;
AudioAmplifier volume_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 ) ;
AudioConnection patchCord10 ( mixer_r , volume_r ) ;
AudioConnection patchCord11 ( mixer_l , volume_l ) ;
# if defined(TEENSY_AUDIO_BOARD)
AudioOutputI2S i2s1 ;
AudioConnection patchCord12 ( volume_r , 0 , i2s1 , 0 ) ;
AudioConnection patchCord13 ( volume_l , 0 , i2s1 , 1 ) ;
AudioControlSGTL5000 sgtl5000_1 ;
# elif defined(TGA_AUDIO_BOARD)
AudioOutputI2S i2s1 ;
AudioConnection patchCord12 ( volume_r , 0 , i2s1 , 1 ) ;
AudioConnection patchCord13 ( volume_l , 0 , i2s1 , 0 ) ;
AudioControlWM8731master wm8731_1 ;
# else
AudioOutputPT8211 pt8211_1 ;
AudioConnection patchCord12 ( volume_r , 0 , pt8211_1 , 1 ) ;
AudioConnection patchCord13 ( volume_l , 0 , pt8211_1 , 0 ) ;
# endif
// Objects
mdaEPiano * ep ;
extern void init_menus ( void ) ;
extern int32_t encoder_value [ NUM_ENCODER ] ;
// more variables
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 , // checksum
1 , // sound
ENC_DECAY_DEFAULT , // decay
ENC_RELEASE_DEFAULT , // release
ENC_HARDNESS_DEFAULT , // hardness
ENC_TREBLE_DEFAULT , // treble
ENC_STEREO_DEFAULT , // stereo
ENC_TRANSPOSE_DEFAULT , // transpose
ENC_TUNE_DEFAULT , // tune
ENC_DETUNE_DEFAULT , // detune
ENC_VELOCITY_SENSE_DEFAULT , // velocity_sense
ENC_PAN_TREM_FREQUENCY_DEFAULT , // pan_trem_frequency
ENC_PAN_TREM_LEVEL_DEFAULT , // pan_trem_level
ENC_OVERDRIVE_DEFAULT , // overdrive
ENC_COMP_GAIN_DEFAULT , // comp_gain
ENC_COMP_RESPONSE_DEFAULT , // comp_response
ENC_COMP_LIMIT_DEFAULT , // comp_limit
ENC_COMP_THRESHOLD_DEFAULT , // comp_threshold
ENC_COMP_ATTACK_DEFAULT , // comp_attack
ENC_COMP_DECAY_DEFAULT , // comp_decay
ENC_REVERB_ROOMSIZE_DEFAULT , // reverb_roomsize
ENC_REVERB_DAMPING_DEFAULT , // reverb_damping
ENC_REVERB_LEVEL_DEFAULT , // reverb_level
ENC_CHORUS_FREQUENCY_DEFAULT , // chorus_frequency
ENC_CHORUS_DELAY_DEFAULT , // chorus_delay
ENC_CHORUS_LEVEL_DEFAULT , // chorus_level
ENC_BASS_LR_LEVEL_DEFAULT , // bass_lr_level
ENC_BASS_MONO_LEVEL_DEFAULT , // bass_mono_level
ENC_EQ_BASS_DEFAULT , // eq_bass
ENC_EQ_TREBLE_DEFAULT , // eq_treble
ENC_LOUDNESS_DEFAULT , // loudness
ENC_MIDI_CHANNEL_DEFAULT , // midi_channel
ENC_MIDI_SOFT_THRU_DEFAULT , // midi_soft_thru
ENC_MAX_POLY_DEFAULT , // max_poly
0 // pan
} ;
float _loudness = mapfloat ( float ( ENC_LOUDNESS_DEFAULT ) , ENC_LOUDNESS_MIN , ENC_LOUDNESS_MAX , 0.0 , 1.0 ) ;
bool eeprom_update_flag = false ;
# ifdef SHOW_CPU_LOAD_MSEC
elapsedMillis cpu_mem_millis ;
# endif
//*************************************************************************************************
//* SETUP FUNCTION
//*************************************************************************************************
void setup ( )
{
Serial . begin ( SERIAL_SPEED ) ;
pinMode ( BUT_L_PIN , INPUT_PULLUP ) ;
pinMode ( BUT_R_PIN , INPUT_PULLUP ) ;
init_menus ( ) ;
// 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 . volume ( 0.5 , 0.5 ) ; // Headphone volume
sgtl5000_1 . unmuteLineout ( ) ;
//sgtl5000_1.autoVolumeDisable(); // turn off AGC
sgtl5000_1 . lineOutLevel ( SGTL5000_LINEOUT_LEVEL ) ;
sgtl5000_1 . audioPostProcessorEnable ( ) ;
sgtl5000_1 . eqSelect ( TONE_CONTROLS ) ;
//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 80 Hz
1 100 Hz
2 125 Hz
3 150 Hz
4 175 Hz
5 200 Hz
6 225 Hz
*/
//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_master_volume ( master_volume ) ;
# 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) " ) ) ;
set_complete_configuration ( ) ;
AudioInterrupts ( ) ;
Serial . println ( F ( " <setup end> " ) ) ;
# 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 + configuration . transpose , inVelocity ) ;
}
}
void handleNoteOff ( byte inChannel , byte inNumber , byte inVelocity )
{
if ( checkMidiChannel ( inChannel ) )
{
ep - > noteOn ( inNumber + configuration . transpose , 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 ( configuration . midi_channel = = MIDI_CHANNEL_OMNI )
{
return ( true ) ;
}
else if ( inChannel ! = configuration . midi_channel )
{
# ifdef DEBUG
Serial . print ( F ( " Ignoring MIDI data on channel " ) ) ;
Serial . print ( inChannel ) ;
Serial . print ( F ( " (listening on " ) ) ;
Serial . print ( configuration . midi_channel ) ;
Serial . println ( F ( " ) " ) ) ;
# endif
return ( false ) ;
}
return ( true ) ;
}
void set_master_volume ( uint8_t value )
{
configuration . pan = 0 ; // BAD HACK!
uint16_t tmp = map ( value , ENC_MASTER_VOLUME_MIN , ENC_MASTER_VOLUME_MAX , 0 , 0x3ff ) ;
float tmp2 = mapfloat ( configuration . pan , ENC_MASTER_PAN_MIN , ENC_MASTER_PAN_MAX , 0.0 , 1.0 ) ;
float tmp3 = ( float ) ( tmp * ( tmp + 2 ) ) / ( float ) ( 1 < < 20 ) ;
# ifdef DEBUG
Serial . print ( F ( " Setting volume: VOL= " ) ) ;
Serial . print ( value , DEC ) ;
Serial . print ( F ( " [ " ) ) ;
Serial . print ( tmp3 , 3 ) ;
Serial . print ( F ( " ] PAN= " ) ) ;
Serial . print ( configuration . pan , DEC ) ;
Serial . print ( F ( " [ " ) ) ;
Serial . print ( tmp2 , 3 ) ;
Serial . print ( F ( " ] " ) ) ;
Serial . print ( tmp3 * sinf ( tmp2 * PI / 2 ) , 3 ) ;
Serial . print ( F ( " / " ) ) ;
Serial . println ( tmp3 * cosf ( tmp2 * PI / 2 ) , 3 ) ;
# endif
// float v = (float)(a * (a + 2))/(float)(1 << 20); // (pseudo-) logarithmic curve for volume control
// http://files.csound-tutorial.net/floss_manual/Release03/Cs_FM_03_ScrapBook/b-panning-and-spatialization.html
mixer_r . gain ( 0 , tmp3 * sinf ( tmp2 * PI / 2 ) ) ;
mixer_l . gain ( 0 , tmp3 * cosf ( tmp2 * 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