/*************************************************************************
 *  sequencer.c
 *
 *  Copyright (C) 1995-1997 Michael Heasley (mheasley@hmc.edu)
 *
 *  Portions Copyright (c) 1997 Takashi Iwai (iwai@dragon.mm.t.u-tokyo.ac.jp)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/soundcard.h>
#include "musserver.h"

//#define SEQ_MIDIOUT(a,b) test(a,b)

struct synth_info sinfo[5];
struct midi_info minfo[5];
int seqfd;
int mixfd;
int use_dev = -1;
int seq_dev = -1;
int synth_patches[16];
int chanvol[16];
int volscale = 100;
unsigned int voxdate;
struct synth_voice *voices;
struct opl_instr fm_instruments[175];
struct sbi_instrument fm_sbi[175];
extern int verbose;

extern void cleanup(int status);
extern void reset_midi();
extern void read_genmidi(FILE * wadfile);

struct opl_instr fm_instruments[175];
struct sbi_instrument fm_sbi[175];

SEQ_USE_EXTBUF();
SEQ_DEFINEBUF(2048);

void seqbuf_dump(void)
{
    if (_seqbufptr)
        if (write(seqfd, _seqbuf, _seqbufptr) == -1)
        {
            perror("write /dev/sequencer");
            exit(-1);
        }
    _seqbufptr = 0;
}

int find_midi(int m_type, int dev_num)
{
    int num_midi;
    int use_midi = -1;
    int m = 0;

    if (ioctl(seqfd, SNDCTL_SEQ_NRMIDIS, &num_midi) == -1)
        return 0;
    for (m = 0; m < num_midi; m++)
    {
        minfo[m].device = m;
        ioctl(seqfd, SNDCTL_MIDI_INFO, &minfo[m]);
        use_midi = m;
        if (minfo[m].dev_type == dev_num)
            break;
    }
    if (m_type == NO_SYNTH)
        return num_midi;
    else
        return use_midi;
}

int find_synth(int s_type, int dev_num)
{
    int num = 0;
    int fm = -1;
#ifdef AWE32_SYNTH_SUPPORT
    int awe = -1;
#endif
    int s = 0;

    if (ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &num) == -1)
        return 0;
    else
        for (s = 0; s < num; s++)
        {
            sinfo[s].device = s;
            ioctl(seqfd, SNDCTL_SYNTH_INFO, &sinfo[s]);
            if (sinfo[s].synth_type == SYNTH_TYPE_SAMPLE)
            {
#ifdef AWE32_SYNTH_SUPPORT
                if (sinfo[s].synth_subtype == SAMPLE_TYPE_AWE32)
                {
                    awe = s;
                    if ((dev_num == SYNTH_TYPE_SAMPLE) && (s_type == AWE32_SYNTH))
                        break;
                }
#endif
            }
            else
            {
                fm = s;
                if ((sinfo[s].synth_type == dev_num) && (s_type == FM_SYNTH))
                    break;
            }
        }
    switch (s_type)
    {
        case FM_SYNTH:
            return fm;
            break;
#ifdef AWE32_SYNTH_SUPPORT
        case AWE32_SYNTH:
            return awe;
            break;
#endif
        case NO_SYNTH:
            return num;
            break;
        default:
            return -1;
            break;
    }
}

#ifdef AWE32_SYNTH_SUPPORT
void setup_awe(int num_awe)
{
    use_dev = AWE32_SYNTH;
    seq_dev = num_awe;
    if (verbose)
        printf("Using synth device number %d (%s)\n", seq_dev + 1, sinfo[seq_dev].name);
}
#endif

void setup_midi(int num_midi)
{
    use_dev = EXT_MIDI;
    seq_dev = num_midi;
    if (verbose)
        printf("Using midi device number %d (%s)\n", seq_dev + 1, minfo[seq_dev].name);
}

void setup_fm(int num_fm)
{
    int x;
    int y;
    FILE *sndstat;
    char sndver[100];
    char snddate[100];

    use_dev = FM_SYNTH;
    seq_dev = num_fm;

    sndstat = fopen("/dev/sndstat", "r");
    if (sndstat == NULL)
    {
        printf("musserver: could not open /dev/sndstat, exiting.\n");
        exit(1);
    }
    fgets(sndver, 100, sndstat);
    for (x = 0; x < strlen(sndver); x++)
        if (sndver[x] == '-')
        {
            x++;
            for (y = x; y < strlen(sndver); y++)
                if (sndver[y] != ' ')
                    snddate[y - x] = sndver[y];
                else
                {
                    snddate[y - x] = 0;
                    break;
                }
            break;
        }
    voxdate = atoi(snddate);
    fclose(sndstat);

    voices = malloc(sinfo[seq_dev].nr_voices * sizeof(struct synth_voice));
    for (x = 0; x < sinfo[seq_dev].nr_voices; x++)
    {
        voices[x].note = -1;
        voices[x].channel = -1;
    }
    for (x = 0; x < 16; x++)
        synth_patches[x] = -1;

    mixfd = open("/dev/mixer", O_WRONLY, 0);

    if (verbose)
        printf("Using synth device number %d (%s)\n", seq_dev + 1, sinfo[seq_dev].name);
}

void list_devs()
{
    int dummy;
    int x;

    if ((seqfd = open("/dev/sequencer", O_WRONLY, 0)) < 0)
    {
        perror("open /dev/sequencer");
        exit(1);
    }

    dummy = find_midi(NO_SYNTH, NO_SYNTH);
    for (x = 0; x < dummy; x++)
        printf("Found a midi device of type %d (%s)\n", minfo[x].dev_type, minfo[x].name);
    dummy = find_synth(NO_SYNTH, NO_SYNTH);
    for (x = 0; x < dummy; x++)
        printf("Found a synth device of type %d (%s)\n", sinfo[x].synth_type, sinfo[x].name);
    exit(0);
}

void seq_setup(int pref_dev, int dev_type)
{
    int num_midi;
    int num_awe;
    int num_fm;

    if ((seqfd = open("/dev/sequencer", O_WRONLY, 0)) < 0)
    {
        perror("open /dev/sequencer");
        exit(1);
    }

    num_midi = find_midi(EXT_MIDI, dev_type);
    num_fm = find_synth(FM_SYNTH, dev_type);
    num_awe = find_synth(AWE32_SYNTH, dev_type);

    if ((num_midi == -1) && (num_fm == -1) && (num_awe == -1))
    {
        printf("musserver: no music devices found, exiting.\n");
        exit(1);
    }

    switch (pref_dev)
    {
        case EXT_MIDI:
            if (num_midi != -1)
                setup_midi(num_midi);
#ifdef AWE32_SYNTH_SUPPORT
            else if (num_awe != -1)
                setup_awe(num_awe);
#endif
            else if (num_fm != -1)
                setup_fm(num_fm);
            break;
        case FM_SYNTH:
            if (num_fm != -1)
                setup_fm(num_fm);
            else if (num_midi != -1)
                setup_midi(num_midi);
#ifdef AWE32_SYNTH_SUPPORT
            else if (num_awe != -1)
                setup_awe(num_awe);
#endif
            break;
#ifdef AWE32_SYNTH_SUPPORT
        case AWE32_SYNTH:
            if (num_awe != -1)
                setup_awe(num_awe);
            else if (num_midi != -1)
                setup_midi(num_midi);
            else if (num_fm != -1)
                setup_fm(num_fm);
            break;
#endif
        default:
            break;
    }
    reset_midi();
}

void cleanup_midi(void)
{
    reset_midi();
    close(seqfd);
    if (use_dev == FM_SYNTH)
        close(mixfd);
}

void reset_midi(void)
{
    unsigned int channel;

#ifdef AWE32_SYNTH_SUPPORT
    if (use_dev == AWE32_SYNTH)
    {
        AWE_SET_CHANNEL_MODE(seq_dev, 1);
        AWE_NOTEOFF_ALL(seq_dev);
        for (channel = 0; channel < 16; channel++)
        {
            SEQ_BENDER_RANGE(seq_dev, channel, 200);
            SEQ_BENDER(seq_dev, channel, 0);
        }
    }
    else
#endif
    if (use_dev == EXT_MIDI)
        for (channel = 0; channel < 16; channel++)
        {
            /*
               all notes off 
             */
            SEQ_MIDIOUT(seq_dev, MIDI_CTL_CHANGE + channel);
            SEQ_MIDIOUT(seq_dev, 0x7b);
            SEQ_MIDIOUT(seq_dev, 0);
            /*
               reset pitch bender 
             */
            SEQ_MIDIOUT(seq_dev, MIDI_PITCH_BEND + channel);
            SEQ_MIDIOUT(seq_dev, 64 >> 7);
            SEQ_MIDIOUT(seq_dev, 64 & 127);
            /*
               reset volume to 100 
             */
            SEQ_MIDIOUT(seq_dev, MIDI_CTL_CHANGE + channel);
            SEQ_MIDIOUT(seq_dev, CTL_MAIN_VOLUME);
            SEQ_MIDIOUT(seq_dev, volscale);
            chanvol[channel] = 100;
            /*
               reset pan 
             */
            SEQ_MIDIOUT(seq_dev, MIDI_CTL_CHANGE + channel);
            SEQ_MIDIOUT(seq_dev, CTL_PAN);
            SEQ_MIDIOUT(seq_dev, 64);

        }
    else
        for (channel = 0; channel < sinfo[seq_dev].nr_voices; channel++)
        {
            SEQ_STOP_NOTE(seq_dev, channel, voices[channel].note, 64);
            SEQ_BENDER_RANGE(seq_dev, channel, 200);
            voices[channel].note = -1;
            voices[channel].channel = -1;
        }
    SEQ_DUMPBUF();
}

