/* $Id: gbhw.c,v 1.31 2004/06/06 00:03:09 ranmachan Exp $
 *
 * gbsplay is a Gameboy sound player
 *
 * 2003 (C) by Tobias Diedrich <ranma@gmx.at>
 * Licensed under GNU GPL.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include "../mednafen.h"
#include "../state.h"
#include "GB.h"
#include "sound.h"
#include "sound/Gb_Apu.h"
#include <blip/Stereo_Buffer.h>

static Gb_Apu gb_apu;
static Stereo_Buffer gb_buf;

SFORMAT GBSound_StateRegs[] =
{
 {&gb_apu.regs, sizeof(gb_apu.regs), "REGS"},

 {&gb_apu.square1.phase, sizeof(int) | MDFNSTATE_RLSB, "s1PH" },
 {&gb_apu.square1.duty, sizeof(int) | MDFNSTATE_RLSB, "s1DU" },

 {&gb_apu.square1.sweep_period, sizeof(int) | MDFNSTATE_RLSB, "s1SP" },
 {&gb_apu.square1.sweep_delay, sizeof(int) | MDFNSTATE_RLSB, "s1SD" },
 {&gb_apu.square1.sweep_shift, sizeof(int) | MDFNSTATE_RLSB, "s1SS" },
 {&gb_apu.square1.sweep_dir, sizeof(int) | MDFNSTATE_RLSB, "s1SD" },
 {&gb_apu.square1.sweep_freq, sizeof(int) | MDFNSTATE_RLSB, "s1SF" },

 {&gb_apu.square1.env_period, sizeof(int) | MDFNSTATE_RLSB, "s1EP" },
 {&gb_apu.square1.env_dir, sizeof(int) | MDFNSTATE_RLSB, "s1ED" },
 {&gb_apu.square1.env_delay, sizeof(int) | MDFNSTATE_RLSB, "s1Ed" },

 {&gb_apu.square1.new_env_period, sizeof(int) | MDFNSTATE_RLSB, "s1NP" },
 {&gb_apu.square1.new_env_dir, sizeof(int) | MDFNSTATE_RLSB, "s1ND" },
 {&gb_apu.square1.new_volume, sizeof(int) | MDFNSTATE_RLSB, "s1NV" },

 {&gb_apu.square1.period, sizeof(int) | MDFNSTATE_RLSB, "s1OP" },
 {&gb_apu.square1.volume, sizeof(int) | MDFNSTATE_RLSB, "s1OV" },
 {&gb_apu.square1.frequency, sizeof(int) | MDFNSTATE_RLSB, "s1OF" },
 {&gb_apu.square1.length, sizeof(int) | MDFNSTATE_RLSB, "s1OL" },
 {&gb_apu.square1.new_length, sizeof(int) | MDFNSTATE_RLSB, "s1Ol" },
 {&gb_apu.square1.ME, sizeof(int) | MDFNSTATE_RLSB, "s1OM" },
 SFVARN(gb_apu.square1.enabled, "s1OE"),
 SFVARN(gb_apu.square1.length_enabled, "s1Oe"),
 {&gb_apu.square1.output_select, sizeof(int) | MDFNSTATE_RLSB, "s1OS" },


 {&gb_apu.square2.phase, sizeof(int) | MDFNSTATE_RLSB, "s2PH" },
 {&gb_apu.square2.duty, sizeof(int) | MDFNSTATE_RLSB, "s2DU" },

 {&gb_apu.square2.sweep_period, sizeof(int) | MDFNSTATE_RLSB, "s2SP" },
 {&gb_apu.square2.sweep_delay, sizeof(int) | MDFNSTATE_RLSB, "s2SD" },
 {&gb_apu.square2.sweep_shift, sizeof(int) | MDFNSTATE_RLSB, "s2SS" },
 {&gb_apu.square2.sweep_dir, sizeof(int) | MDFNSTATE_RLSB, "s2SD" },
 {&gb_apu.square2.sweep_freq, sizeof(int) | MDFNSTATE_RLSB, "s2SF" },

 {&gb_apu.square2.env_period, sizeof(int) | MDFNSTATE_RLSB, "s2EP" },
 {&gb_apu.square2.env_dir, sizeof(int) | MDFNSTATE_RLSB, "s2ED" },
 {&gb_apu.square2.env_delay, sizeof(int) | MDFNSTATE_RLSB, "s2Ed" },

 {&gb_apu.square2.new_env_period, sizeof(int) | MDFNSTATE_RLSB, "s2NP" },
 {&gb_apu.square2.new_env_dir, sizeof(int) | MDFNSTATE_RLSB, "s2ND" },
 {&gb_apu.square2.new_volume, sizeof(int) | MDFNSTATE_RLSB, "s2NV" },

 {&gb_apu.square2.period, sizeof(int) | MDFNSTATE_RLSB, "s2OP" },
 {&gb_apu.square2.volume, sizeof(int) | MDFNSTATE_RLSB, "s2OV" },
 {&gb_apu.square2.frequency, sizeof(int) | MDFNSTATE_RLSB, "s2OF" },
 {&gb_apu.square2.length, sizeof(int) | MDFNSTATE_RLSB, "s2OL" },
 {&gb_apu.square2.new_length, sizeof(int) | MDFNSTATE_RLSB, "s2Ol" },
 {&gb_apu.square2.ME, sizeof(int) | MDFNSTATE_RLSB, "s2OM" },
 SFVARN(gb_apu.square2.enabled, "s2OE"),
 SFVARN(gb_apu.square2.length_enabled, "s2Oe"),
 {&gb_apu.square2.output_select, sizeof(int) | MDFNSTATE_RLSB, "s2OS" },


 {&gb_apu.noise.bits, sizeof(int) | MDFNSTATE_RLSB, "sNNB" },
 {&gb_apu.noise.tap, sizeof(int) | MDFNSTATE_RLSB, "sNNT" },

 {&gb_apu.noise.env_period, sizeof(int) | MDFNSTATE_RLSB, "sNEP" },
 {&gb_apu.noise.env_dir, sizeof(int) | MDFNSTATE_RLSB, "sNED" },
 {&gb_apu.noise.env_delay, sizeof(int) | MDFNSTATE_RLSB, "sNEd" },

 {&gb_apu.noise.new_env_period, sizeof(int) | MDFNSTATE_RLSB, "s2NP" },
 {&gb_apu.noise.new_env_dir, sizeof(int) | MDFNSTATE_RLSB, "s2ND" },
 {&gb_apu.noise.new_volume, sizeof(int) | MDFNSTATE_RLSB, "s2NV" },

 {&gb_apu.noise.period, sizeof(int) | MDFNSTATE_RLSB, "sNOP" },
 {&gb_apu.noise.volume, sizeof(int) | MDFNSTATE_RLSB, "sNOV" },
 {&gb_apu.noise.frequency, sizeof(int) | MDFNSTATE_RLSB, "sNOF" },
 {&gb_apu.noise.length, sizeof(int) | MDFNSTATE_RLSB, "sNOL" },
 {&gb_apu.noise.new_length, sizeof(int) | MDFNSTATE_RLSB, "sNOl" },
 {&gb_apu.noise.ME, sizeof(int) | MDFNSTATE_RLSB, "sNOM" },
 SFVARN(gb_apu.noise.enabled, "sNOE"),
 SFVARN(gb_apu.noise.length_enabled, "sNOe"),


 {&gb_apu.wave.volume_shift, sizeof(int) | MDFNSTATE_RLSB, "sWWS" },
 {&gb_apu.wave.wave_pos, sizeof(int) | MDFNSTATE_RLSB, "sWWP" },
 {gb_apu.wave.wave, 32, "sWWW" },

 {&gb_apu.wave.period, sizeof(int) | MDFNSTATE_RLSB, "sWOP" },
 {&gb_apu.wave.volume, sizeof(int) | MDFNSTATE_RLSB, "sWOV" },
 {&gb_apu.wave.frequency, sizeof(int) | MDFNSTATE_RLSB, "sWOF" },
 {&gb_apu.wave.length, sizeof(int) | MDFNSTATE_RLSB, "sWOL" },
 {&gb_apu.wave.new_length, sizeof(int) | MDFNSTATE_RLSB, "sWOl" },
 {&gb_apu.wave.ME, sizeof(int) | MDFNSTATE_RLSB, "sWOM" },
 SFVARN(gb_apu.wave.enabled, "sWOE"),
 SFVARN(gb_apu.wave.length_enabled, "sWOe"),
 SFEND
};

void MDFNGBSOUND_Reset(void)
{
	gb_apu.reset();
}

uint32 MDFNGBSOUND_Read(int ts, uint32 addr)
{
	return(gb_apu.read_register(ts, addr));
}

void MDFNGBSOUND_Write(int ts, uint32 addr, uint8 val)
{
	gb_apu.write_register(ts, addr, val);
}

void MDFNGB_SetSoundMultiplier(double multiplier)
{
	gb_buf.clock_rate((long)(4194304 * multiplier));
}

void MDFNGB_SetSoundVolume(uint32 volume)
{
	gb_apu.volume((double)volume / 2 / 100);
}

void MDFNGB_Sound(int rate)
{
	gb_buf.set_sample_rate(rate?rate:44100, 40);
}

static void srest(int version)
{
	gb_apu.dirty();
}

int MDFNGBSOUND_StateAction(StateMem *sm, int load, int data_only)
{
 std::vector <SSDescriptor> love;

 love.push_back(SSDescriptor(GBSound_StateRegs, "SND"));

int ret = MDFNSS_StateAction(sm, load, data_only, love);

 if(load)
  srest(load);
 return(ret);
}

static bool forcemono;

void MDFNGBSOUND_Init(bool WantMono)
{
	gb_buf.set_sample_rate(FSettings.SndRate?FSettings.SndRate:44100, 40);
	gb_buf.clock_rate(4194304);

        forcemono = WantMono;

        if(forcemono)
 	 gb_apu.output(gb_buf.left(), NULL, NULL);
	else
         gb_apu.output(gb_buf.center(), gb_buf.left(), gb_buf.right());
}

int16 *MDFNGBSOUND_Flush(int ts, int32 *length)
{
	static int16 buffer[8000];

	gb_apu.end_frame(ts);

	if(forcemono)
        {
         gb_buf.left()->end_frame(ts);
         *length = gb_buf.left()->read_samples(buffer, 8000);
        }
	else
	{
	 gb_buf.end_frame(ts);
	 *length = gb_buf.read_samples(buffer, 8000);
	 *length /= 2;
	}
	return(buffer);
}
