#include <audio.h>

#include <emu.h>
#include <ppu.h>

#include <portaudio.h>
#include <pa_ringbuffer.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>

#define SAMPLE_RATE 44100.0
#define CPU_RATE 4194304.0
#define FRAMES_PER_BUFFER 16
#define SAMPLES_PER_AUDIO_TICK SAMPLE_RATE / 4194304.0
#define HISTORY_INTERVAL 5

const u8 square_sample_00[8] = {
    0x0,
    0xF,
    0xF,
    0xF,
    0xF,
    0xF,
    0xF,
    0xF
};

const u8 square_sample_01[8] = {
    0x0,
    0x0,
    0xF,
    0xF,
    0xF,
    0xF,
    0xF,
    0xF
};

const u8 square_sample_10[8] = {
    0x0,
    0x0,
    0x0,
    0x0,
    0xF,
    0xF,
    0xF,
    0xF
};

const u8 square_sample_11[8] = {
    0x0,
    0x0,
    0x0,
    0x0,
    0x0,
    0x0,
    0xF,
    0xF
};

const u8 *square_sample[4] = {
    square_sample_00,
    square_sample_01,
    square_sample_10,
    square_sample_11
};

static audio_context ctx;
static PaStream *stream;
static float previous_left;
static float previous_right;

audio_context *audio_get_context() {
    return &ctx;
}

void audio_init(){
    PaStreamParameters output_parameters;
    PaError err;

    ctx.left_audio_buffer = malloc(sizeof(float) * FRAMES_PER_BUFFER);
    ctx.right_audio_buffer = malloc(sizeof(float) * FRAMES_PER_BUFFER);

    memset(ctx.left_audio_buffer, 0, sizeof(float) * FRAMES_PER_BUFFER);
    memset(ctx.right_audio_buffer, 0, sizeof(float) * FRAMES_PER_BUFFER);

    ctx.audio_enabled = true;
    ctx.ch1_left = true;
    ctx.ch1_right = true;
    ctx.ch2_left = true;
    ctx.ch2_right = true;
    ctx.ch3_left = true;
    ctx.ch3_right = false;
    ctx.ch4_left = true;
    ctx.ch4_right = false;

    ctx.volume_left = 0x7;
    ctx.volume_right = 0x7;

    ctx.sq1_duty = 0x2;
    ctx.sq1_volume = 0xF;
    ctx.sq1_sample = 0x0;
    ctx.sq1_period_reset = 0x0;
    ctx.sq1_period_timer = 0x0;
    ctx.sq1_enable = true;
    ctx.sq1_len_enable = false;
    ctx.sq1_sweep_pace = 0x0;
    ctx.sq1_sweep_direction = false;
    ctx.sq1_initial_len = 0x3F;
    ctx.sq1_len = 0x0;
    ctx.sq1_initial_volume = 0xF;
    ctx.sq1_env_direction = false;
    ctx.sq1_env_pace = 0x3;
    ctx.sq1_env_pace_buffer = 0x3;
    ctx.sq1_env_timer = 0x0;
    ctx.sq1_sweep_step = 0x0;

    ctx.sq2_duty = 0x0;
    ctx.sq2_volume = 0x0;
    ctx.sq2_sample = 0x0;
    ctx.sq2_period_reset = 0x0;
    ctx.sq2_period_timer = 0x0;
    ctx.sq2_enable = false;
    ctx.sq2_len_enable = false;
    ctx.sq2_initial_len = 0x3F;
    ctx.sq2_len = 0x0;
    ctx.sq2_initial_volume = 0x0;
    ctx.sq2_env_direction = false;
    ctx.sq2_env_pace = 0x0;
    ctx.sq2_env_timer = 0x0;
    
    err = Pa_Initialize();
    if (err != paNoError) goto error;
    output_parameters.device = Pa_GetDefaultOutputDevice();
    if(output_parameters.device == paNoDevice) {
        fprintf(stderr, "No default audio device! Number of devices: %d\n", Pa_GetDeviceCount());
        goto error;
    }
    output_parameters.channelCount = 2;
    output_parameters.sampleFormat = paFloat32 | paNonInterleaved;
    output_parameters.suggestedLatency = Pa_GetDeviceInfo(output_parameters.device)->defaultLowOutputLatency;
    output_parameters.hostApiSpecificStreamInfo = NULL;

    err = Pa_OpenStream(&stream,
                        NULL,
                        &output_parameters,
                        SAMPLE_RATE,
                        FRAMES_PER_BUFFER,
                        paNoFlag,
                        NULL,
                        &ctx);
    if(err != paNoError) goto error;
    err = Pa_StartStream(stream);
    if(err != paNoError) goto error;
    return;
error:
    Pa_Terminate();
    fprintf(stderr, "portaudio stream error\n\tError Number: %d\n\tError Message: %s\n", err, Pa_GetErrorText(err));
    exit(1);
}

