#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ibm.h"

#include "device.h"
#include "dma.h"
#include "io.h"
#include "pic.h"
#include "sound.h"
#include "sound_gus.h"
#include "timer.h"

typedef struct gus_t {
        int reset;

        int global;
        uint32_t addr, dmaaddr;
        int voice;
        uint32_t start[32], end[32], cur[32];
        uint32_t startx[32], endx[32], curx[32];
        int rstart[32], rend[32];
        int rcur[32];
        uint16_t freq[32];
        uint16_t rfreq[32];
        uint8_t ctrl[32];
        uint8_t rctrl[32];
        int curvol[32];
        int pan_l[32], pan_r[32];
        int t1on, t2on;
        uint8_t tctrl;
        uint16_t t1, t2, t1l, t2l;
        uint8_t irqstatus, irqstatus2;
        uint8_t adcommand;
        int waveirqs[32], rampirqs[32];
        int voices;
        uint8_t dmactrl;

        int32_t out_l, out_r;

        int16_t buffer[2][MAXSOUNDBUFLEN];
        int pos;

        pc_timer_t samp_timer;
        uint64_t samp_latch;

        uint8_t *ram;

        int irqnext;

        pc_timer_t timer_1, timer_2;

        int irq, dma, irq_midi;
        int latch_enable;

        uint8_t sb_2xa, sb_2xc, sb_2xe;
        uint8_t sb_ctrl;
        int sb_nmi;

        uint8_t reg_ctrl;

        uint8_t ad_status, ad_data;
        uint8_t ad_timer_ctrl;

        uint8_t midi_ctrl, midi_status;
        uint8_t midi_data;
        int midi_loopback;

        uint8_t gp1, gp2;
        uint16_t gp1_addr, gp2_addr;

        uint8_t usrr;
} gus_t;

static int gus_irqs[8] = {-1, 2, 5, 3, 7, 11, 12, 15};
static int gus_irqs_midi[8] = {-1, 2, 5, 3, 7, 11, 12, 15};
static int gus_dmas[8] = {-1, 1, 3, 5, 6, 7, -1, -1};

int gusfreqs[] = {44100, 41160, 38587, 36317, 34300, 32494, 30870, 29400, 28063, 26843,
                  25725, 24696, 23746, 22866, 22050, 21289, 20580, 19916, 19293};

double vol16bit[4096];

enum { MIDI_INT_RECEIVE = 0x01, MIDI_INT_TRANSMIT = 0x02, MIDI_INT_MASTER = 0x80 };

enum { MIDI_CTRL_TRANSMIT_MASK = 0x60, MIDI_CTRL_TRANSMIT = 0x20, MIDI_CTRL_RECEIVE = 0x80 };

enum { GUS_INT_MIDI_TRANSMIT = 0x01, GUS_INT_MIDI_RECEIVE = 0x02 };

enum { GUS_TIMER_CTRL_AUTO = 0x01 };

void gus_update_int_status(gus_t *gus) {
        int c;
        int irq_pending = 0;
        int midi_irq_pending = 0;

        gus->irqstatus &= ~0x60;
        gus->irqstatus2 = 0xE0;
        for (c = 0; c < 32; c++) {
                if (gus->waveirqs[c]) {
                        //                        gus->waveirqs[c]=0;
                        gus->irqstatus2 = 0x60 | c;
                        if (gus->rampirqs[c])
                                gus->irqstatus2 |= 0x80;
                        gus->irqstatus |= 0x20;
                        //                        printf("  Voice IRQ %i %02X %i\n",c,gus->irqstatus2,ins);
                        irq_pending = 1;
                        break;
                }
                if (gus->rampirqs[c]) {
                        //                        gus->rampirqs[c]=0;
                        gus->irqstatus2 = 0xA0 | c;
                        gus->irqstatus |= 0x40;
                        //                        printf("  Ramp IRQ %i %02X %i\n",c,gus->irqstatus2,ins);
                        irq_pending = 1;
                        break;
                }
        }
        if ((gus->tctrl & 4) && (gus->irqstatus & 0x04))
                irq_pending = 1; /*Timer 1 interrupt pending*/
        if ((gus->tctrl & 8) && (gus->irqstatus & 0x08))
                irq_pending = 1; /*Timer 2 interrupt pending*/
        if ((gus->irqstatus & 0x80) && (gus->dmactrl & 0x20))
                irq_pending = 1; /*DMA TC interrupt pending*/

        midi_irq_pending = gus->midi_status & MIDI_INT_MASTER;

        //        pclog("gus_update_int_status: tctrl=%02x irqstatus=%02x irq_pending=%i midi_irq_pending=%i gus->irq=%i
        //        gus->irq_midi=%i\n",
        //                        gus->tctrl, gus->irqstatus, irq_pending, midi_irq_pending, gus->irq, gus->irq_midi);
        if (gus->irq == gus->irq_midi && gus->irq != -1) {
                if (irq_pending || midi_irq_pending)
                        picintlevel(1 << gus->irq);
                else
                        picintc(1 << gus->irq);
        } else {
                if (gus->irq != -1) {
                        if (irq_pending)
                                picintlevel(1 << gus->irq);
                        else
                                picintc(1 << gus->irq);
                }
                if (gus->irq_midi != -1) {
                        if (midi_irq_pending)
                                picintlevel(1 << gus->irq_midi);
                        else
                                picintc(1 << gus->irq_midi);
                }
        }
}

void gus_midi_update_int_status(gus_t *gus) {
        gus->midi_status &= ~MIDI_INT_MASTER;
        if ((gus->midi_ctrl & MIDI_CTRL_TRANSMIT_MASK) == MIDI_CTRL_TRANSMIT && (gus->midi_status & MIDI_INT_TRANSMIT)) {
                gus->midi_status |= MIDI_INT_MASTER;
                gus->irqstatus |= GUS_INT_MIDI_TRANSMIT;
        } else
                gus->irqstatus &= ~GUS_INT_MIDI_TRANSMIT;

        if ((gus->midi_ctrl & MIDI_CTRL_RECEIVE) && (gus->midi_status & MIDI_INT_RECEIVE)) {
                gus->midi_status |= MIDI_INT_MASTER;
                gus->irqstatus |= GUS_INT_MIDI_RECEIVE;
        } else
                gus->irqstatus &= ~GUS_INT_MIDI_RECEIVE;

        gus_update_int_status(gus);
}