void note_off(int note, int channel, int volume)
{
    int x = 0;

#ifdef AWE32_SYNTH_SUPPORT
    if (use_dev == AWE32_SYNTH)
    {
        SEQ_STOP_NOTE(seq_dev, channel, note, volume);
    }
    else
#endif
    if (use_dev == EXT_MIDI)
    {
        SEQ_MIDIOUT(seq_dev, MIDI_NOTEOFF + channel);
        SEQ_MIDIOUT(seq_dev, note);
        SEQ_MIDIOUT(seq_dev, volume);
    }
    else
    {
        for (x = 0; x < sinfo[seq_dev].nr_voices; x++)
            if ((voices[x].note == note) && (voices[x].channel == channel))
                break;
        if (x != sinfo[seq_dev].nr_voices)
        {
            voices[x].note = -1;
            voices[x].channel = -1;
            SEQ_STOP_NOTE(seq_dev, x, note, volume);
        }
    }
    SEQ_DUMPBUF();
}

void note_on(int note, int channel, int volume)
{
    int x = 0;

#ifdef AWE32_SYNTH_SUPPORT
    if (use_dev == AWE32_SYNTH)
    {
        SEQ_START_NOTE(seq_dev, channel, note, volume);
    }
    else
#endif
    if (use_dev == EXT_MIDI)
    {
        SEQ_MIDIOUT(seq_dev, MIDI_NOTEON + channel);
        SEQ_MIDIOUT(seq_dev, note);
        SEQ_MIDIOUT(seq_dev, volume);
    }
    else
    {
        for (x = 0; x < sinfo[seq_dev].nr_voices; x++)
            if ((voices[x].note == -1) && (voices[x].channel == -1))
                break;
        if (x != sinfo[seq_dev].nr_voices)
        {
            voices[x].note = note;
            voices[x].channel = channel;
            if (channel == 9)   /* drum note */
            {
                if (use_dev == FM_SYNTH)
                {
                    SEQ_SET_PATCH(seq_dev, x, note + 93);
                    note = fm_instruments[note + 93].note;
                }
                else
                    SEQ_SET_PATCH(seq_dev, x, note + 128);
            }
            else
            {
                SEQ_SET_PATCH(seq_dev, x, synth_patches[channel]);
                if ((voxdate != 950728) && (use_dev == FM_SYNTH))
                    note = note + 12;
            }
            SEQ_START_NOTE(seq_dev, x, note, volume);
        }
    }
    SEQ_DUMPBUF();
}

