/**
 * @file main.cpp
 * @author Lukas Chytil (xchyti13@vutbr.cz)
 * @brief Engine sound simulator for RC car
 * @date 2023-05-02
 *
 * @copyright Copyright (c) 2023
 *
 */
#include <Arduino.h>

//ESP8266Audio libs
#include "AudioFileSourcePROGMEM.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2S.h"
#include "AudioOutputMixer.h"

//project libs
#include "../lib/VariabelsConstants.h"
#include "../lib/Sounds.h"
#include "../lib/Functions.h"

void setup()
{
	Serial.begin(115200);

	// input pin from receiver
	pinMode(RC_PWM_PIN, INPUT);

	// init built-in LED on ESP32
	pinMode(LED_BUILTIN, OUTPUT);

	if(ULTRASONIC_ENABLE)
	{
		//sets the trigPin as an Output
		pinMode(TRIGPIN, OUTPUT);

		//sets the echoPin as an Input
		pinMode(ECHOPIN, INPUT);
	}

	//measuring PWM TIMEON time in microseconds
	attachInterrupt(digitalPinToInterrupt(RC_PWM_PIN),pulse_width,CHANGE);


	//print ESP8266audio lib error message
	audioLogger = &Serial;

	file = NULL;
	PROGMEMfile_mixer[0] = NULL;
	PROGMEMfile_mixer[1] = NULL;


	out = new AudioOutputI2S();
	PROGMEMout_mixer = new AudioOutputI2S();
	mixer = new AudioOutputMixer(32, PROGMEMout_mixer);

	//mixer setup
	stub[0] = mixer->NewInput();
 	stub[0]->SetGain(0.5);
	stub[1] = mixer->NewInput();
 	stub[1]->SetGain(1.0);

	//create .wav players
	audio = new AudioGeneratorWAV();
	PROGMEMaudio_mixer[0] = new AudioGeneratorWAV();
	PROGMEMaudio_mixer[1] = new AudioGeneratorWAV();

	//volume
	out -> SetGain(volume);
	PROGMEMout_mixer -> SetGain(volume);

	// I2S Connections
	out -> SetPinout(I2S_BCLK,I2S_LRC,I2S_DOUT);
	PROGMEMout_mixer -> SetPinout(I2S_BCLK,I2S_LRC,I2S_DOUT);
}

void loop()
{
	//loop players
	setup_audio_players();

	//call every 20 ms, usual PWM signal period for RC models
	static unsigned long last_call_time = 0;
  	if(millis() - last_call_time >= 20)
	{
		last_call_time = millis();
		signal_analyses();
	}

	//call every 200 milliseconds, reading the value from the potentiometer
	static unsigned long last_call_audio_time = 0;
  	if(millis() - last_call_audio_time >= 200)
	{
		last_call_audio_time = millis();

		//updating the volume
		audio_volume();

		//updating the power
		power_adjust();
	}

	//create engine sound
	sound_creator();
}

void sound_creator()
{
	//saving the TIMEON value at the beginning of the function because an interruption may occur that overwrites it in the middle of the function. Value fluctuations would occur.
	long timeon_function_begin = time_on_value;
	stop_main_player = false;

	//if the receiver does not receive any signal, an error message will be played
	if(!transmitter_signal_check(timeon_function_begin))
		return;

	//after starting the vehicle, the engine starts
	if(starting_engine())
		return;

	//timeon time reglulator, moderate the sudden change in revolutions
	timeon_regulator(timeon_function_begin);

	//braking detection
	if(braking(timeon_function_begin))
	{
		//gradual transition of the engine to idling
		if(timeon_function_sound >= NEUTRAL_TIME_ON_MICRO_SEC + TOLERANCY)
		{
			driving_forward();
		}
		return;
	}

	driving_backwards();

	neutral_engine_rpm();

	driving_forward();

	//if timeon_function_begin is between track interval and no players plays, the last played will start
	if(!stop_main_player)
		play_last_if_silence();
}