void sq1_sweep() {
    //frequency calculation
    int step = (ctx.sq1_sweep_period >> ctx.sq1_sweep_step);
    step = ctx.sq1_sweep_direction ? -step : step;
    ctx.sq1_calc_period = ctx.sq1_sweep_period + step;
    //overflow check
    if(ctx.sq1_calc_period > 0x7FF) {
        ctx.sq1_enable = false;
		}
}

void lfsr_tick() {
    ctx.lfsr_timer--;
    if(ctx.lfsr_timer == 0) {
        ctx.lfsr_timer = (ctx.ch4_clock_divider > 0 ? (ctx.ch4_clock_divider << 4) : 8) << ctx.ch4_clock_shift;
        unsigned bit_mask = ctx.ch4_lfsr_width ? 0x4040 : 0x4000;
        bool new_bit = (ctx.ch4_lfsr ^ (ctx.ch4_lfsr >> 1) ^ 1) & 1;
        ctx.ch4_lfsr >>= 1;
        if(new_bit) {
            ctx.ch4_lfsr |= bit_mask;
        } else {
            ctx.ch4_lfsr &= ~bit_mask;
        }
    }
}

void audio_sample_tick() {
    lfsr_tick();
    float left = 0;
    float right = 0;
    float sq1_val = 0;
    float sq2_val = 0;
    float ch3_val = 0;
    float ch4_val = 0;
    if(ctx.audio_enabled){
        if(ctx.ch1_dac){
            sq1_val = -1;
            if(ctx.sq1_enable && (ctx.ch1_left || ctx.ch1_right)) {
                sq1_val = ((float)ctx.sq1_volume/15.0f) * (((float)(square_sample[ctx.sq1_duty][ctx.sq1_sample]) - 7.5f)/7.5f);
                if(ctx.ch1_left) {
                    left += sq1_val;
                }else {
                    left -= 1;
                }
                if(ctx.ch1_right) {
                    right += sq1_val;
                }else {
                    right -= 1;
                }
            } else {
                left -= 1;
                right -= 1;
            }
        }
        if(ctx.ch2_dac){
            sq2_val = -1;
            if(ctx.sq2_enable && (ctx.ch2_left || ctx.ch2_right)) {
                sq2_val = ((float)ctx.sq2_volume/15.0f) * (((float)(square_sample[ctx.sq2_duty][ctx.sq2_sample]) - 7.5f)/7.5f);
                if(ctx.ch2_left) {
                    left += sq2_val;
                }else {
                    left -= 1;
                }
                if(ctx.ch2_right) {
                    right += sq2_val;
                }else {
                    right -= 1;
                }
            } else {
                left -= 1;
                right -= 1;
            }
        }
        if(ctx.ch3_dac){
            u8 shift = 0;
            if(ctx.ch3_volume == 0b10) {
                shift = 1;
            }
            if(ctx.ch3_volume == 0b00) {
                shift = 4;
            }
            if(ctx.ch3_volume == 0b11) {
                shift = 2;
            }
            ch3_val = -1;
            if(ctx.ch3_enable && (ctx.ch3_left || ctx.ch3_right)) {
                ch3_val = (((float)(ctx.ch3_last_sample >> shift) - 7.5f)/7.5f);
                if(ctx.ch3_left) {
                    left += ch3_val;
                }else {
                    left -= 1;
                }
                if(ctx.ch3_right) {
                    right += ch3_val;
                }else {
                    right -= 1;
                }
            } else {
                left -= 1;
                right -= 1;
            }
        }
        if(ctx.ch4_dac){
            ch4_val = -1;
            if(ctx.ch4_enable && (ctx.ch4_left || ctx.ch4_right)) {
                ch4_val = ((ctx.ch4_lfsr & 0b1) == 0b1) ? (((float)ctx.ch4_volume - 7.5f)/7.5f) : -1.0f;
                if(ctx.ch4_left) {
                    left += ch4_val;
                }else {
                    left -= 1;
                }
                if(ctx.ch4_right) {
                    right += ch4_val;
                } else {
                    right -= 1;
                }
            } else {
                left -= 1;
                right -= 1;
            }
        }
    }

    u8 left_vol = ctx.volume_left == 0 ? 1 : ctx.volume_left;
    u8 right_vol = ctx.volume_right == 0 ? 1 : ctx.volume_right;
    left *= (float)left_vol/7.0f;
    right *= (float)right_vol/7.0f;
    double rc = 1.0 / (2.0 * M_PI * (SAMPLE_RATE/2));
    double dt = 1.0 / CPU_RATE;
    double alpha = dt / (rc+dt);
    left = alpha * left + (1.0 - alpha) * previous_left;
    previous_left = left;

    right = alpha * right + (1.0 - alpha) * previous_right;
    previous_right = right;

    ctx.cycles_needed += emu_get_context()->fast_forward ? SAMPLES_PER_AUDIO_TICK/FAST_FORWARD_SPEED : SAMPLES_PER_AUDIO_TICK;
    if(ctx.cycles_needed > 1){
        ctx.cycles_needed -= 1;
        //if(Pa_GetStreamWriteAvailable(stream) < 1)
        //    return;

        bool dacs = ctx.ch1_dac || ctx.ch2_dac || ctx.ch3_dac || ctx.ch4_dac;
        float left_out = 0;
        float right_out = 0;
        if(dacs) {
            left_out = left - ctx.left_cap;
            right_out = right - ctx.right_cap;
            ctx.left_cap = left - left_out * 0.999958;
            ctx.right_cap = right - right_out * 0.999958;
        }

        
        left_out /= 4;
        right_out /= 4;

        if(left_out < -1.0f) {
            left_out = -1;
        }
        if(left_out > 1.0f) {
            left_out = 1;
        }
        if(right_out < -1.0f) {
            right_out = -1;
        }
        if(right_out > 1.0f) {
            right_out = 1;
        }

        if(ctx.buffer_write_index < FRAMES_PER_BUFFER){
            ctx.left_audio_buffer[(ctx.buffer_write_index)] = left_out;
            ctx.right_audio_buffer[(ctx.buffer_write_index)] = right_out;
            ctx.buffer_write_index++;
        }

        ctx.history_timer++;
        if(ctx.history_timer >= HISTORY_INTERVAL){
            ctx.history_timer = 0;
            ctx.sq1_history[ctx.sq1_index++] = sq1_val;
            if (ctx.sq1_index == 384) {
                ctx.sq1_index = 0;
            }
    
            ctx.sq2_history[ctx.sq2_index++] = sq2_val;
            if (ctx.sq2_index == 384) {
                ctx.sq2_index = 0;
            }
    
            ctx.ch3_history[ctx.ch3_index++] = ch3_val;
            if (ctx.ch3_index == 384) {
                ctx.ch3_index = 0; 
            }
    
            ctx.ch4_history[ctx.ch4_index++] = ch4_val;
            if (ctx.ch4_index == 384) {
                ctx.ch4_index = 0;
            }
            ctx.left_history[ctx.left_index++] = left_out;
            if (ctx.left_index == 384) {
                ctx.left_index = 0;
            }
    
            ctx.right_history[ctx.right_index++] = right_out;
            if (ctx.right_index == 384) {
                ctx.right_index = 0;
            }
        }

        if(ctx.buffer_write_index == FRAMES_PER_BUFFER) {
            float *data[2];
            data[0] = ctx.left_audio_buffer;
            data[1] = ctx.right_audio_buffer;
            //long available = Pa_GetStreamWriteAvailable(stream);
            //if(available > 0) {
            //    if(available > FRAMES_PER_BUFFER) {
            //        available = FRAMES_PER_BUFFER;
            //    }
            //    ctx.buffer_write_index -= available;
                ctx.buffer_write_index = 0;
                int err = Pa_WriteStream(stream, data, FRAMES_PER_BUFFER);
                if(err != paNoError && err != paOutputUnderflowed) {
                    fprintf(stderr, "portaudio stream error\n\tError Number: %d\n\tError Message: %s\n", err, Pa_GetErrorText(err));
                }
            //}
        }
    }
}