void pitch_bend(int channel, signed int value)
{
    int x;

#ifdef AWE32_SYNTH_SUPPORT
    if (use_dev == AWE32_SYNTH)
    {
        SEQ_BENDER(seq_dev, channel, 128 * value);
    }
    else
#endif
    if (use_dev == EXT_MIDI)
    {
        SEQ_MIDIOUT(seq_dev, MIDI_PITCH_BEND + channel);
        SEQ_MIDIOUT(seq_dev, value >> 7);
        SEQ_MIDIOUT(seq_dev, value & 127);
    }
    else
    {
        for (x = 0; x < sinfo[seq_dev].nr_voices; x++)
            if (voices[x].channel == channel)
            {
                SEQ_BENDER_RANGE(seq_dev, x, 200);
                SEQ_BENDER(seq_dev, x, 128 * value);
            }
    }
    SEQ_DUMPBUF();
}

void control_change(int controller, int channel, int value)
{
    int x;

    if (controller == CTL_MAIN_VOLUME)
    {
        chanvol[channel] = value;
        value = value * volscale / 100;
    }

#ifdef AWE32_SYNTH_SUPPORT
    if (use_dev == AWE32_SYNTH)
    {
        SEQ_CONTROL(seq_dev, channel, controller, value);
    }
    else
#endif
    if (use_dev == EXT_MIDI)
    {
        SEQ_MIDIOUT(seq_dev, MIDI_CTL_CHANGE + channel);
        SEQ_MIDIOUT(seq_dev, controller);
        SEQ_MIDIOUT(seq_dev, value);
    }
    else
    {
        for (x = 0; x < sinfo[seq_dev].nr_voices; x++)
            if ((voices[x].channel == channel) && (controller == CTL_MAIN_VOLUME))
                SEQ_MAIN_VOLUME(seq_dev, x, value);
    }
    SEQ_DUMPBUF();
}