void timeon_regulator(long timeon)
{
	static bool big_acc = false;
	static bool big_dec = false;

	//there was a big acceleration based on the difference in values
	if(difference > BIG_ACC && !big_acc)
	{
		big_acc = true;
		timeon_function_sound = prev_time_on_value[1];
	}
	if(difference < (-1 * BIG_ACC) && !big_dec)
	{
		big_dec = true;
		timeon_function_sound = prev_time_on_value[1];
	}

	if(big_acc)	//if big acceleration
	{
		//gradual incrementing of the value, the engine does not suddenly raise the revolutions
		if(timeon_function_sound < timeon)
		{
			static unsigned long last_time = 0;
			if(millis() - last_time >= power)
			{
				last_time = millis();

				if(timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC + ((MAX_TIME_ON_MICRO_SEC - NEUTRAL_TIME_ON_MICRO_SEC)/4)))
					timeon_function_sound += 13;
				else if(timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC + ((MAX_TIME_ON_MICRO_SEC - NEUTRAL_TIME_ON_MICRO_SEC)/3)))
					timeon_function_sound += 9;
				else if(timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC + ((MAX_TIME_ON_MICRO_SEC - NEUTRAL_TIME_ON_MICRO_SEC)/2)))
					timeon_function_sound += 10;
				else if(timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC + ((MAX_TIME_ON_MICRO_SEC - NEUTRAL_TIME_ON_MICRO_SEC)/1.5)))
					timeon_function_sound += 11;
				else
					timeon_function_sound += 15;
			}
		}
		else	//the engine has reached the speed corresponding to the position of the controller
		{
			big_acc = false;
		}
	}
	else if(big_dec)	//if big deceleration
	{
		//gradual decrementing of the value, the engine does not suddenly fall the revolutions
		if(timeon_function_sound > timeon)
		{
			static unsigned long last_time = 0;
			if(millis() - last_time >= power)
			{
				last_time = millis();

				timeon_function_sound -=  9;
			}
		}
		else	//the engine has reached the speed corresponding to the position of the controller
		{
			big_dec = false;
		}
	}
	else //rapid acceleration/decelration did not occur
	{
		timeon_function_sound = timeon;
	}

}

void driving_forward()
{
	//forward speed levels
	if((timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC + TOLERANCY + REMOVE_EDGE)) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + STEP + TOLERANCY )) )
	{
		rpm_function(2);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= ( NEUTRAL_TIME_ON_MICRO_SEC + 2* STEP + TOLERANCY )) )
	{
		rpm_function(3);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 2*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 3* STEP + TOLERANCY )) )
	{
		rpm_function(4);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 3*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 4* STEP + TOLERANCY )) )
	{
		rpm_function(5);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 4*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 5* STEP + TOLERANCY )))
	{
		rpm_function(6);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 5*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 6* STEP + TOLERANCY )))
	{
		rpm_function(7);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 6*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 7* STEP + TOLERANCY )))
	{
		rpm_function(8);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 7*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 8* STEP + TOLERANCY )))
	{
		rpm_function(9);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 8*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 9* STEP + TOLERANCY )))
	{
		rpm_function(10);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 9*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 10* STEP + TOLERANCY )))
	{
		rpm_function(11);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 10*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 11* STEP + TOLERANCY )))
	{
		rpm_function(12);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 11*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 12* STEP + TOLERANCY )))
	{
		rpm_function(13);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 12*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 13* STEP + TOLERANCY )))
	{
		rpm_function(14);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 13*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 14* STEP + TOLERANCY )))
	{
		rpm_function(15);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 14*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 15* STEP + TOLERANCY )))
	{
		rpm_function(16);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 15*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 16* STEP + TOLERANCY )))
	{
		rpm_function(17);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 16*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 17* STEP + TOLERANCY )))
	{
		rpm_function(18);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 17*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 18* STEP + TOLERANCY )))
	{
		rpm_function(19);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 18*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 19* STEP + TOLERANCY )))
	{
		rpm_function(20);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 19*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 20* STEP + TOLERANCY )))
	{
		rpm_function(21);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 20*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + 21* STEP + TOLERANCY )))
	{
		rpm_function(22);
	}
	if((timeon_function_sound > (NEUTRAL_TIME_ON_MICRO_SEC + 21*STEP + REMOVE_EDGE + TOLERANCY )) && (timeon_function_sound <= MAX_TIME_ON_MICRO_SEC))
	{
		rpm_function(23);
	}
}