bool skipped = false;
void audio_period_tick() {
    ctx.period_tick++;
    if(ctx.period_tick % 2 == 0){
        ctx.sq1_period_timer++;
        ctx.sq2_period_timer++;

        if(ctx.sq1_period_timer >= 0x800) {
            ctx.sq1_period_timer = ctx.sq1_period_reset;
            ctx.sq1_sample = (ctx.sq1_sample + 1) % 8;
        }

        if(ctx.sq2_period_timer >= 0x800) {
            ctx.sq2_period_timer = ctx.sq2_period_reset;
            ctx.sq2_sample = (ctx.sq2_sample + 1) % 8;
        }
    }
    ctx.ch3_period_timer++;
    if(ctx.ch3_period_timer >= 0x800) {
        ctx.ch3_period_timer = ctx.ch3_period_reset;
        ctx.ch3_sample = (ctx.ch3_sample + 1) % 32;
        if((ctx.ch3_sample & 0b1) == 0b1) {
            ctx.ch3_last_sample = ctx.wave_ram[ctx.ch3_sample >> 1] & 0xF;
        } else {
            ctx.ch3_last_sample = ctx.wave_ram[ctx.ch3_sample >> 1] >> 4;
        }
    }
}

void audio_tick(){ 
    u32 prev_ticks = ctx.ticks;
    ctx.ticks++;
    if((ctx.ticks % 2) == 0) {
        if(ctx.sq1_len_enable) {
            ctx.sq1_len++;
            if(ctx.sq1_len >= 64) {
                ctx.sq1_len = 0;
                ctx.sq1_enable = false;
                ctx.ch1_len_fz = true;
            }
        }

        if(ctx.sq2_len_enable) {
            ctx.sq2_len++;
            if(ctx.sq2_len >= 64) {
                ctx.sq2_len = 0;
                ctx.sq2_enable = false;
                ctx.ch2_len_fz = true;
            }
        }

        if(ctx.ch3_len_enable) {
            ctx.ch3_len++;
            if(ctx.ch3_len == 0) {
                ctx.ch3_enable = false;
                ctx.ch3_len_fz = true;
            }
        }

        if(ctx.ch4_len_enable && ctx.ch4_enable) {
            ctx.ch4_len++;
            if(ctx.ch4_len >= 64) {
                ctx.ch4_len = 0;
                ctx.ch4_enable = false;
                ctx.ch4_len_fz = true;
            }
        }
    }

    if((prev_ticks & (1 << 2)) && !(ctx.ticks & (1 << 2))) {
        if(ctx.sq1_env_pace != 0){
            ctx.sq1_env_timer++;
            if(ctx.sq1_env_timer >= ctx.sq1_env_pace) {
                ctx.sq1_env_timer = 0;
                if((ctx.sq1_env_direction && ctx.sq1_volume != 15) || (!ctx.sq1_env_direction && ctx.sq1_volume != 0)){
                    ctx.sq1_volume += ctx.sq1_env_direction ? 1 : -1;
                    if(ctx.sq1_volume < 0)
                        ctx.sq1_volume = 0;
                    if(ctx.sq1_volume > 15)
                        ctx.sq1_volume = 15;
                }
            }
        }

        if(ctx.sq2_env_pace != 0){
            ctx.sq2_env_timer++;
            if(ctx.sq2_env_timer >= ctx.sq2_env_pace) {
                ctx.sq2_env_timer = 0;
                if((ctx.sq2_env_direction && ctx.sq2_volume != 15) || (!ctx.sq2_env_direction && ctx.sq2_volume != 0)){
                    ctx.sq2_volume += ctx.sq2_env_direction ? 1 : -1;
                    if(ctx.sq2_volume < 0)
                        ctx.sq2_volume = 0;
                    if(ctx.sq2_volume > 15)
                        ctx.sq2_volume = 15;
                }
            }
        }

        ctx.ch4_env_timer--;
        if(ctx.ch4_env_timer <= 0) {
            ctx.ch4_env_timer = ctx.ch4_env_pace_buffer != 0 ? ctx.ch4_env_pace_buffer : 8;
            if(ctx.ch4_env_pace != 0 && ((ctx.ch4_env_direction && ctx.ch4_volume != 15) || (!ctx.ch4_env_direction && ctx.ch4_volume != 0))){
                ctx.ch4_volume += ctx.ch4_env_direction ? 1 : -1;
                if(ctx.ch4_volume < 0)
                    ctx.ch4_volume = 0;
                if(ctx.ch4_volume > 15)
                    ctx.ch4_volume = 15;
            }
        }

    }

    if((prev_ticks & (1 << 1)) && !(ctx.ticks & (1 << 1)) && ctx.sq1_sweep_enabled) {
        ctx.sq1_sweep_timer--;
        if(ctx.sq1_sweep_timer <= 0){
            ctx.sq1_sweep_timer = ctx.sq1_sweep_pace != 0 ? ctx.sq1_sweep_pace : 8;
            if(ctx.sq1_enable && ctx.sq1_sweep_pace > 0 && ctx.sq1_sweep_enabled){
                sq1_sweep();
                if(ctx.sq1_calc_period <= 0x7FF && ctx.sq1_sweep_step != 0) {
                    ctx.sq1_sweep_period = ctx.sq1_calc_period;
                    ctx.sq1_period_reset = ctx.sq1_calc_period;
                    sq1_sweep();
                }
            }
        }
    }
}