void writegus(uint16_t addr, uint8_t val, void *p) {
        gus_t *gus = (gus_t *)p;
        int c, d;
        int old;
        //        pclog("Write GUS %04X %02X %04X:%04X\n",addr,val,CS,pc);
        if (gus->latch_enable && addr != 0x24b)
                gus->latch_enable = 0;
        switch (addr) {
        case 0x340: /*MIDI control*/
                old = gus->midi_ctrl;
                gus->midi_ctrl = val;

                if ((val & 3) == 3)
                        gus->midi_status = 0;
                else if ((old & 3) == 3) {
                        gus->midi_status |= MIDI_INT_TRANSMIT;
                        //                        pclog("MIDI_INT_TRANSMIT\n");
                }
                gus_midi_update_int_status(gus);
                break;

        case 0x341: /*MIDI data*/
                if (gus->midi_loopback) {
                        gus->midi_status |= MIDI_INT_RECEIVE;
                        gus->midi_data = val;
                } else
                        gus->midi_status |= MIDI_INT_TRANSMIT;
                break;

        case 0x342: /*Voice select*/
                gus->voice = val & 31;
                break;
        case 0x343: /*Global select*/
                gus->global = val;
                break;
        case 0x344: /*Global low*/
                    //                if (gus->global!=0x43 && gus->global!=0x44) printf("Writing register %02X %02X %02X
                    //                %i\n",gus->global,gus->voice,val, ins);
                switch (gus->global) {
                case 0: /*Voice control*/
                        //                        if (val&1 && !(gus->ctrl[gus->voice]&1)) printf("Voice on %i\n",gus->voice);
                        gus->ctrl[gus->voice] = val;
                        break;
                case 1: /*Frequency control*/
                        gus->freq[gus->voice] = (gus->freq[gus->voice] & 0xFF00) | val;
                        break;
                case 2: /*Start addr high*/
                        gus->startx[gus->voice] = (gus->startx[gus->voice] & 0xF807F) | (val << 7);
                        gus->start[gus->voice] = (gus->start[gus->voice] & 0x1F00FFFF) | (val << 16);
                        //                        printf("Write %i start %08X
                        //                        %08X\n",gus->voice,gus->start[gus->voice],gus->startx[gus->voice]);
                        break;
                case 3: /*Start addr low*/
                        gus->start[gus->voice] = (gus->start[gus->voice] & 0x1FFFFF00) | val;
                        //                        printf("Write %i start %08X
                        //                        %08X\n",gus->voice,gus->start[gus->voice],gus->startx[gus->voice]);
                        break;
                case 4: /*End addr high*/
                        gus->endx[gus->voice] = (gus->endx[gus->voice] & 0xF807F) | (val << 7);
                        gus->end[gus->voice] = (gus->end[gus->voice] & 0x1F00FFFF) | (val << 16);
                        //                        printf("Write %i end %08X
                        //                        %08X\n",gus->voice,gus->end[gus->voice],gus->endx[gus->voice]);
                        break;
                case 5: /*End addr low*/
                        gus->end[gus->voice] = (gus->end[gus->voice] & 0x1FFFFF00) | val;
                        //                        printf("Write %i end %08X
                        //                        %08X\n",gus->voice,gus->end[gus->voice],gus->endx[gus->voice]);
                        break;

                case 0x6: /*Ramp frequency*/
                        gus->rfreq[gus->voice] = (int)((double)((val & 63) * 512) / (double)(1 << (3 * (val >> 6))));
                        //                        printf("RFREQ %02X %i %i
                        //                        %f\n",val,gus->voice,gus->rfreq[gus->voice],(double)(val & 63)/(double)(1 <<
                        //                        (3*(val >> 6))));
                        break;

                case 0x9: /*Current volume*/
                        gus->curvol[gus->voice] = gus->rcur[gus->voice] = (gus->rcur[gus->voice] & ~(0xff << 6)) | (val << 6);
                        //                        printf("Vol %i is %04X\n",gus->voice,gus->curvol[gus->voice]);
                        break;

                case 0xA: /*Current addr high*/
                        gus->cur[gus->voice] = (gus->cur[gus->voice] & 0x1F00FFFF) | (val << 16);
                        gus->curx[gus->voice] = (gus->curx[gus->voice] & 0xF807F00) | ((val << 7) << 8);
                        //                      gus->cur[gus->voice]=(gus->cur[gus->voice]&0x0F807F00)|((val<<7)<<8);
                        //                        printf("Write %i cur
                        //                        %08X\n",gus->voice,gus->cur[gus->voice],gus->curx[gus->voice]);
                        break;
                case 0xB: /*Current addr low*/
                        gus->cur[gus->voice] = (gus->cur[gus->voice] & 0x1FFFFF00) | val;
                        //                        printf("Write %i cur
                        //                        %08X\n",gus->voice,gus->cur[gus->voice],gus->curx[gus->voice]);
                        break;

                case 0x42: /*DMA address low*/
                        gus->dmaaddr = (gus->dmaaddr & 0xFF000) | (val << 4);
                        break;

                case 0x43: /*Address low*/
                        gus->addr = (gus->addr & 0xFFF00) | val;
                        break;
                case 0x45: /*Timer control*/
                           //                        printf("Timer control %02X\n",val);
                        gus->tctrl = val;
                        gus_update_int_status(gus);
                        break;
                }
                break;
        case 0x345: /*Global high*/
                //                if (gus->global!=0x43 && gus->global!=0x44) printf("HWriting register %02X %02X %02X %04X:%04X
                //                %i %X\n",gus->global,gus->voice,val,CS,pc, ins, gus->rcur[1] >> 10);
                switch (gus->global) {
                case 0: /*Voice control*/
                        if (!(val & 1) && gus->ctrl[gus->voice] & 1) {
                                //                                printf("Voice on %i - start %05X end %05X freq
                                //                                %04X\n",gus->voice,gus->start[gus->voice],gus->end[gus->voice],gus->freq[gus->voice]);
                                //                                if (val&0x40) gus->cur[gus->voice]=gus->end[gus->voice]<<8;
                                //                                else          gus->cur[gus->voice]=gus->start[gus->voice]<<8;
                        }

                        gus->ctrl[gus->voice] = val & 0x7f;

                        old = gus->waveirqs[gus->voice];
                        gus->waveirqs[gus->voice] = ((val & 0xa0) == 0xa0) ? 1 : 0;
                        if (gus->waveirqs[gus->voice] != old)
                                gus_update_int_status(gus);
                        break;
                case 1: /*Frequency control*/
                        gus->freq[gus->voice] = (gus->freq[gus->voice] & 0xFF) | (val << 8);
                        break;
                case 2: /*Start addr high*/
                        gus->startx[gus->voice] = (gus->startx[gus->voice] & 0x07FFF) | (val << 15);
                        gus->start[gus->voice] = (gus->start[gus->voice] & 0x00FFFFFF) | ((val & 0x1F) << 24);
                        //                        printf("Write %i start %08X %08X
                        //                        %02X\n",gus->voice,gus->start[gus->voice],gus->startx[gus->voice],val);
                        break;
                case 3: /*Start addr low*/
                        gus->startx[gus->voice] = (gus->startx[gus->voice] & 0xFFF80) | (val & 0x7F);
                        gus->start[gus->voice] = (gus->start[gus->voice] & 0x1FFF00FF) | (val << 8);
                        //                        printf("Write %i start %08X
                        //                        %08X\n",gus->voice,gus->start[gus->voice],gus->startx[gus->voice]);
                        break;
                case 4: /*End addr high*/
                        gus->endx[gus->voice] = (gus->endx[gus->voice] & 0x07FFF) | (val << 15);
                        gus->end[gus->voice] = (gus->end[gus->voice] & 0x00FFFFFF) | ((val & 0x1F) << 24);
                        //                        printf("Write %i end %08X %08X
                        //                        %02X\n",gus->voice,gus->end[gus->voice],gus->endx[gus->voice],val);
                        break;
                case 5: /*End addr low*/
                        gus->endx[gus->voice] = (gus->endx[gus->voice] & 0xFFF80) | (val & 0x7F);
                        gus->end[gus->voice] = (gus->end[gus->voice] & 0x1FFF00FF) | (val << 8);
                        //                        printf("Write %i end %08X
                        //                        %08X\n",gus->voice,gus->end[gus->voice],gus->endx[gus->voice]);
                        break;

                case 0x6: /*Ramp frequency*/
                        gus->rfreq[gus->voice] = (int)((double)((val & 63) * (1 << 10)) / (double)(1 << (3 * (val >> 6))));
                        //                        pclog("Ramp freq %02X %i %i %f %i\n", val, gus->voice, gus->rfreq[gus->voice],
                        //                        (double)(val & 63)/(double)(1 << (3*(val >> 6))), ins);
                        break;
                case 0x7: /*Ramp start*/
                        gus->rstart[gus->voice] = val << 14;
                        //                        pclog("Ramp start %04X\n", gus->rstart[gus->voice] >> 10);
                        break;
                case 0x8: /*Ramp end*/
                        gus->rend[gus->voice] = val << 14;
                        //                        pclog("Ramp end %04X\n", gus->rend[gus->voice] >> 10);
                        break;
                case 0x9: /*Current volume*/
                        gus->curvol[gus->voice] = gus->rcur[gus->voice] = (gus->rcur[gus->voice] & ~(0xff << 14)) | (val << 14);
                        //                        printf("Vol %i is %04X\n",gus->voice,gus->curvol[gus->voice]);
                        break;

                case 0xA: /*Current addr high*/
                        gus->cur[gus->voice] = (gus->cur[gus->voice] & 0x00FFFFFF) | ((val & 0x1F) << 24);
                        gus->curx[gus->voice] = (gus->curx[gus->voice] & 0x07FFF00) | ((val << 15) << 8);
                        //                        printf("Write %i cur %08X %08X
                        //                        %02X\n",gus->voice,gus->cur[gus->voice],gus->curx[gus->voice],val);
                        //                      gus->cur[gus->voice]=(gus->cur[gus->voice]&0x007FFF00)|((val<<15)<<8);
                        break;
                case 0xB: /*Current addr low*/
                        gus->cur[gus->voice] = (gus->cur[gus->voice] & 0x1FFF00FF) | (val << 8);
                        gus->curx[gus->voice] = (gus->curx[gus->voice] & 0xFFF8000) | ((val & 0x7F) << 8);
                        //                      gus->cur[gus->voice]=(gus->cur[gus->voice]&0x0FFF8000)|((val&0x7F)<<8);
                        //                        printf("Write %i cur %08X
                        //                        %08X\n",gus->voice,gus->cur[gus->voice],gus->curx[gus->voice]);
                        break;
                case 0xC: /*Pan*/
                        gus->pan_l[gus->voice] = 15 - (val & 0xf);
                        gus->pan_r[gus->voice] = (val & 0xf);
                        break;
                case 0xD: /*Ramp control*/
                        old = gus->rampirqs[gus->voice];
                        gus->rctrl[gus->voice] = val & 0x7F;
                        gus->rampirqs[gus->voice] = ((val & 0xa0) == 0xa0) ? 1 : 0;
                        if (gus->rampirqs[gus->voice] != old)
                                gus_update_int_status(gus);
                        //                        printf("Ramp control %02i %02X %02X
                        //                        %i\n",gus->voice,val,gus->rampirqs[gus->voice],ins);
                        break;

                case 0xE:
                        gus->voices = (val & 63) + 1;
                        if (gus->voices > 32)
                                gus->voices = 32;
                        if (gus->voices < 14)
                                gus->voices = 14;
                        gus->global = val;
                        //                        printf("GUS voices %i\n",val&31);
                        if (gus->voices < 14)
                                gus->samp_latch = (uint64_t)(TIMER_USEC * (1000000.0 / 44100.0));
                        else
                                gus->samp_latch = (uint64_t)(TIMER_USEC * (1000000.0 / gusfreqs[gus->voices - 14]));
                        break;

                case 0x41: /*DMA*/
                        if (val & 1 && gus->dma != -1) {
                                //                                printf("DMA start! %05X %02X\n",gus->dmaaddr,val);
                                if (val & 2) {
                                        c = 0;
                                        while (c < 65536) {
                                                int dma_result;
                                                if (val & 0x04) {
                                                        uint32_t gus_addr =
                                                                (gus->dmaaddr & 0xc0000) | ((gus->dmaaddr & 0x1ffff) << 1);
                                                        d = gus->ram[gus_addr] | (gus->ram[gus_addr + 1] << 8);
                                                        if (val & 0x80)
                                                                d ^= 0x8080;
                                                        dma_result = dma_channel_write(gus->dma, d);
                                                        if (dma_result == DMA_NODATA)
                                                                break;
                                                } else {
                                                        d = gus->ram[gus->dmaaddr];
                                                        if (val & 0x80)
                                                                d ^= 0x80;
                                                        dma_result = dma_channel_write(gus->dma, d);
                                                        if (dma_result == DMA_NODATA)
                                                                break;
                                                }
                                                gus->dmaaddr++;
                                                gus->dmaaddr &= 0xFFFFF;
                                                c++;
                                                if (dma_result & DMA_OVER)
                                                        break;
                                        }
                                        //                                        printf("GUS->MEM Transferred %i bytes\n",c);
                                        gus->dmactrl = val & ~0x40;
                                        gus->irqnext = 1;
                                } else {
                                        c = 0;
                                        while (c < 65536) {
                                                d = dma_channel_read(gus->dma);
                                                if (d == DMA_NODATA)
                                                        break;
                                                if (val & 0x04) {
                                                        uint32_t gus_addr =
                                                                (gus->dmaaddr & 0xc0000) | ((gus->dmaaddr & 0x1ffff) << 1);
                                                        if (val & 0x80)
                                                                d ^= 0x8080;
                                                        gus->ram[gus_addr] = d & 0xff;
                                                        gus->ram[gus_addr + 1] = (d >> 8) & 0xff;
                                                } else {
                                                        if (val & 0x80)
                                                                d ^= 0x80;
                                                        gus->ram[gus->dmaaddr] = d;
                                                }
                                                gus->dmaaddr++;
                                                gus->dmaaddr &= 0xFFFFF;
                                                c++;
                                                if (d & DMA_OVER)
                                                        break;
                                        }
                                        //                                        printf("MEM->GUS Transferred %i bytes\n",c);
                                        gus->dmactrl = val & ~0x40;
                                        gus->irqnext = 1;
                                }
                                //                                exit(-1);
                        }
                        break;

                case 0x42: /*DMA address low*/
                        gus->dmaaddr = (gus->dmaaddr & 0xFF0) | (val << 12);
                        break;

                case 0x43: /*Address low*/
                        gus->addr = (gus->addr & 0xF00FF) | (val << 8);
                        break;
                case 0x44: /*Address high*/
                        gus->addr = (gus->addr & 0xFFFF) | ((val << 16) & 0xF0000);
                        break;
                case 0x45: /*Timer control*/
                        if (!(val & 4))
                                gus->irqstatus &= ~4;
                        if (!(val & 8))
                                gus->irqstatus &= ~8;
                        if (!(val & 0x20)) {
                                gus->ad_status &= ~0x18;
                                nmi = 0;
                        }
                        if (!(val & 0x02)) {
                                gus->ad_status &= ~0x01;
                                nmi = 0;
                        }
                        //                        printf("Timer control %02X\n",val);
                        /*                        if ((val&4) && !(gus->tctrl&4))
                                                {
                                                        gus->t1=gus->t1l;
                                                        gus->t1on=1;
                                                }*/
                        gus->tctrl = val;
                        gus->sb_ctrl = val;
                        gus_update_int_status(gus);
                        break;
                case 0x46: /*Timer 1*/
                        gus->t1 = gus->t1l = val;
                        gus->t1on = 1;
                        //                        printf("GUS timer 1 %i\n",val);
                        break;
                case 0x47: /*Timer 2*/
                        gus->t2 = gus->t2l = val;
                        gus->t2on = 1;
                        //                        printf("GUS timer 2 %i\n",val);
                        break;

                case 0x4c: /*Reset*/
                        gus->reset = val;
                        break;
                }
                break;
        case 0x347: /*DRAM access*/
                gus->ram[gus->addr] = val;
                //                pclog("GUS RAM write %05X %02X\n",gus->addr,val);
                gus->addr &= 0xFFFFF;
                break;
        case 0x248:
        case 0x388:
                gus->adcommand = val;
                //                pclog("Setting ad command %02X %02X %p\n", val, gus->adcommand, &gus->adcommand);
                break;

        case 0x389:
                if ((gus->tctrl & GUS_TIMER_CTRL_AUTO) || gus->adcommand != 4) {
                        gus->ad_data = val;
                        gus->ad_status |= 0x01;
                        if (gus->sb_ctrl & 0x02) {
                                if (gus->sb_nmi)
                                        nmi = 1;
                                else if (gus->irq != -1)
                                        picint(1 << gus->irq);
                        }
                } else if (!(gus->tctrl & GUS_TIMER_CTRL_AUTO) && gus->adcommand == 4) {
                        if (val & 0x80) {
                                gus->ad_status &= ~0x60;
                        } else {
                                gus->ad_timer_ctrl = val;

                                if (val & 0x01)
                                        gus->t1on = 1;
                                else
                                        gus->t1 = gus->t1l;

                                if (val & 0x02)
                                        gus->t2on = 1;
                                else
                                        gus->t2 = gus->t2l;
                        }
                }
                break;

        case 0x240:
                gus->midi_loopback = val & 0x20;
                gus->latch_enable = (val & 0x40) ? 2 : 1;
                break;

        case 0x24b:
                switch (gus->reg_ctrl & 0x07) {
                case 0:
                        if (gus->latch_enable == 1)
                                gus->dma = gus_dmas[val & 7];
                        if (gus->latch_enable == 2) {
                                gus->irq = gus_irqs[val & 7];

                                if (val & 0x40) {
                                        if (gus->irq == -1)
                                                gus->irq = gus->irq_midi = gus_irqs[(val >> 3) & 7];
                                        else
                                                gus->irq_midi = gus->irq;
                                } else
                                        gus->irq_midi = gus_irqs_midi[(val >> 3) & 7];

                                gus->sb_nmi = val & 0x80;
                        }
                        gus->latch_enable = 0;
                        //                        pclog("IRQ %i DMA %i\n", gus->irq, gus->dma);
                        break;
                case 1:
                        gus->gp1 = val;
                        break;
                case 2:
                        gus->gp2 = val;
                        break;
                case 3:
                        gus->gp1_addr = val;
                        break;
                case 4:
                        gus->gp2_addr = val;
                        break;
                case 5:
                        gus->usrr = 0;
                        break;
                case 6:
                        break;
                }
                break;

        case 0x246:
                gus->ad_status |= 0x08;
                if (gus->sb_ctrl & 0x20) {
                        if (gus->sb_nmi)
                                nmi = 1;
                        else if (gus->irq != -1)
                                picint(1 << gus->irq);
                }
                break;
        case 0x24a:
                gus->sb_2xa = val;
                break;
        case 0x24c:
                gus->ad_status |= 0x10;
                if (gus->sb_ctrl & 0x20) {
                        if (gus->sb_nmi)
                                nmi = 1;
                        else if (gus->irq != -1)
                                picint(1 << gus->irq);
                }
        case 0x24d:
                gus->sb_2xc = val;
                break;
        case 0x24e:
                gus->sb_2xe = val;
                break;
        case 0x24f:
                gus->reg_ctrl = val;
                break;
        }
}

