#include "daisy_seed.h"
#include "daisysp.h"

#include "scope.hpp"
#include "osclsk.hpp"

using namespace daisy;
using namespace daisysp;

#define DMA_AREA_SIZE		(1 << 12)
#define SCOPE_RING_BUF_SIZE	1024
#define AUDIO_BLOCK_SIZE	2


/* declarations */
DaisySeed daisy_hw;
CpuLoadMeter load_meter;

osclsk_scope scope;

uint8_t DMA_BUFFER_MEM_SECTION dma_area[DMA_AREA_SIZE];

static void audio_cb(AudioHandle::InputBuffer in,
    AudioHandle::OutputBuffer out, size_t sz);

Oscillator osc;
WhiteNoise noise;

AdEnv kickVolEnv, kickPitchEnv, snareEnv;

Switch kick, snare;

RingBuffer<float, SCOPE_RING_BUF_SIZE>	scope_in;

void
audio_cb(AudioHandle::InputBuffer in,
    AudioHandle::OutputBuffer out, size_t sz)
{
	float osc_out, noise_out, snr_env_out, kck_env_out;
	float sig[AUDIO_BLOCK_SIZE];

//	load_meter.OnBlockStart();
	//Get rid of any bouncing
	snare.Debounce();
	kick.Debounce();

	//If you press the kick button...
	if(kick.RisingEdge())
	{
		//Trigger both envelopes!
		kickVolEnv.Trigger();
		kickPitchEnv.Trigger();
	}

	//If press the snare button trigger its envelope
	if(snare.RisingEdge())
	{
		snareEnv.Trigger();
	}

	//Prepare the audio block
	for(size_t i = 0; i < sz; i++)
	{
		//Get the next volume samples
		snr_env_out = snareEnv.Process();
		kck_env_out = kickVolEnv.Process();

		//Apply the pitch envelope to the kick
		osc.SetFreq(kickPitchEnv.Process());
		//Set the kick volume to the envelope's output
		osc.SetAmp(kck_env_out);
		//Process the next oscillator sample
		osc_out = osc.Process();

		//Get the next snare sample
		noise_out = noise.Process();
		//Set the sample to the correct volume
		noise_out *= snr_env_out;

		//Mix the two signals at half volume
		sig[i] = .5 * noise_out + .5 * osc_out;
	}

	/* write to output */
	for (size_t i = 0; i < sz; i++) {
		out[0][i] = sig[i];
		out[1][i] = sig[i];
	}

	scope_in.Overwrite(sig, sz);

//	load_meter.OnBlockEnd();
}

static void
setup_drums(void)
{
	float samplerate;

	/* kick n snare */
	samplerate = daisy_hw.AudioSampleRate();

	//Initialize oscillator for kickdrum
	osc.Init(samplerate);
	osc.SetWaveform(Oscillator::WAVE_TRI);
	osc.SetAmp(1);

	//Initialize noise
	noise.Init();

	//Initialize envelopes, this one's for the snare amplitude
	snareEnv.Init(samplerate);
	snareEnv.SetTime(ADENV_SEG_ATTACK, .01);
	snareEnv.SetTime(ADENV_SEG_DECAY, .2);
	snareEnv.SetMax(1);
	snareEnv.SetMin(0);

	//This envelope will control the kick oscillator's pitch
	//Note that this envelope is much faster than the volume
	kickPitchEnv.Init(samplerate);
	kickPitchEnv.SetTime(ADENV_SEG_ATTACK, .01);
	kickPitchEnv.SetTime(ADENV_SEG_DECAY, .05);
	kickPitchEnv.SetMax(400);
	kickPitchEnv.SetMin(50);

	//This one will control the kick's volume
	kickVolEnv.Init(samplerate);
	kickVolEnv.SetTime(ADENV_SEG_ATTACK, .01);
	kickVolEnv.SetTime(ADENV_SEG_DECAY, 1);
	kickVolEnv.SetMax(1);
	kickVolEnv.SetMin(0);

	//Initialize the kick and snare buttons on pins 27 and 28
	//The callback rate is samplerate / blocksize (48)
	snare.Init(daisy_hw.GetPin(27), samplerate / (float)AUDIO_BLOCK_SIZE);
	kick.Init(daisy_hw.GetPin(28), samplerate / (float)AUDIO_BLOCK_SIZE);
}


#define LOAD_METER_TICKS	(1 << 20)
int
main(void)
{
	size_t read;
	float s[AUDIO_BLOCK_SIZE];
	uint32_t load_tick;
	daisy_hw.Configure();
	daisy_hw.Init();
	daisy_hw.StartLog(false); /* true = wait for usb connection */
	daisy_hw.SetAudioBlockSize(AUDIO_BLOCK_SIZE);

	//load_meter.Init(daisy_hw.AudioSampleRate(), daisy_hw.AudioBlockSize());
	//load_tick = LOAD_METER_TICKS;

	if (scope.init(dma_area, sizeof(dma_area)) == -1)
		daisy_hw.PrintLine("scope.init failed");

	scope_in.Init();

	setup_drums();

	daisy_hw.StartAudio(audio_cb);

	while (true) {


		switch (scope.state()) {
		case osclsk_scope::osc_state::SAMPLE_DONE:
			scope.render_block();
			break;
		case osclsk_scope::osc_state::RENDER:
			scope.render_block();
			break;
		case osclsk_scope::osc_state::RENDER_DONE:
			scope.prep();
			break;
		case osclsk_scope::osc_state::READY:
			scope.trig();
			break;
		default:
			break;
		}

		read = scope_in.readable();
		read = (read <= sizeof(s)/sizeof(*s)) ? read :
		    sizeof(s)/sizeof(*s);
		scope_in.ImmediateRead(s, read);
		scope.sample(s, read);


//		if (--load_tick == 0) {
//			load_tick = LOAD_METER_TICKS;
//			// get the current load (smoothed value and peak values)
//			const float avgLoad = load_meter.GetAvgCpuLoad();
//			const float maxLoad = load_meter.GetMaxCpuLoad();
//			const float minLoad = load_meter.GetMinCpuLoad();
//			// print it to the serial connection (as percentages)
//			daisy_hw.PrintLine("Processing Load %:");
//			daisy_hw.PrintLine("Max: " FLT_FMT3, FLT_VAR3(maxLoad * 100.0f));
//			daisy_hw.PrintLine("Avg: " FLT_FMT3, FLT_VAR3(avgLoad * 100.0f));
//			daisy_hw.PrintLine("Min: " FLT_FMT3, FLT_VAR3(minLoad * 100.0f));
//		}
	}
}