void enable_square1() {
    if(ctx.ch1_dac){
        ctx.sq1_enable = true;
    }
    ctx.sq1_volume = ctx.sq1_initial_volume;
    ctx.sq1_env_timer = 0;
    ctx.sq1_sweep_period = ctx.sq1_period_reset;
    ctx.sq1_sweep_timer = ctx.sq1_sweep_pace != 0 ? ctx.sq1_sweep_pace : 8;
    ctx.sq1_env_direction = ctx.sq1_env_direction_buffer;
    ctx.sq1_env_pace = ctx.sq1_env_pace_buffer;
    if(ctx.sq1_sweep_step != 0) {
        sq1_sweep();
    }
}

void enable_square2() {
    if(!ctx.ch2_dac){
        return;
    }
    ctx.sq2_enable = true;
    ctx.sq2_volume = ctx.sq2_initial_volume;
    ctx.sq2_env_timer = 0;
    ctx.sq2_sample = 0;
    ctx.sq2_period_timer = ctx.sq2_period_reset;
    ctx.sq2_env_direction = ctx.sq2_env_direction_buffer;
    ctx.sq2_env_pace = ctx.sq2_env_pace_buffer;
}

void enable_wave() {
    if(!ctx.ch3_dac){
        return;
    }
    ctx.ch3_enable = true;
    ctx.ch3_sample = 0;
    ctx.ch3_period_timer = ctx.ch3_period_reset;
}