void patch_change(int patch, int channel)
{
    int x;

#ifdef AWE32_SYNTH_SUPPORT
    if (use_dev == AWE32_SYNTH)
    {
        SEQ_SET_PATCH(seq_dev, channel, patch);
    }
    else
#endif
    if (use_dev == EXT_MIDI)
    {
        SEQ_MIDIOUT(seq_dev, MIDI_PGM_CHANGE + channel);
        SEQ_MIDIOUT(seq_dev, patch);
    }
    else
    {
        for (x = 0; x < sinfo[seq_dev].nr_voices; x++)
            if (((voices[x].channel == -1) && (voices[x].note == -1)) || (voices[x].channel == channel))
            {
                synth_patches[channel] = patch;
                break;
            }
    }
    SEQ_DUMPBUF();
}

void midi_wait(float time)
{
    ioctl(seqfd, SNDCTL_SEQ_SYNC);
    SEQ_WAIT_TIME((int) time);
    SEQ_DUMPBUF();
}

void midi_timer(int action)
{
    switch (action)
    {
        case 0:
            SEQ_START_TIMER();
            break;
        case 1:
            SEQ_STOP_TIMER();
            break;
        case 2:
            SEQ_CONTINUE_TIMER();
            break;
    }
}

void vol_change(int volume)
{
    int x;
    int logscale[32] = { 0, 15, 25, 33, 40, 45, 50, 55, 59, 62, 65, 68, 70, 73, 75, 77, 79, 81, 83, 85, 87, 89, 90, 92, 93, 94, 95, 97, 98, 99, 100, 100 };

    volume = (volume < 0 ? 0 : (volume > 31 ? 31 : volume));
    volscale = logscale[volume];
#ifdef AWE32_SYNTH_SUPPORT
    if (use_dev == AWE32_SYNTH)
    {
        for (x = 0; x < 16; x++)
            SEQ_CONTROL(seq_dev, x, CTL_MAIN_VOLUME, chanvol[x] * volscale / 100);
    }
    else
#endif
    if (use_dev == EXT_MIDI)
    {
        for (x = 0; x < 16; x++)
        {
            SEQ_MIDIOUT(seq_dev, MIDI_CTL_CHANGE + x);
            SEQ_MIDIOUT(seq_dev, CTL_MAIN_VOLUME);
            SEQ_MIDIOUT(seq_dev, chanvol[x] * volscale / 100);
        }
    }
    else
    {
        volume = volscale;
        volume |= (volume << 8);
        //if (-1 == ioctl(mixfd, SOUND_MIXER_WRITE_SYNTH, &volume))
        //  perror("volume change");
        ioctl(mixfd, SOUND_MIXER_WRITE_SYNTH, &volume);
        ioctl(mixfd, SOUND_MIXER_WRITE_LINE2, &volume);
    }
    SEQ_DUMPBUF();
}