uint8_t readgus(uint16_t addr, void *p) {
        gus_t *gus = (gus_t *)p;
        uint8_t val = 0xff;
        //        /*if (addr!=0x246) */printf("Read GUS %04X %04X(%06X):%04X %02X\n",addr,CS,cs,pc,gus->global);
        switch (addr) {
        case 0x340: /*MIDI status*/
                val = gus->midi_status;
                //                pclog("Read MIDI status %02X\n", val);
                break;

        case 0x341: /*MIDI data*/
                val = gus->midi_data;
                gus->midi_status &= ~MIDI_INT_RECEIVE;
                gus_midi_update_int_status(gus);
                break;

        case 0x240:
                return 0;
        case 0x246: /*IRQ status*/
                val = gus->irqstatus & ~0x10;
                if (gus->ad_status & 0x19)
                        val |= 0x10;
                //                pclog("Read IRQ status %02X\n", val);
                return val;

        case 0x24F:
                return 0;
        case 0x342:
                return gus->voice;
        case 0x343:
                return gus->global;
        case 0x344: /*Global low*/
                    //                /*if (gus->global!=0x43 && gus->global!=0x44) */printf("Reading register %02X
                    //                %02X\n",gus->global,gus->voice);
                switch (gus->global) {
                case 0x82: /*Start addr high*/
                        return gus->start[gus->voice] >> 16;
                case 0x83: /*Start addr low*/
                        return gus->start[gus->voice] & 0xFF;

                case 0x89: /*Current volume*/
                        return gus->rcur[gus->voice] >> 6;
                case 0x8A: /*Current addr high*/
                        return gus->cur[gus->voice] >> 16;
                case 0x8B: /*Current addr low*/
                        return gus->cur[gus->voice] & 0xFF;

                case 0x8F: /*IRQ status*/
                        val = gus->irqstatus2;
                        //                        pclog("Read IRQ status - %02X\n",val);
                        gus->rampirqs[gus->irqstatus2 & 0x1F] = 0;
                        gus->waveirqs[gus->irqstatus2 & 0x1F] = 0;
                        gus_update_int_status(gus);
                        return val;

                case 0x00:
                case 0x01:
                case 0x02:
                case 0x03:
                case 0x04:
                case 0x05:
                case 0x06:
                case 0x07:
                case 0x08:
                case 0x09:
                case 0x0a:
                case 0x0b:
                case 0x0c:
                case 0x0d:
                case 0x0e:
                case 0x0f:
                        val = 0xff;
                        break;

                        //                        default:
                        //                        fatal("Bad GUS global low read %02X\n",gus->global);
                }
                break;
        case 0x345: /*Global high*/
                    //                /*if (gus->global!=0x43 && gus->global!=0x44) */printf("HReading register %02X
                    //                %02X\n",gus->global,gus->voice);
                switch (gus->global) {
                case 0x80: /*Voice control*/
                           //                        pclog("Read voice control %02i %02X\n", gus->voice,
                           //                        gus->ctrl[gus->voice]|(gus->waveirqs[gus->voice]?0x80:0));
                        return gus->ctrl[gus->voice] | (gus->waveirqs[gus->voice] ? 0x80 : 0);

                case 0x82: /*Start addr high*/
                        return gus->start[gus->voice] >> 24;
                case 0x83: /*Start addr low*/
                        return gus->start[gus->voice] >> 8;

                case 0x89: /*Current volume*/
                           //                        pclog("Read current volume %i\n", gus->rcur[gus->voice] >> 14);
                        return gus->rcur[gus->voice] >> 14;

                case 0x8A: /*Current addr high*/
                        return gus->cur[gus->voice] >> 24;
                case 0x8B: /*Current addr low*/
                        return gus->cur[gus->voice] >> 8;

                case 0x8C: /*Pan*/
                        return gus->pan_r[gus->voice];

                case 0x8D:
                        //                        pclog("Read ramp control %02X %04X %08X  %08X
                        //                        %08X\n",gus->rctrl[gus->voice]|(gus->rampirqs[gus->voice]?0x80:0),gus->rcur[gus->voice]
                        //                        >> 14,gus->rfreq[gus->voice],gus->rstart[gus->voice],gus->rend[gus->voice]);
                        return gus->rctrl[gus->voice] | (gus->rampirqs[gus->voice] ? 0x80 : 0);

                case 0x8F: /*IRQ status*/
                           //                        pclog("Read IRQ 1\n");
                        val = gus->irqstatus2;
                        gus->rampirqs[gus->irqstatus2 & 0x1F] = 0;
                        gus->waveirqs[gus->irqstatus2 & 0x1F] = 0;
                        gus_update_int_status(gus);
                        //                        pclog("Read IRQ status - %02X  %i %i\n",val,
                        //                        gus->waveirqs[gus->irqstatus2&0x1F], gus->rampirqs[gus->irqstatus2&0x1F]);
                        return val;

                case 0x41: /*DMA control*/
                        val = gus->dmactrl | ((gus->irqstatus & 0x80) ? 0x40 : 0);
                        gus->irqstatus &= ~0x80;
                        return val;
                case 0x45: /*Timer control*/
                        return gus->tctrl;
                case 0x49: /*Sampling control*/
                        return 0;

                case 0x00:
                case 0x01:
                case 0x02:
                case 0x03:
                case 0x04:
                case 0x05:
                case 0x06:
                case 0x07:
                case 0x08:
                case 0x09:
                case 0x0a:
                case 0x0b:
                case 0x0c:
                case 0x0d:
                case 0x0e:
                case 0x0f:
                        val = 0xff;
                        break;

                        //                        default:
                        //                        fatal("Bad GUS global high read %02X\n",gus->global);
                }
                break;
        case 0x346:
                return 0xff;
        case 0x347: /*DRAM access*/
                val = gus->ram[gus->addr];
                //                pclog("GUS RAM read %05X %02X\n",gus->addr,val);
                gus->addr &= 0xFFFFF;
                return val;
        case 0x349:
                return 0;
        case 0x746:          /*Revision level*/
                return 0xff; /*Pre 3.7 - no mixer*/

        case 0x24b:
                switch (gus->reg_ctrl & 0x07) {
                case 1:
                        val = gus->gp1;
                        break;
                case 2:
                        val = gus->gp2;
                        break;
                case 3:
                        val = gus->gp1_addr;
                        break;
                case 4:
                        val = gus->gp2_addr;
                        break;
                }
                break;

        case 0x24c:
                val = gus->sb_2xc;
                if (gus->reg_ctrl & 0x20)
                        gus->sb_2xc &= 0x80;
                break;
        case 0x24e:
                /*                gus->ad_status |= 0x10;
                                if (gus->reg_ctrl & 0x80)
                                {
                                        gus->reg_ctrl_r |= 0x80;
                                        if (gus->sb_nmi)
                                                nmi = 1;
                                        else
                                                picint(1 << gus->irq);
                                }*/
                return gus->sb_2xe;

        case 0x248:
        case 0x388:
                //                pclog("Read ad_status %02X\n", gus->ad_status);
                if (gus->tctrl & GUS_TIMER_CTRL_AUTO)
                        val = gus->sb_2xa;
                else {
                        val = gus->ad_status & ~(gus->ad_timer_ctrl & 0x60);
                        if (val & 0x60)
                                val |= 0x80;
                }
                break;

        case 0x249:
                gus->ad_status &= ~0x01;
                nmi = 0;
        case 0x389:
                val = gus->ad_data;
                break;

        case 0x24A:
                val = gus->adcommand;
                //                pclog("Read ad command %02X %02X %p\n", gus->adcommand, val, &gus->adcommand);
                break;
        }
        //        printf("Bad GUS read %04X! %02X\n",addr,gus->global);
        //        exit(-1);
        return val;
}