void enable_noise() {
    ctx.ch4_enable = true;
    ctx.ch4_env_timer = ctx.ch4_env_pace_buffer != 0 ? ctx.ch4_env_pace_buffer : 8;
    ctx.ch4_env_direction = ctx.ch4_env_direction_buffer;
    ctx.ch4_env_pace = ctx.ch4_env_pace_buffer;
    ctx.ch4_volume = ctx.ch4_initial_volume;
    ctx.ch4_lfsr = 0;
    ctx.lfsr_timer = (ctx.ch4_clock_divider > 0 ? (ctx.ch4_clock_divider << 4) : 8) << ctx.ch4_clock_shift;
    if(!ctx.ch4_dac){
        ctx.ch4_enable = false;
    }
}

u8 audio_read(u16 address) {
    //printf("read at %02X\n", address);
    if(address == 0xFF26) {
        u8 value = (ctx.audio_enabled << 7) | (ctx.ch4_enable << 3) | (ctx.ch3_enable << 2) | (ctx.sq2_enable << 1) | (ctx.sq1_enable);
        return value | 0b01110000;
    }
    if(address == 0xFF25) {
        u8 value = 0x0;
        value |= ctx.ch4_left << 7;
        value |= ctx.ch3_left << 6;
        value |= ctx.ch2_left << 5;
        value |= ctx.ch1_left << 4;

        value |= ctx.ch4_right << 3;
        value |= ctx.ch3_right << 2;
        value |= ctx.ch2_right << 1;
        value |= ctx.ch1_right;
        //printf("read at %02X; Value: %02X\n", address, value);
        return value;
    }

    if(address == 0xFF24) {
        u8 value = (ctx.volume_left & 0b111) << 4;
        value |= (ctx.volume_right & 0b111);
        value |= (ctx.vin_left << 7);
        value |= (ctx.vin_right << 3);
        return value;
    }

    if(address == 0xFF10) {
        u8 value = (ctx.sq1_sweep_pace & 0b111) << 4;
        value |= (ctx.sq1_sweep_direction) << 3;
        value |= ctx.sq1_sweep_step & 0b111;
        return value | 0x80;
    }
    if(address == 0xFF11) {
        u8 value = (ctx.sq1_duty & 0b11) << 6;
        return value | 0b00111111;
    }
    if(address == 0xFF12) {
        u8 value = (ctx.sq1_initial_volume & 0b1111) << 4;
        value |= ctx.sq1_env_direction_buffer << 3;
        value |= (ctx.sq1_env_pace_buffer & 0b111);
        return value;
    }
    if(address == 0xFF13) {
        return 0xFF;
    }
    if(address == 0xFF14) {
        return (ctx.sq1_len_enable << 6) | 0b10111111;
    }


    if(address == 0xFF16) {
        u8 value = (ctx.sq2_duty & 0b11) << 6;
        return value | 0b00111111;
    }
    if(address == 0xFF17) {
        u8 value = (ctx.sq2_initial_volume & 0b1111) << 4;
        value |= ctx.sq2_env_direction_buffer << 3;
        value |= (ctx.sq2_env_pace_buffer & 0b111);
        return value;
    }
    if(address == 0xFF18) {
        return 0xFF;
    }
    if(address == 0xFF19) {
        return (ctx.sq2_len_enable << 6) | 0b10111111;
    }

    if(address == 0xFF1A) {
        return (ctx.ch3_dac << 7) | 0b01111111;
    }
    
    if(address == 0xFF1B) {
        return 0xFF;
    }

    if(address == 0xFF1C) {
        return ((ctx.ch3_volume & 0b11) << 5) | 0b10011111;
    }

    if(address == 0xFF1D) {
        return 0xFF;
    }

    if(address == 0xFF1E) {
        return (ctx.ch3_len_enable << 6) | 0b10111111;
    }

    if(address == 0xFF20) {
        return 0xFF;
    }

    if(address == 0xFF21) {
        u8 value = (ctx.ch4_initial_volume & 0b1111) << 4;
        value |= ctx.ch4_env_direction_buffer << 3;
        value |= (ctx.ch4_env_pace_buffer & 0b111);
        return value;
    }

    if(address == 0xFF22) {
        u8 value = (ctx.ch4_clock_shift & 0xF) << 4;
        value |= ctx.ch4_lfsr_width << 3;
        value |= (ctx.ch4_clock_divider & 0b111);
        return value;
    }

    if(address == 0xFF23) {
        return (ctx.ch4_len_enable << 6) | 0b10111111;
    }

    if(BETWEEN(address, 0xFF30, 0xFF3F)) {
        return ctx.wave_ram[address - 0xFF30];
    }
    return 0xFF;
}