void driving_backwards()
{

	//if ultrasonic is enabled
	if((ULTRASONIC_ENABLE) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 4*STEP - TOLERANCY)) &&  (timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - TOLERANCY - REMOVE_EDGE)) )
	{
		//reversing sensor
		reverse_distance_sensor();

		stop_main_player = true;
		if(!PROGMEMaudio_mixer[1]->isRunning())
		{
			play_wav_from_PROGMEM_mixer(1);
			rpm_track = -2;
		}
		return;
	}


	//speed levels, ultrasonic disable
	if((timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - STEP - TOLERANCY)) &&  (timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - TOLERANCY - REMOVE_EDGE)) )
	{
		rpm_function(-2);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= ( NEUTRAL_TIME_ON_MICRO_SEC - 2* STEP - TOLERANCY )) )
	{
		rpm_function(-3);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 2*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 3* STEP - TOLERANCY )) )
	{
		rpm_function(-4);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 3*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 4* STEP - TOLERANCY )) )
	{
		rpm_function(-5);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 4*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 5* STEP - TOLERANCY )) )
	{
		rpm_function(-6);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 5*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 6* STEP - TOLERANCY )) )
	{
		rpm_function(-7);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 6*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 7* STEP - TOLERANCY )) )
	{
		rpm_function(-8);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 7*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 8* STEP - TOLERANCY )) )
	{
		rpm_function(-9);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 8*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 9* STEP - TOLERANCY )) )
	{
		rpm_function(-10);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 9*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 10* STEP - TOLERANCY )) )
	{
		rpm_function(-11);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 10*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 11* STEP - TOLERANCY )) )
	{
		rpm_function(-12);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 11*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 12* STEP - TOLERANCY )) )
	{
		rpm_function(-13);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 12*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 13* STEP - TOLERANCY )) )
	{
		rpm_function(-14);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 13*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 14* STEP - TOLERANCY )) )
	{
		rpm_function(-15);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 14*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 15* STEP - TOLERANCY )) )
	{
		rpm_function(-16);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 15*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 16* STEP - TOLERANCY )) )
	{
		rpm_function(-17);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 16*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 17* STEP - TOLERANCY )) )
	{
		rpm_function(-18);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 17*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 18* STEP - TOLERANCY )) )
	{
		rpm_function(-19);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 18*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 19* STEP - TOLERANCY )) )
	{
		rpm_function(-20);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 19*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 20* STEP - TOLERANCY )) )
	{
		rpm_function(-21);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 20*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - 21* STEP - TOLERANCY )) )
	{
		rpm_function(-22);
	}
	if((timeon_function_sound < (NEUTRAL_TIME_ON_MICRO_SEC - 21*STEP - REMOVE_EDGE - TOLERANCY )) && (timeon_function_sound >= MIN_TIME_ON_MICRO_SEC) )
	{
		rpm_function(-23);
	}

}

void signal_analyses()
{
	//difference for acceleration evaluation
	difference = time_on_value - prev_time_on_value[1];

	//store previous TIMEON value
	for(int i  = 4; i > 0; i--)
	{
		prev_time_on_value[i]= prev_time_on_value[i-1];
	}
	prev_time_on_value[0] = time_on_value;

	//print TIMEON value for each period
	/*Serial.print("This is TIME_ON value: ");
	Serial.println(time_on_value);*/
}

void neutral_engine_rpm()
{
	//neutral rpm engine sound
	if((timeon_function_sound >= (NEUTRAL_TIME_ON_MICRO_SEC - TOLERANCY)) && (timeon_function_sound <= (NEUTRAL_TIME_ON_MICRO_SEC + TOLERANCY)))
	{
		rpm_function(1);
	}
}

bool starting_engine()
{
	static bool was_starting = false;

	//the engine has not been started yet
	if(!was_starting)
	{
		//play starting track only one time
		static bool play_one_time = false;
		if(!play_one_time)
		{
			rpm_function(0);
			play_one_time = true;
		}

		if(millis() < played_time + engine_00_millis - 10 )
		{
			//while the engine is still starting, play starting track
			return true;
		}
		else
		{
			//the engine is already started
			was_starting = true;
		}
	}

	//the engine is already started
	return false;
}