void fmload(void)
{
    int x;

    for (x = 0; x < 175; x++)
    {
        fm_sbi[x].key = FM_PATCH;
        fm_sbi[x].device = seq_dev;
        fm_sbi[x].channel = x;
        fm_sbi[x].operators[0] = fm_instruments[x].patchdata[0];
        fm_sbi[x].operators[1] = fm_instruments[x].patchdata[7];
        fm_sbi[x].operators[2] = fm_instruments[x].patchdata[4] + fm_instruments[x].patchdata[5];
        fm_sbi[x].operators[3] = fm_instruments[x].patchdata[11] + fm_instruments[x].patchdata[12];
        fm_sbi[x].operators[4] = fm_instruments[x].patchdata[1];
        fm_sbi[x].operators[5] = fm_instruments[x].patchdata[8];
        fm_sbi[x].operators[6] = fm_instruments[x].patchdata[2];
        fm_sbi[x].operators[7] = fm_instruments[x].patchdata[9];
        fm_sbi[x].operators[8] = fm_instruments[x].patchdata[3];
        fm_sbi[x].operators[9] = fm_instruments[x].patchdata[10];
        fm_sbi[x].operators[10] = fm_instruments[x].patchdata[6];
        fm_sbi[x].operators[11] = fm_instruments[x].patchdata[16];
        fm_sbi[x].operators[12] = fm_instruments[x].patchdata[23];
        fm_sbi[x].operators[13] = fm_instruments[x].patchdata[20] + fm_instruments[x].patchdata[21];
        fm_sbi[x].operators[14] = fm_instruments[x].patchdata[27] + fm_instruments[x].patchdata[28];
        fm_sbi[x].operators[15] = fm_instruments[x].patchdata[17];
        fm_sbi[x].operators[16] = fm_instruments[x].patchdata[24];
        fm_sbi[x].operators[17] = fm_instruments[x].patchdata[18];
        fm_sbi[x].operators[18] = fm_instruments[x].patchdata[25];
        fm_sbi[x].operators[19] = fm_instruments[x].patchdata[19];
        fm_sbi[x].operators[20] = fm_instruments[x].patchdata[26];
        fm_sbi[x].operators[21] = fm_instruments[x].patchdata[22];
        SEQ_WRPATCH(&fm_sbi[x], sizeof(fm_sbi[x]));
    }
}