void reset_buffers(){
    ctx.ch1_dac = 0;
    ctx.ch2_dac = 0;
    ctx.ch3_dac = 0;
    ctx.ch1_left = false;
    ctx.ch1_right = false;
    ctx.ch2_left = false;
    ctx.ch2_right = false;
    ctx.ch3_left = false;
    ctx.ch3_right = false;
    ctx.ch4_left = false;
    ctx.ch4_right = false;
    ctx.volume_left = 0;
    ctx.volume_right = 0;
    ctx.vin_left = 0;
    ctx.vin_right = 0;
    ctx.sq1_duty = 0;
    ctx.sq1_volume = 0;
    ctx.sq1_sample = 0;
    ctx.sq1_period_reset = 0;
    ctx.sq1_period_timer = 0;
    ctx.sq1_enable = 0;
    ctx.sq1_len_enable = 0;
    ctx.sq1_sweep_pace = 0;
    ctx.sq1_sweep_direction = 0;
    ctx.sq1_initial_len = 0;
    ctx.sq1_len = 0;
    ctx.sq1_initial_volume = 0;
    ctx.sq1_env_direction = 0;
    ctx.sq1_env_direction_buffer = 0;
    ctx.sq1_env_pace = 0;
    ctx.sq1_env_pace_buffer = 0; 
    ctx.sq1_env_timer = 0;
    ctx.sq1_sweep_step = 0;
    ctx.sq1_sweep_timer = 0;
    ctx.sq1_sweep_enabled = 0;
    ctx.sq1_sweep_period = 0;
    ctx.sq1_calc_period = 0;
    ctx.sq2_duty = 0;
    ctx.sq2_volume = 0;
    ctx.sq2_sample = 0;
    ctx.sq2_period_reset = 0;
    ctx.sq2_period_timer = 0;
    ctx.sq2_enable = 0;
    ctx.sq2_len_enable = 0;
    ctx.sq2_initial_len = 0;
    ctx.sq2_len = 0;
    ctx.sq2_initial_volume = 0;
    ctx.sq2_env_direction = 0;
    ctx.sq2_env_direction_buffer = 0;
    ctx.sq2_env_pace = 0;
    ctx.sq2_env_pace_buffer = 0;
    ctx.sq2_env_timer = 0;
    ctx.ch3_enable = 0;
    ctx.ch3_dac = 0;
    ctx.ch3_initial_len = 0;
    ctx.ch3_len = 0;
    ctx.ch3_volume = 0;
    ctx.ch3_initial_volume = 0;
    ctx.ch3_period_timer = 0;
    ctx.ch3_period_reset = 0;
    ctx.ch3_len_enable = 0;
    ctx.ch3_last_sample = 0;
    ctx.ch3_sample = 0;
    ctx.ch4_enable = 0;
    ctx.ch4_initial_len = 0;
    ctx.ch4_len = 0;
    ctx.ch4_initial_volume = 0;
    ctx.ch4_volume = 0;
    ctx.ch4_env_pace = 0;
    ctx.ch4_env_pace_buffer = 0;
    ctx.ch4_env_timer = 0;
    ctx.ch4_env_direction = 0;
    ctx.ch4_env_direction_buffer = 0;
    ctx.ch4_clock_shift = 0;
    ctx.ch4_lfsr_width = 0;
    ctx.ch4_clock_divider = 0;
    ctx.ch4_len_enable = 0;
    ctx.ch4_lfsr = 0;
}