bool braking(long timeon_function_begin)
{
	bool forward = false;
	static bool braking = false;

	//deciding whether the vehicle was moving forward based on the last 4 samples
	for(int i = 0; i < 5; i++)
	{
		if(prev_time_on_value[i] > (NEUTRAL_TIME_ON_MICRO_SEC + TOLERANCY + BRAKE_SENSITIVITY ))
			forward = true;
	}

	//if the vehicle was going forward and now the control position is in the reverse position
	if(forward && (timeon_function_begin < (NEUTRAL_TIME_ON_MICRO_SEC - BRAKE_SENSITIVITY)) || (timeon_function_begin < (NEUTRAL_TIME_ON_MICRO_SEC - BRAKE_SENSITIVITY) && rpm_track > 1))
	{
		//activate brake
		braking = true;
	}

	if(braking)
	{
		// still braking, builtin LED will be on
		if(timeon_function_begin < (NEUTRAL_TIME_ON_MICRO_SEC - TOLERANCY - REMOVE_EDGE))
		{
			//brake is activated

			//the engine has already slowed down, play idling
			if(timeon_function_sound < NEUTRAL_TIME_ON_MICRO_SEC + TOLERANCY)
			{
				int engine_track_len = track_length(1);
				bool play = false;

				//if track is ending
				if( millis() > played_time + engine_track_len - 10)
					play = true;

				//play neutral track when the brake is activated
				if(play)
				{
					//neutral sound
					rpm_function(1);
					rpm_track = 1;
				}
			}

			digitalWrite(LED_BUILTIN, HIGH);
			return true;
		}
		else	//the controller position returned to neutral, builtin led will be off
		{
			digitalWrite(LED_BUILTIN, LOW);
			braking = false;
			return false;
		}
	}
	return false;
}

void pulse_width()
{
	static unsigned long start_time = 0;
	static unsigned long current_time = 0;
	static long temp_time_on = 0;
	current_time = micros();

	//determine the pulse length
	if (current_time > start_time)
	{
		temp_time_on = current_time - start_time;
		start_time = current_time;

		//filtering out invalid values
		if(temp_time_on >= MIN_TIME_ON_MICRO_SEC && temp_time_on <= MAX_TIME_ON_MICRO_SEC)
		{
			time_on_value = temp_time_on;
		}
	}
}

void stop_playing_mixer_sound()
{
	//end playing audio in mixer
	if(PROGMEMaudio_mixer[0]->isRunning() || PROGMEMaudio_mixer[1]->isRunning())
	{
		for(int i = 0; i < 2 ; i++)
		{
			if(PROGMEMfile_mixer[i]!= NULL)
			{
				if(PROGMEMaudio_mixer[i]->isRunning())
				{
					PROGMEMaudio_mixer[i]->stop();
					stub[i]->stop();
				}
			}
		}
	}
}

void play_wav_from_PROGMEM(int track_num)
{
	//Serial.println(track_num);

	if(ULTRASONIC_ENABLE == 1)
		stop_playing_mixer_sound();

	//create new file on begin
	if(file == NULL)
		file = new AudioFileSourcePROGMEM(engine_00, sizeof(engine_00));

	//load audio from PROGMEM
	open_audio_file(track_num);

	//start playing
	if(!audio->begin(file, out))
	{
		//try again
		Serial.println("--------------Feiled read WAV-------------");
		audio->stop();
		delete audio;
		audio = new AudioGeneratorWAV();
	}

	//time when it started playing
	played_time = millis();
}

void play_wav_from_PROGMEM_mixer(int channel)
{
	//mute others player
	audio->stop();

	Serial.println(channel);

	//correctly closed last played track
	if(PROGMEMfile_mixer[channel]!= NULL)
	{
		PROGMEMfile_mixer[channel] ->close();
		delete PROGMEMfile_mixer[channel];
	}

	//load and play new track
	if(channel == 1)
		PROGMEMfile_mixer[channel] = new AudioFileSourcePROGMEM(engine_02, sizeof(engine_02));

	if(channel == 0)
		PROGMEMfile_mixer[channel] = new AudioFileSourcePROGMEM(reverse, sizeof(reverse));

	PROGMEMaudio_mixer[channel]->begin(PROGMEMfile_mixer[channel], stub[channel]);
}

void rpm_function(int track_num)
{
	//converting the negative value of the audio track when reversing
	int always_positive_track_num = track_num;
	if(always_positive_track_num < 0)
		always_positive_track_num *= -1;

	//determining the length of the played track
	int engine_track_len = track_length(always_positive_track_num);
	bool play = false;

	//determining whether the audio track is nearing the end
	if( millis() > played_time + engine_track_len - 10)
		play = true;

	//if track is ending or the track number has changed
	if( (play || rpm_track != track_num ))
	{
		play_wav_from_PROGMEM(always_positive_track_num);
	}

	//number of the currently playing track
	rpm_track = track_num;
}

void play_last_if_silence()
{
	//converting the negative value of the audio track when reversing
	int always_positive_track_num = rpm_track;
	if(always_positive_track_num < 0)
		always_positive_track_num *= -1;

	//determining the length of the played track
	int engine_track_len = track_length(always_positive_track_num);
	bool play = false;

	//determining whether the audio track is nearing the end
	if( millis() > played_time + engine_track_len - 10)
		play = true;

	//if time on is betweeen two steps and the soundtrack is just before the end
	if(play)
	{
		//Serial.println("Silence, play last played engine sound");

		//play the last played song
		rpm_function(rpm_track);
	}
}

