2021-02-14 14:40:38 -08:00
/* Copyright 2016-2020 Jack Humbert
* Copyright 2020 JohSchneider
* 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 2 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, see <http://www.gnu.org/licenses/>.
*/
#include "audio.h"
#include "eeconfig.h"
#include "timer.h"
#include "wait.h"
/* audio system:
*
* audio.[ch] takes care of all overall state, tracking the actively playing
* notes/tones; the notes a SONG consists of;
* ...
* = everything audio-related that is platform agnostic
*
* driver_[avr|chibios]_[dac|pwm] take care of the lower hardware dependent parts,
* specific to each platform and the used subsystem/driver to drive
* the output pins/channels with the calculated frequencies for each
* active tone
* as part of this, the driver has to trigger regular state updates by
* calling 'audio_update_state' through some sort of timer - be it a
* dedicated one or piggybacking on for example the timer used to
* generate a pwm signal/clock.
*
*
* A Note on terminology:
* tone, pitch and frequency are used somewhat interchangeably, in a strict Wikipedia-sense:
* "(Musical) tone, a sound characterized by its duration, pitch (=frequency),
* intensity (=volume), and timbre"
* - intensity/volume is currently not handled at all, although the 'dac_additive' driver could do so
* - timbre is handled globally (TODO: only used with the pwm drivers at the moment)
*
* in musical_note.h a 'note' is the combination of a pitch and a duration
* these are used to create SONG arrays; during playback their frequencies
* are handled as single successive tones, while the durations are
* kept track of in 'audio_update_state'
*
* 'voice' as it is used here, equates to a sort of instrument with its own
* characteristics sound and effects
* the audio system as-is deals only with (possibly multiple) tones of one
* instrument/voice at a time (think: chords). since the number of tones that
* can be reproduced depends on the hardware/driver in use: pwm can only
* reproduce one tone per output/speaker; DACs can reproduce/mix multiple
* when doing additive synthesis.
*
* 'duration' can either be in the beats-per-minute related unit found in
* musical_notes.h, OR in ms; keyboards create SONGs with the former, while
* the internal state of the audio system does its calculations with the later - ms
*/
#ifndef AUDIO_TONE_STACKSIZE
# define AUDIO_TONE_STACKSIZE 8
#endif
2022-02-12 10:29:31 -08:00
uint8_t active_tones = 0 ; // number of tones pushed onto the stack by audio_play_tone - might be more than the hardware is able to reproduce at any single time
musical_tone_t tones [ AUDIO_TONE_STACKSIZE ]; // stack of currently active tones
2021-02-14 14:40:38 -08:00
2022-02-12 10:29:31 -08:00
bool playing_melody = false ; // playing a SONG?
bool playing_note = false ; // or (possibly multiple simultaneous) tones
bool state_changed = false ; // global flag, which is set if anything changes with the active_tones
2021-02-14 14:40:38 -08:00
// melody/SONG related state variables
2022-02-12 10:29:31 -08:00
float ( * notes_pointer )[][ 2 ]; // SONG, an array of MUSICAL_NOTEs
uint16_t notes_count ; // length of the notes_pointer array
bool notes_repeat ; // PLAY_SONG or PLAY_LOOP?
uint16_t melody_current_note_duration = 0 ; // duration of the currently playing note from the active melody, in ms
uint8_t note_tempo = TEMPO_DEFAULT ; // beats-per-minute
uint16_t current_note = 0 ; // index into the array at notes_pointer
bool note_resting = false ; // if a short pause was introduced between two notes with the same frequency while playing a melody
2021-02-14 14:40:38 -08:00
uint16_t last_timestamp = 0 ;
#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING
# ifndef AUDIO_MAX_SIMULTANEOUS_TONES
# define AUDIO_MAX_SIMULTANEOUS_TONES 3
# endif
uint16_t tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT ;
2022-02-12 10:29:31 -08:00
uint8_t tone_multiplexing_index_shift = 0 ; // offset used on active-tone array access
2021-02-14 14:40:38 -08:00
#endif
// provided and used by voices.c
extern uint8_t note_timbre ;
extern bool glissando ;
extern bool vibrato ;
extern uint16_t voices_timer ;
#ifndef STARTUP_SONG
# define STARTUP_SONG SONG(STARTUP_SOUND)
#endif
#ifndef AUDIO_ON_SONG
# define AUDIO_ON_SONG SONG(AUDIO_ON_SOUND)
#endif
#ifndef AUDIO_OFF_SONG
# define AUDIO_OFF_SONG SONG(AUDIO_OFF_SOUND)
#endif
float startup_song [][ 2 ] = STARTUP_SONG ;
float audio_on_song [][ 2 ] = AUDIO_ON_SONG ;
float audio_off_song [][ 2 ] = AUDIO_OFF_SONG ;
static bool audio_initialized = false ;
static bool audio_driver_stopped = true ;
audio_config_t audio_config ;
void audio_init () {
if ( audio_initialized ) {
return ;
}
// Check EEPROM
#ifdef EEPROM_ENABLE
if ( ! eeconfig_is_enabled ()) {
eeconfig_init ();
}
audio_config . raw = eeconfig_read_audio ();
2022-02-12 10:29:31 -08:00
#else // EEPROM settings
2021-02-14 14:40:38 -08:00
audio_config . enable = true ;
# ifdef AUDIO_CLICKY_ON
audio_config . clicky_enable = true ;
# endif
2022-02-12 10:29:31 -08:00
#endif // EEPROM settings
2021-02-14 14:40:38 -08:00
for ( uint8_t i = 0 ; i < AUDIO_TONE_STACKSIZE ; i ++ ) {
tones [ i ] = ( musical_tone_t ){. time_started = 0 , . pitch = - 1.0f , . duration = 0 };
}
if ( ! audio_initialized ) {
audio_driver_initialize ();
audio_initialized = true ;
}
stop_all_notes ();
2021-08-15 13:51:50 -07:00
#ifndef AUDIO_INIT_DELAY
audio_startup ();
#endif
2021-02-14 14:40:38 -08:00
}
void audio_startup ( void ) {
if ( audio_config . enable ) {
PLAY_SONG ( startup_song );
}
last_timestamp = timer_read ();
}
void audio_toggle ( void ) {
if ( audio_config . enable ) {
stop_all_notes ();
}
audio_config . enable ^= 1 ;
eeconfig_update_audio ( audio_config . raw );
if ( audio_config . enable ) {
audio_on_user ();
2021-12-12 01:58:25 +01:00
} else {
audio_off_user ();
2021-02-14 14:40:38 -08:00
}
}
void audio_on ( void ) {
audio_config . enable = 1 ;
eeconfig_update_audio ( audio_config . raw );
audio_on_user ();
PLAY_SONG ( audio_on_song );
}
void audio_off ( void ) {
PLAY_SONG ( audio_off_song );
2021-12-12 01:58:25 +01:00
audio_off_user ();
2021-02-14 14:40:38 -08:00
wait_ms ( 100 );
audio_stop_all ();
audio_config . enable = 0 ;
eeconfig_update_audio ( audio_config . raw );
}
2022-02-12 10:29:31 -08:00
bool audio_is_on ( void ) {
return ( audio_config . enable != 0 );
}
2021-02-14 14:40:38 -08:00
void audio_stop_all () {
if ( audio_driver_stopped ) {
return ;
}
active_tones = 0 ;
audio_driver_stop ();
playing_melody = false ;
playing_note = false ;
melody_current_note_duration = 0 ;
for ( uint8_t i = 0 ; i < AUDIO_TONE_STACKSIZE ; i ++ ) {
tones [ i ] = ( musical_tone_t ){. time_started = 0 , . pitch = - 1.0f , . duration = 0 };
}
audio_driver_stopped = true ;
}
void audio_stop_tone ( float pitch ) {
if ( pitch < 0.0f ) {
pitch = - 1 * pitch ;
}
if ( playing_note ) {
if ( ! audio_initialized ) {
audio_init ();
}
bool found = false ;
for ( int i = AUDIO_TONE_STACKSIZE - 1 ; i >= 0 ; i -- ) {
found = ( tones [ i ]. pitch == pitch );
if ( found ) {
tones [ i ] = ( musical_tone_t ){. time_started = 0 , . pitch = - 1.0f , . duration = 0 };
for ( int j = i ; ( j < AUDIO_TONE_STACKSIZE - 1 ); j ++ ) {
tones [ j ] = tones [ j + 1 ];
tones [ j + 1 ] = ( musical_tone_t ){. time_started = 0 , . pitch = - 1.0f , . duration = 0 };
}
break ;
}
}
if ( ! found ) {
return ;
}
state_changed = true ;
active_tones -- ;
if ( active_tones < 0 ) active_tones = 0 ;
#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING
if ( tone_multiplexing_index_shift >= active_tones ) {
tone_multiplexing_index_shift = 0 ;
}
#endif
if ( active_tones == 0 ) {
audio_driver_stop ();
audio_driver_stopped = true ;
playing_note = false ;
}
}
}
void audio_play_note ( float pitch , uint16_t duration ) {
if ( ! audio_config . enable ) {
return ;
}
if ( ! audio_initialized ) {
audio_init ();
}
if ( pitch < 0.0f ) {
pitch = - 1 * pitch ;
}
// round-robin: shifting out old tones, keeping only unique ones
// if the new frequency is already amongst the active tones, shift it to the top of the stack
bool found = false ;
for ( int i = active_tones - 1 ; i >= 0 ; i -- ) {
found = ( tones [ i ]. pitch == pitch );
if ( found ) {
for ( int j = i ; ( j < active_tones - 1 ); j ++ ) {
tones [ j ] = tones [ j + 1 ];
tones [ j + 1 ] = ( musical_tone_t ){. time_started = timer_read (), . pitch = pitch , . duration = duration };
}
2022-02-12 10:29:31 -08:00
return ; // since this frequency played already, the hardware was already started
2021-02-14 14:40:38 -08:00
}
}
// frequency/tone is actually new, so we put it on the top of the stack
active_tones ++ ;
if ( active_tones > AUDIO_TONE_STACKSIZE ) {
active_tones = AUDIO_TONE_STACKSIZE ;
// shift out the oldest tone to make room
for ( int i = 0 ; i < active_tones - 1 ; i ++ ) {
tones [ i ] = tones [ i + 1 ];
}
}
state_changed = true ;
playing_note = true ;
tones [ active_tones - 1 ] = ( musical_tone_t ){. time_started = timer_read (), . pitch = pitch , . duration = duration };
// TODO: needs to be handled per note/tone -> use its timestamp instead?
2022-02-12 10:29:31 -08:00
voices_timer = timer_read (); // reset to zero, for the effects added by voices.c
2021-02-14 14:40:38 -08:00
if ( audio_driver_stopped ) {
audio_driver_start ();
audio_driver_stopped = false ;
}
}
2022-02-12 10:29:31 -08:00
void audio_play_tone ( float pitch ) {
audio_play_note ( pitch , 0xffff );
}
2021-02-14 14:40:38 -08:00
void audio_play_melody ( float ( * np )[][ 2 ], uint16_t n_count , bool n_repeat ) {
if ( ! audio_config . enable ) {
audio_stop_all ();
return ;
}
if ( ! audio_initialized ) {
audio_init ();
}
// Cancel note if a note is playing
if ( playing_note ) audio_stop_all ();
playing_melody = true ;
note_resting = false ;
notes_pointer = np ;
notes_count = n_count ;
notes_repeat = n_repeat ;
2022-02-12 10:29:31 -08:00
current_note = 0 ; // note in the melody-array/list at note_pointer
2021-02-14 14:40:38 -08:00
// start first note manually, which also starts the audio_driver
// all following/remaining notes are played by 'audio_update_state'
audio_play_note (( * notes_pointer )[ current_note ][ 0 ], audio_duration_to_ms (( * notes_pointer )[ current_note ][ 1 ]));
last_timestamp = timer_read ();
melody_current_note_duration = audio_duration_to_ms (( * notes_pointer )[ current_note ][ 1 ]);
}
float click [ 2 ][ 2 ];
void audio_play_click ( uint16_t delay , float pitch , uint16_t duration ) {
uint16_t duration_tone = audio_ms_to_duration ( duration );
uint16_t duration_delay = audio_ms_to_duration ( delay );
if ( delay <= 0.0f ) {
click [ 0 ][ 0 ] = pitch ;
click [ 0 ][ 1 ] = duration_tone ;
click [ 1 ][ 0 ] = 0.0f ;
click [ 1 ][ 1 ] = 0.0f ;
audio_play_melody ( & click , 1 , false );
} else {
// first note is a rest/pause
click [ 0 ][ 0 ] = 0.0f ;
click [ 0 ][ 1 ] = duration_delay ;
// second note is the actual click
click [ 1 ][ 0 ] = pitch ;
click [ 1 ][ 1 ] = duration_tone ;
audio_play_melody ( & click , 2 , false );
}
}
2022-02-12 10:29:31 -08:00
bool audio_is_playing_note ( void ) {
return playing_note ;
}
2021-02-14 14:40:38 -08:00
2022-02-12 10:29:31 -08:00
bool audio_is_playing_melody ( void ) {
return playing_melody ;
}
2021-02-14 14:40:38 -08:00
2022-02-12 10:29:31 -08:00
uint8_t audio_get_number_of_active_tones ( void ) {
return active_tones ;
}
2021-02-14 14:40:38 -08:00
float audio_get_frequency ( uint8_t tone_index ) {
if ( tone_index >= active_tones ) {
return 0.0f ;
}
return tones [ active_tones - tone_index - 1 ]. pitch ;
}
float audio_get_processed_frequency ( uint8_t tone_index ) {
if ( tone_index >= active_tones ) {
return 0.0f ;
}
int8_t index = active_tones - tone_index - 1 ;
// new tones are stacked on top (= appended at the end), so the most recent/current is MAX-1
#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING
index = index - tone_multiplexing_index_shift ;
2022-02-12 10:29:31 -08:00
if ( index < 0 ) // wrap around
2021-02-14 14:40:38 -08:00
index += active_tones ;
#endif
if ( tones [ index ]. pitch <= 0.0f ) {
return 0.0f ;
}
return voice_envelope ( tones [ index ]. pitch );
}
bool audio_update_state ( void ) {
if ( ! playing_note && ! playing_melody ) {
return false ;
}
bool goto_next_note = false ;
uint16_t current_time = timer_read ();
if ( playing_melody ) {
goto_next_note = timer_elapsed ( last_timestamp ) >= melody_current_note_duration ;
if ( goto_next_note ) {
uint16_t delta = timer_elapsed ( last_timestamp ) - melody_current_note_duration ;
last_timestamp = current_time ;
uint16_t previous_note = current_note ;
current_note ++ ;
2022-02-12 10:29:31 -08:00
voices_timer = timer_read (); // reset to zero, for the effects added by voices.c
2021-02-14 14:40:38 -08:00
if ( current_note >= notes_count ) {
if ( notes_repeat ) {
current_note = 0 ;
} else {
audio_stop_all ();
return false ;
}
}
if ( ! note_resting && ( * notes_pointer )[ previous_note ][ 0 ] == ( * notes_pointer )[ current_note ][ 0 ]) {
note_resting = true ;
// special handling for successive notes of the same frequency:
// insert a short pause to separate them audibly
audio_play_note ( 0.0f , audio_duration_to_ms ( 2 ));
current_note = previous_note ;
melody_current_note_duration = audio_duration_to_ms ( 2 );
} else {
note_resting = false ;
// TODO: handle glissando here (or remember previous and current tone)
/* there would need to be a freq(here we are) -> freq(next note)
* and do slide/glissando in between problem here is to know which
* frequency on the stack relates to what other? e.g. a melody starts
* tones in a sequence, and stops expiring one, so the most recently
* stopped is the starting point for a glissando to the most recently started?
* how to detect and preserve this relation?
* and what about user input, chords, ...?
*/
// '- delta': Skip forward in the next note's length if we've over shot
// the last, so the overall length of the song is the same
uint16_t duration = audio_duration_to_ms (( * notes_pointer )[ current_note ][ 1 ]);
// Skip forward past any completely missed notes
while ( delta > duration && current_note < notes_count - 1 ) {
delta -= duration ;
current_note ++ ;
duration = audio_duration_to_ms (( * notes_pointer )[ current_note ][ 1 ]);
}
if ( delta < duration ) {
duration -= delta ;
} else {
// Only way to get here is if it is the last note and
// we have completely missed it. Play it for 1ms...
duration = 1 ;
}
audio_play_note (( * notes_pointer )[ current_note ][ 0 ], duration );
melody_current_note_duration = duration ;
}
}
}
if ( playing_note ) {
#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING
tone_multiplexing_index_shift = ( int )( current_time / tone_multiplexing_rate ) % MIN ( AUDIO_MAX_SIMULTANEOUS_TONES , active_tones );
goto_next_note = true ;
#endif
if ( vibrato || glissando ) {
// force update on each cycle, since vibrato shifts the frequency slightly
goto_next_note = true ;
}
// housekeeping: stop notes that have no playtime left
for ( int i = 0 ; i < active_tones ; i ++ ) {
2022-02-12 10:29:31 -08:00
if (( tones [ i ]. duration != 0xffff ) // indefinitely playing notes, started by 'audio_play_tone'
&& ( tones [ i ]. duration != 0 ) // 'uninitialized'
2021-02-14 14:40:38 -08:00
) {
if ( timer_elapsed ( tones [ i ]. time_started ) >= tones [ i ]. duration ) {
2022-02-12 10:29:31 -08:00
audio_stop_tone ( tones [ i ]. pitch ); // also sets 'state_changed=true'
2021-02-14 14:40:38 -08:00
}
}
}
}
// state-changes have a higher priority, always triggering the hardware to update
if ( state_changed ) {
state_changed = false ;
return true ;
}
return goto_next_note ;
}
// Tone-multiplexing functions
#ifdef AUDIO_ENABLE_TONE_MULTIPLEXING
2022-02-12 10:29:31 -08:00
void audio_set_tone_multiplexing_rate ( uint16_t rate ) {
tone_multiplexing_rate = rate ;
}
void audio_enable_tone_multiplexing ( void ) {
tone_multiplexing_rate = AUDIO_TONE_MULTIPLEXING_RATE_DEFAULT ;
}
void audio_disable_tone_multiplexing ( void ) {
tone_multiplexing_rate = 0 ;
}
2021-02-14 14:40:38 -08:00
void audio_increase_tone_multiplexing_rate ( uint16_t change ) {
if (( 0xffff - change ) > tone_multiplexing_rate ) {
tone_multiplexing_rate += change ;
}
}
void audio_decrease_tone_multiplexing_rate ( uint16_t change ) {
if ( change <= tone_multiplexing_rate ) {
tone_multiplexing_rate -= change ;
}
}
#endif
// Tempo functions
void audio_set_tempo ( uint8_t tempo ) {
if ( tempo < 10 ) note_tempo = 10 ;
// else if (tempo > 250)
// note_tempo = 250;
else
note_tempo = tempo ;
}
void audio_increase_tempo ( uint8_t tempo_change ) {
if ( tempo_change > 255 - note_tempo )
note_tempo = 255 ;
else
note_tempo += tempo_change ;
}
void audio_decrease_tempo ( uint8_t tempo_change ) {
if ( tempo_change >= note_tempo - 10 )
note_tempo = 10 ;
else
note_tempo -= tempo_change ;
}
// TODO in the int-math version are some bugs; songs sometimes abruptly end - maybe an issue with the timer/system-tick wrapping around?
uint16_t audio_duration_to_ms ( uint16_t duration_bpm ) {
#if defined(__AVR__)
// doing int-math saves us some bytes in the overall firmware size, but the intermediate result is less accurate before being cast to/returned as uint
return (( uint32_t ) duration_bpm * 60 * 1000 ) / ( 64 * note_tempo );
// NOTE: beware of uint16_t overflows when note_tempo is low and/or the duration is long
#else
return (( float ) duration_bpm * 60 ) / ( 64 * note_tempo ) * 1000 ;
#endif
}
uint16_t audio_ms_to_duration ( uint16_t duration_ms ) {
#if defined(__AVR__)
return (( uint32_t ) duration_ms * 64 * note_tempo ) / 60 / 1000 ;
#else
return (( float ) duration_ms * 64 * note_tempo ) / 60 / 1000 ;
#endif
}