void audio_write(u16 address, u8 value){
    if(BETWEEN(address, 0xFF30, 0xFF3F)) {
        ctx.wave_ram[address - 0xFF30] = value;
    }

    if(address == 0xFF26) {
        ctx.audio_enabled = (value & 0x80) == 0x80;
        if(!ctx.audio_enabled) {
            reset_buffers();
        }
    }

    if(!ctx.audio_enabled){
        return;
    }

    if(address == 0xFF25) {
        ctx.ch4_left = value & 0b10000000;
        ctx.ch3_left = value & 0b01000000;
        ctx.ch2_left = value & 0b00100000;
        ctx.ch1_left = value & 0b00010000;

        ctx.ch4_right = value & 0b00001000;
        ctx.ch3_right = value & 0b00000100;
        ctx.ch2_right = value & 0b00000010;
        ctx.ch1_right = value & 0b00000001;
    }

    if(address == 0xFF24) {
        ctx.volume_left = (value >> 4) & 0b111;
        ctx.volume_right = value & 0b111;
        ctx.vin_left = value & 0x80;
        ctx.vin_right = value & 0x8;
    }

    if(address == 0xFF10) {
        ctx.sq1_sweep_pace = (value >> 4) & 0b111;
        ctx.sq1_sweep_direction = value & 0x08;
        ctx.sq1_sweep_step = value & 0b111;
    	ctx.sq1_sweep_enabled = ((ctx.sq1_sweep_pace != 0) || (ctx.sq1_sweep_step != 0));
    }
    if(address == 0xFF11) {
        ctx.sq1_duty = value >> 6;
        ctx.sq1_initial_len = value & 0x3F;
        ctx.sq1_len = ctx.sq1_initial_len;
        ctx.ch1_len_fz = false;
    }
    if(address == 0xFF12) {
        ctx.sq1_initial_volume = (value >> 4) & 0x0F;
        ctx.sq1_env_direction_buffer = (value & 0x8) == 0x8;
        ctx.sq1_env_pace_buffer = value & 0b111;
        if(ctx.sq1_env_direction_buffer == 0 && ctx.sq1_initial_volume == 0) {
            ctx.sq1_enable = false;
            ctx.ch1_dac = false;
        } else {
            ctx.ch1_dac = true;
        }
    }
    if(address == 0xFF13) {
        ctx.sq1_period_reset = (ctx.sq1_period_reset & 0xF00) | value;
    }
    if(address == 0xFF14) {
        if(ctx.sq1_len == 64) {
            ctx.sq1_len = 0;
        }
        ctx.sq1_period_reset = (ctx.sq1_period_reset & 0x0FF) | ((value & 0b111) << 8);
        bool len_en = (value & 0x40) == 0x40;
        if((ctx.ticks % 2) == 0 && !ctx.ch1_len_fz && !ctx.sq1_len_enable && len_en){
            ctx.sq1_len++;
            if(ctx.sq1_len >= 64) {
                ctx.sq1_len = 0;
                ctx.sq1_enable = false;
                ctx.ch1_len_fz = true;
            }
        } 
        if((ctx.ticks % 2) == 0 && ctx.ch1_len_fz && (value & 0x80) == 0x80){
            ctx.ch1_len_fz = false;
            if(len_en){
                ctx.sq1_len++;
            }
        }
        ctx.sq1_len_enable = len_en;
        if(value & 0x80) {
            enable_square1();
        }
    }


    if(address == 0xFF16) {
        ctx.sq2_duty = value >> 6;
        ctx.sq2_initial_len = value & 0x3F;
        ctx.sq2_len = ctx.sq2_initial_len;
        ctx.ch2_len_fz = false;
    }
    if(address == 0xFF17) {
        ctx.sq2_initial_volume = (value >> 4) & 0x0F;
        ctx.sq2_env_direction_buffer = (value & 0x8) == 0x8;
        ctx.sq2_env_pace_buffer = value & 0b111;
        if(ctx.sq2_env_direction_buffer == 0 && ctx.sq2_initial_volume == 0) {
            ctx.sq2_enable = false;
            ctx.ch2_dac = false;
        } else {
            ctx.ch2_dac = true;
        }
    }
    if(address == 0xFF18) {
        u16 prev_period = ctx.sq2_period_reset;
        ctx.sq2_period_reset = (ctx.sq2_period_reset & 0xF00) | value;
    }
    if(address == 0xFF19) {
        if(ctx.sq2_len == 64) {
            ctx.sq2_len = 0;
        }
        ctx.sq2_period_reset = (ctx.sq2_period_reset & 0x0FF) | ((value & 0b111) << 8);
        bool len_en = (value & 0x40) == 0x40;
        if((ctx.ticks % 2) == 0 && !ctx.ch2_len_fz && !ctx.sq2_len_enable && len_en){
            ctx.sq2_len++;
            if(ctx.sq2_len >= 64) {
                ctx.sq2_len = 0;
                ctx.sq2_enable = false;
                ctx.ch2_len_fz = true;
            }
        } 
        if((ctx.ticks % 2) == 0 && ctx.ch2_len_fz && (value & 0x80) == 0x80){
            ctx.ch2_len_fz = false;
            if(len_en){
                ctx.sq2_len++;
            }
        }
        ctx.sq2_len_enable = len_en;
        if(value & 0x80) {
            enable_square2();
        }
    }

    if(address == 0xFF1A) {
        ctx.ch3_dac = value >> 7;
        if(!ctx.ch3_dac){
            ctx.ch3_enable = false;
        }
    }
    
    if(address == 0xFF1B) {
        ctx.ch3_initial_len = value;
        ctx.ch3_len = ctx.ch3_initial_len;
        ctx.ch3_len_fz = false;
    }

    if(address == 0xFF1C) {
        ctx.ch3_volume = (value >> 5) & 0b11;
    }

    if(address == 0xFF1D) {
        ctx.ch3_period_reset = (ctx.ch3_period_reset & 0xF00) | value;
    }

    if(address == 0xFF1E) {
        ctx.ch3_period_reset = (ctx.ch3_period_reset & 0x0FF) | ((value & 0b111) << 8);
        bool len_en = (value & 0x40) == 0x40;
        if((ctx.ticks % 2) == 0 && !ctx.ch3_len_fz && !ctx.ch3_len_enable && len_en){
            if(ctx.ch3_len >= 255) {
                ctx.ch3_len = 0;
                ctx.ch3_enable = false;
                ctx.ch3_len_fz = true;
            } else {
                ctx.ch3_len++;
            }
        } 
        if((ctx.ticks % 2) == 0 && ctx.ch3_len_fz && (value & 0x80) == 0x80){
            ctx.ch3_len_fz = false;
            if(len_en){
                ctx.ch3_len++;
            }
        }
        ctx.ch3_len_enable = len_en;
        if(value & 0x80) {
            enable_wave();
        }
    }

    if(address == 0xFF20) {
        ctx.ch4_initial_len = value & 0b111111;
        ctx.ch4_len = ctx.ch4_initial_len;
        ctx.ch4_len_fz = false;
    }

    if(address == 0xFF21) {
        ctx.ch4_initial_volume = (value >> 4) & 0x0F;
        ctx.ch4_env_direction_buffer = (value & 0x8) == 0x8;
        ctx.ch4_env_pace_buffer = value & 0b111;
        if(ctx.ch4_env_direction_buffer == 0 && ctx.ch4_initial_volume == 0) {
            ctx.ch4_enable = false;
            ctx.ch4_dac = false;
        } else {
            ctx.ch4_dac = true;
        }
    }

    if(address == 0xFF22) {
        ctx.ch4_clock_shift = (value >> 4) & 0xF;
        ctx.ch4_lfsr_width = (value & 0x08);
        ctx.ch4_clock_divider = value & 0b111;
    }

    if(address == 0xFF23) {
        if(ctx.ch4_len == 64) {
            ctx.ch4_len = 0;
        }
        bool len_en = (value & 0x40) == 0x40;
        if((ctx.ticks % 2) == 0 && !ctx.ch4_len_fz && !ctx.ch4_len_enable && len_en){
            ctx.ch4_len++;
            if(ctx.ch4_len >= 64) {
                ctx.ch4_len = 0;
                ctx.ch4_enable = false;
                ctx.ch4_len_fz = true;
            }
        } 
        if((ctx.ticks % 2) == 0 && ctx.ch4_len_fz && (value & 0x80) == 0x80){
            ctx.ch4_len_fz = false;
            if(len_en){
                ctx.ch4_len++;
            }
        }
        ctx.ch4_len_enable = len_en;
        if(value & 0x80) {
            enable_noise();
        }
    }
}

void audio_save_state(audio_state* state) {
    state->ctx = ctx;
    state->ctx.left_audio_buffer = 0;
    state->ctx.right_audio_buffer = 0;
}

void audio_load_state(const audio_state* state) {
    ctx = state->ctx;
    ctx.left_audio_buffer = malloc(sizeof(float) * FRAMES_PER_BUFFER);
    ctx.right_audio_buffer = malloc(sizeof(float) * FRAMES_PER_BUFFER);
}