void measure_distance()
{
	//cleaning the TRIGPIN
	digitalWrite(TRIGPIN, LOW);
	delayMicroseconds(2);
	//Setting the Trigger puls on HIGH state for 10 micro seconds for HC-SR04, https://techfun.sk/produkt/hc-sr04-ultrazvukovy-senzor-vzdialenosti/
	digitalWrite(TRIGPIN, HIGH);
	delayMicroseconds(10);
	digitalWrite(TRIGPIN, LOW);

	//reading the ECHOPIN, returns the sound wave travel time in microseconds
	long duration = pulseIn(ECHOPIN, HIGH);

	//calculate the distance, calculate with the help of the resource https://RandomNerdTutorials.com/esp32-hc-sr04-ultrasonic-arduino/
	distance_cm = duration * SOUND_SPEED/2;

	//print distance
	Serial.print("Distance (cm): ");
	Serial.println(distance_cm);
}

void reverse_distance_sensor()
{
	//distance is measured every 40 miliseconds
	static unsigned long last_sensor_time = 0;
	if(millis() - last_sensor_time >= 40)
	{
		measure_distance();
		last_sensor_time = millis();
	}

	//no need to beep, there is no obstacle nearby
	if(distance_cm > 40)
		return;

	//calculation of how often the song should be played, every 5 centimeters are added 80  milliseconds to the length of the song
	int beep_delay = 500 + (int)(distance_cm/5) * 80;

	//play beep sound
	static unsigned long last_beep_time = 0;
	if(millis() - last_beep_time >= beep_delay)
	{
		last_beep_time = millis();
		play_wav_from_PROGMEM_mixer(0);
	}
}

void setup_audio_players()
{
	//PROGMEM audio
	if(file!= NULL)
	{
		if(!stop_main_player)
			if(!audio->loop())
				audio->stop();
	}


	//PROGMEM mixer setup
	for(int i = 0; i < 2; i++)
	{
		if(PROGMEMfile_mixer[i] != NULL)
		{
			if (!PROGMEMaudio_mixer[i]->loop())
			{
				PROGMEMaudio_mixer[i]->stop();
				stub[i]->stop();
			}
		}
	}
}

bool transmitter_signal_check(long time_on)
{
	static long transmitter_timer = millis() - 4000;
	static bool playing = true;

	//play error message every 7 second, first call is after 2 seconds
	if((millis() - transmitter_timer >= 7000) && time_on == 0)
	{
		//Serial.println("No PWM signal");
		play_wav_from_PROGMEM(100);
		transmitter_timer = millis();
		playing = true;
		return false;
	}

	//stop playing error message when receiver got valid PWM value
	if(time_on != 0 && playing)
	{
		playing = false;
		return true;
	}

	//return true when Transmitter is connected
	if(time_on != 0)
	{
		return true;
	}

	//otherwise, timer value is less than 7 second and time_on value is 0
	return false;
}

int track_length( int track_num)
{
	switch (track_num)
	{
		case 0:
			return engine_00_millis;		//engine start
			break;
		case 1:
			return engine_01_millis;		//neutral engine sound
			break;
		case 2:
			return engine_02_millis;
			break;
		case 3:
			return engine_03_millis;
			break;
		case 4:
			return engine_04_millis;
			break;
		case 5:
			return engine_05_millis;
			break;
		case 6:
			return engine_06_millis;
			break;
		case 7:
			return engine_07_millis;
			break;
		case 8:
			return engine_08_millis;
			break;
		case 9:
			return engine_09_millis;
			break;
		case 10:
			return engine_10_millis;
			break;
		case 11:
			return engine_11_millis;
			break;
		case 12:
			return engine_12_millis;
			break;
		case 13:
			return engine_13_millis;
			break;
		case 14:
			return engine_14_millis;
			break;
		case 15:
			return engine_15_millis;
			break;
		case 16:
			return engine_16_millis;
			break;
		case 17:
			return engine_17_millis;
			break;
		case 18:
			return engine_18_millis;
			break;
		case 19:
			return engine_19_millis;
			break;
		case 20:
			return engine_20_millis;
			break;
		case 21:
			return engine_21_millis;
			break;
		case 22:
			return engine_22_millis;
			break;
		case 23:
			return engine_23_millis;
			break;
		default:
			return engine_01_millis;
			break;
	}
}