void gus_poll_timer_1(void *p) {
        gus_t *gus = (gus_t *)p;

        timer_advance_u64(&gus->timer_1, (uint64_t)(TIMER_USEC * 80));
        //	pclog("gus_poll_timer_1 %i %i  %i %i %02X\n", gustime, gus->t1on, gus->t1, gus->t1l, gus->tctrl);
        if (gus->t1on) {
                gus->t1++;
                if (gus->t1 > 0xFF) {
                        //                        gus->t1on=0;
                        gus->t1 = gus->t1l;
                        gus->ad_status |= 0x40;
                        if (gus->tctrl & 4) {
                                gus->ad_status |= 0x04;
                                gus->irqstatus |= 0x04;
                                //                                pclog("GUS T1 IRQ!\n");
                        }
                }
        }
        if (gus->irqnext) {
                //                pclog("Take IRQ\n");
                gus->irqnext = 0;
                gus->irqstatus |= 0x80;
        }
        gus_midi_update_int_status(gus);
        gus_update_int_status(gus);
}

void gus_poll_timer_2(void *p) {
        gus_t *gus = (gus_t *)p;

        timer_advance_u64(&gus->timer_2, (uint64_t)(TIMER_USEC * 320));
        //	pclog("pollgus2 %i %i  %i %i %02X\n", gustime, gus->t2on, gus->t2, gus->t2l, gus->tctrl);
        if (gus->t2on) {
                gus->t2++;
                if (gus->t2 > 0xFF) {
                        //                        gus->t2on=0;
                        gus->t2 = gus->t2l;
                        gus->ad_status |= 0x20;
                        if (gus->tctrl & 8) {
                                gus->ad_status |= 0x02;
                                gus->irqstatus |= 0x08;
                                //                                pclog("GUS T2 IRQ!\n");
                        }
                }
        }
        if (gus->irqnext) {
                //                pclog("Take IRQ\n");
                gus->irqnext = 0;
                gus->irqstatus |= 0x80;
        }
        gus_update_int_status(gus);
}