void open_audio_file(int track_num)
{
	switch (track_num)
	{
		case 0:
			file->open(engine_00,sizeof(engine_00));
			break;
		case 1:
			file->open(engine_01,sizeof(engine_01));
			break;
		case 2:
			file->open(engine_02,sizeof(engine_02));
			break;
		case 3:
			file->open(engine_03,sizeof(engine_03));
			break;
		case 4:
			file->open(engine_04,sizeof(engine_04));
			break;
		case 5:
			file->open(engine_05,sizeof(engine_05));
			break;
		case 6:
			file->open(engine_06,sizeof(engine_06));
			break;
		case 7:
			file->open(engine_07,sizeof(engine_07));
			break;
		case 8:
			file->open(engine_08,sizeof(engine_08));
			break;
		case 9:
			file->open(engine_09,sizeof(engine_09));
			break;
		case 10:
			file->open(engine_10,sizeof(engine_10));
			break;
		case 11:
			file->open(engine_11,sizeof(engine_11));
			break;
		case 12:
			file->open(engine_12,sizeof(engine_12));
			break;
		case 13:
			file->open(engine_13,sizeof(engine_13));
			break;
		case 14:
			file->open(engine_14,sizeof(engine_14));
			break;
		case 15:
			file->open(engine_15,sizeof(engine_15));
			break;
		case 16:
			file->open(engine_16,sizeof(engine_16));
			break;
		case 17:
			file->open(engine_17,sizeof(engine_17));
			break;
		case 18:
			file->open(engine_18,sizeof(engine_18));
			break;
		case 19:
			file->open(engine_19,sizeof(engine_19));
			break;
		case 20:
			file->open(engine_20,sizeof(engine_20));
			break;
		case 21:
			file->open(engine_21,sizeof(engine_21));
			break;
		case 22:
			file->open(engine_22,sizeof(engine_22));
			break;
		case 23:
			file->open(engine_23,sizeof(engine_23));
			break;

		case 100:
			file->open(error_transmitter,sizeof(error_transmitter));
			break;
		default:
			Serial.println("Track was not fount in PROGMEM");
			file->open(engine_01,sizeof(engine_01));
			break;
	}
}

void audio_volume()
{
	//read value from pin
	int volume_read = analogRead(VOLUME_PIN);

	static int arr_size = 3;
	static float volume_arr[3] ={volume, volume, volume};
	int changed_values = 0;
	float new_volume = 0.0;

	//value adjustment
	if(volume_read < 100)
	{
		new_volume = 0.1;
	}
	else if (volume_read > 4000)
	{
		new_volume = 3.99;	//max supported value
	}
	else
	{
		//remove last 2 digits
		new_volume = float (volume_read / 100);

		//adjust in supported range
		new_volume = float (new_volume / 10.0);
	}

	//update sound array
	for(int i = arr_size - 1; i > 0; i--)
	{
		volume_arr[i] = volume_arr[i - 1];
	}
	volume_arr[0] = new_volume;

	//counting equal values ​​in the field
	for(int i =  0; i < arr_size; i++)
	{
		if(volume != volume_arr[i])
			changed_values++;
	}

	//all values was changed, update volume
	if(changed_values == arr_size)
	{
		//update volume
		volume = new_volume;
		out -> SetGain(volume);
		PROGMEMout_mixer -> SetGain(volume);
	}

	//Serial.print("Volume is: ");
	//Serial.println(volume);
}

void power_adjust()
{
	//read value from pin
	int power_read = analogRead(POWER_PIN);

	static int arr_size = 3;
	static int power_arr[3] ={power, power, power};
	int changed_values = 0;
	int new_power = 0;

	//value adjustment
	if(power_read < 700)
	{
		new_power = 7;
	}
	else
	{
		//remove last 2 digits
		new_power = int (power_read / 100);
	}

	//update power array
	for(int i = arr_size - 1; i > 0; i--)
	{
		power_arr[i] = power_arr[i - 1];
	}
	power_arr[0] = new_power;

	//counting equal values ​​in the field
	for(int i =  0; i < arr_size; i++)
	{
		if(volume != power_arr[i])
			changed_values++;
	}

	//all values was changed, update power
	if(changed_values == arr_size)
	{
		power = new_power;
	}

	Serial.print("Power is: ");
	Serial.println(power);
}