static void gus_update(gus_t *gus) {
        for (; gus->pos < sound_pos_global; gus->pos++) {
                if (gus->out_l < -32768)
                        gus->buffer[0][gus->pos] = -32768;
                else if (gus->out_l > 32767)
                        gus->buffer[0][gus->pos] = 32767;
                else
                        gus->buffer[0][gus->pos] = gus->out_l;
                if (gus->out_r < -32768)
                        gus->buffer[1][gus->pos] = -32768;
                else if (gus->out_r > 32767)
                        gus->buffer[1][gus->pos] = 32767;
                else
                        gus->buffer[1][gus->pos] = gus->out_r;
        }
}

void gus_poll_wave(void *p) {
        gus_t *gus = (gus_t *)p;
        uint32_t addr;
        int d;
        int16_t v;
        int32_t vl;
        int update_irqs = 0;

        gus_update(gus);

        timer_advance_u64(&gus->samp_timer, gus->samp_latch);

        gus->out_l = gus->out_r = 0;

        if ((gus->reset & 3) != 3)
                return;
        // pclog("gus_poll_wave\n");
        for (d = 0; d < 32; d++) {
                if (!(gus->ctrl[d] & 3)) {
                        if (gus->ctrl[d] & 4) {
                                addr = gus->cur[d] >> 9;
                                addr = (addr & 0xC0000) | ((addr << 1) & 0x3FFFE);
                                if (!(gus->freq[d] >> 10)) /*Interpolate*/
                                {
                                        vl = (int16_t)(int8_t)((gus->ram[(addr + 1) & 0xFFFFF] ^ 0x80) - 0x80) *
                                             (511 - (gus->cur[d] & 511));
                                        vl += (int16_t)(int8_t)((gus->ram[(addr + 3) & 0xFFFFF] ^ 0x80) - 0x80) *
                                              (gus->cur[d] & 511);
                                        v = vl >> 9;
                                } else
                                        v = (int16_t)(int8_t)((gus->ram[(addr + 1) & 0xFFFFF] ^ 0x80) - 0x80);
                        } else {
                                if (!(gus->freq[d] >> 10)) /*Interpolate*/
                                {
                                        vl = ((int8_t)((gus->ram[(gus->cur[d] >> 9) & 0xFFFFF] ^ 0x80) - 0x80)) *
                                             (511 - (gus->cur[d] & 511));
                                        vl += ((int8_t)((gus->ram[((gus->cur[d] >> 9) + 1) & 0xFFFFF] ^ 0x80) - 0x80)) *
                                              (gus->cur[d] & 511);
                                        v = vl >> 9;
                                } else
                                        v = (int16_t)(int8_t)((gus->ram[(gus->cur[d] >> 9) & 0xFFFFF] ^ 0x80) - 0x80);
                        }

                        //                        pclog("Voice %i : %04X %05X %04X ", d, v, gus->cur[d] >> 9, gus->rcur[d] >> 10);
                        if ((gus->rcur[d] >> 14) > 4095)
                                v = (int16_t)(float)(v)*24.0 * vol16bit[4095];
                        else
                                v = (int16_t)(float)(v)*24.0 * vol16bit[(gus->rcur[d] >> 10) & 4095];
                        //                        pclog("%f %04X\n", vol16bit[(gus->rcur[d]>>10) & 4095], v);

                        gus->out_l += (v * gus->pan_l[d]) / 7;
                        gus->out_r += (v * gus->pan_r[d]) / 7;

                        if (gus->ctrl[d] & 0x40) {
                                gus->cur[d] -= (gus->freq[d] >> 1);
                                if (gus->cur[d] <= gus->start[d]) {
                                        int diff = gus->start[d] - gus->cur[d];

                                        if (gus->ctrl[d] & 8) {
                                                if (gus->ctrl[d] & 0x10)
                                                        gus->ctrl[d] ^= 0x40;
                                                gus->cur[d] =
                                                        (gus->ctrl[d] & 0x40) ? (gus->end[d] - diff) : (gus->start[d] + diff);
                                        } else if (!(gus->rctrl[d] & 4)) {
                                                gus->ctrl[d] |= 1;
                                                gus->cur[d] = (gus->ctrl[d] & 0x40) ? gus->end[d] : gus->start[d];
                                        }

                                        if ((gus->ctrl[d] & 0x20) && !gus->waveirqs[d]) {
                                                gus->waveirqs[d] = 1;
                                                update_irqs = 1;
                                                //                                                pclog("Causing wave IRQ %02X
                                                //                                                %i\n", gus->ctrl[d], d);
                                        }
                                }
                        } else {
                                gus->cur[d] += (gus->freq[d] >> 1);

                                if (gus->cur[d] >= gus->end[d]) {
                                        int diff = gus->cur[d] - gus->end[d];

                                        if (gus->ctrl[d] & 8) {
                                                if (gus->ctrl[d] & 0x10)
                                                        gus->ctrl[d] ^= 0x40;
                                                gus->cur[d] =
                                                        (gus->ctrl[d] & 0x40) ? (gus->end[d] - diff) : (gus->start[d] + diff);
                                        } else if (!(gus->rctrl[d] & 4)) {
                                                gus->ctrl[d] |= 1;
                                                gus->cur[d] = (gus->ctrl[d] & 0x40) ? gus->end[d] : gus->start[d];
                                        }

                                        if ((gus->ctrl[d] & 0x20) && !gus->waveirqs[d]) {
                                                gus->waveirqs[d] = 1;
                                                update_irqs = 1;
                                                //                                                pclog("Causing wave IRQ %02X
                                                //                                                %i\n", gus->ctrl[d], d);
                                        }
                                }
                        }
                }
                if (!(gus->rctrl[d] & 3)) {
                        if (gus->rctrl[d] & 0x40) {
                                gus->rcur[d] -= gus->rfreq[d];
                                if (gus->rcur[d] <= gus->rstart[d]) {
                                        int diff = gus->rstart[d] - gus->rcur[d];
                                        if (!(gus->rctrl[d] & 8)) {
                                                gus->rctrl[d] |= 1;
                                                gus->rcur[d] = (gus->rctrl[d] & 0x40) ? gus->rstart[d] : gus->rend[d];
                                        } else {
                                                if (gus->rctrl[d] & 0x10)
                                                        gus->rctrl[d] ^= 0x40;
                                                gus->rcur[d] =
                                                        (gus->rctrl[d] & 0x40) ? (gus->rend[d] - diff) : (gus->rstart[d] + diff);
                                        }

                                        if ((gus->rctrl[d] & 0x20) && !gus->rampirqs[d]) {
                                                gus->rampirqs[d] = 1;
                                                update_irqs = 1;
                                                //                                                pclog("Causing ramp IRQ %02X
                                                //                                                %i\n",gus->rctrl[d], d);
                                        }
                                }
                        } else {
                                gus->rcur[d] += gus->rfreq[d];
                                //                                        if (d == 1) printf("RCUR+ %i %08X %08X %08X
                                //                                        %08X\n",d,gus->rfreq[d],gus->rcur[d],gus->rstart[d],gus->rend[d]);
                                if (gus->rcur[d] >= gus->rend[d]) {
                                        int diff = gus->rcur[d] - gus->rend[d];
                                        if (!(gus->rctrl[d] & 8)) {
                                                gus->rctrl[d] |= 1;
                                                gus->rcur[d] = (gus->rctrl[d] & 0x40) ? gus->rstart[d] : gus->rend[d];
                                        } else {
                                                if (gus->rctrl[d] & 0x10)
                                                        gus->rctrl[d] ^= 0x40;
                                                gus->rcur[d] =
                                                        (gus->rctrl[d] & 0x40) ? (gus->rend[d] - diff) : (gus->rstart[d] + diff);
                                        }

                                        if ((gus->rctrl[d] & 0x20) && !gus->rampirqs[d]) {
                                                gus->rampirqs[d] = 1;
                                                update_irqs = 1;
                                                //                                                        pclog("Causing ramp IRQ
                                                //                                                        %02X
                                                //                                                        %i\n",gus->rctrl[d], d);
                                        }
                                }
                        }
                }
        }

        if (update_irqs)
                gus_update_int_status(gus);
}

static void gus_get_buffer(int32_t *buffer, int len, void *p) {
        gus_t *gus = (gus_t *)p;
        int c;

        gus_update(gus);

        for (c = 0; c < len * 2; c++) {
                buffer[c] += (int32_t)gus->buffer[c & 1][c >> 1];
        }

        gus->pos = 0;
}

void *gus_init() {
        int c;
        double out = 1.0;
        gus_t *gus = malloc(sizeof(gus_t));
        memset(gus, 0, sizeof(gus_t));

        gus->ram = malloc(1 << 20);
        memset(gus->ram, 0, 1 << 20);

        pclog("gus_init\n");

        for (c = 0; c < 32; c++) {
                gus->ctrl[c] = 1;
                gus->rctrl[c] = 1;
                gus->rfreq[c] = 63 * 512;
        }

        for (c = 4095; c >= 0; c--) {
                vol16bit[c] = out;  //(float)c/4095.0;//out;
                out /= 1.002709201; /* 0.0235 dB Steps */
        }

        printf("Top volume %f %f %f %f\n", vol16bit[4095], vol16bit[3800], vol16bit[3000], vol16bit[2048]);
        gus->voices = 14;

        gus->samp_latch = (uint64_t)(TIMER_USEC * (1000000.0 / 44100.0));

        gus->t1l = gus->t2l = 0xff;

        io_sethandler(0x0240, 0x0010, readgus, NULL, NULL, writegus, NULL, NULL, gus);
        io_sethandler(0x0340, 0x0010, readgus, NULL, NULL, writegus, NULL, NULL, gus);
        io_sethandler(0x0746, 0x0001, readgus, NULL, NULL, writegus, NULL, NULL, gus);
        io_sethandler(0x0388, 0x0002, readgus, NULL, NULL, writegus, NULL, NULL, gus);
        timer_add(&gus->samp_timer, gus_poll_wave, gus, 1);
        timer_add(&gus->timer_1, gus_poll_timer_1, gus, 1);
        timer_add(&gus->timer_2, gus_poll_timer_2, gus, 1);

        sound_add_handler(gus_get_buffer, gus);

        return gus;
}

void gus_close(void *p) {
        gus_t *gus = (gus_t *)p;

        free(gus->ram);
        free(gus);
}

void gus_speed_changed(void *p) {
        gus_t *gus = (gus_t *)p;

        if (gus->voices < 14)
                gus->samp_latch = (uint64_t)(TIMER_USEC * (1000000.0 / 44100.0));
        else
                gus->samp_latch = (uint64_t)(TIMER_USEC * (1000000.0 / gusfreqs[gus->voices - 14]));
}

static void gus_add_status_info(char *s, int max_len, void *p) {
        gus_t *gus = (gus_t *)p;
        char temps[256];

        sprintf(temps, "GUS max voices : %i\nGUS playback frequency : %iHz\n\n", gus->voices, gusfreqs[gus->voices - 14]);
        strncat(s, temps, max_len);
}

device_t gus_device = {"Gravis UltraSound", 0, gus_init, gus_close, NULL, gus_speed_changed, NULL, gus_add_status_info};
