/*
    The implementation of Syna functions

    - Marq
*/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "syna.h"

#define KANTTI 0
#define SINI   1
#define SAHA   2
#define KOHINA 3

#define WAVES 4
#define INSTR 30
#define PTLEN 100
#define MAXR 1000
#define BFREQ 262

#define ECHON -4
#define ECHOFF -5
#define STOP -6
#define END -2
#define LOOP -3
#define VOL -7
#define SLIDE -8

#define FPSHIFT 12

#undef TINY

static double   *aalto[WAVES];

static int  *instr[INSTR],*echo[INSTR],vol[INSTR],pola[INSTR],
            prev[INSTR],pan[INSTR];

/* Make these long long if you want more FPSHIFT */
static int plus[INSTR],off[INSTR],slide[INSTR];

static int  global,slen,update,trak[INSTR][PTLEN],ptn[MAXR][PTLEN],
            ti[INSTR],pi[INSTR],len[INSTR],ekolen;
static char *module,eko[INSTR];

int counter;

static char *notes[]={
            "c1","C1","d1","D1", "e1","f1","F1","g1", "G1","a1","A1","b1",
            "c2","C2","d2","D2", "e2","f2","F2","g2", "G2","a2","A2","b2",
            "c3","C3","d3","D3", "e3","f3","F3","g3", "G3","a3","A3","b3",
            "c4","C4","d4","D4", "e4","f4","F4","g4", "G4","a4","A4","b4",
            "c5","C5","d5","D5", "e5","f5","F5","g5", "G5","a5","A5","b5",
            "c6","C6","d6","D6", "e6","f6","F6","g6", "G6","a6","A6","b6",
            "echo1","echo0","stop","vol","sld","0"};

static int notei[]={
            0,0,0,0, 0,0,0,0, 0,0,0,0,
            0,0,0,0, 0,0,0,0, 0,0,0,0,
            0,0,0,0, 0,0,0,0, 0,0,0,0,
            0,0,0,0, 0,0,0,0, 0,0,0,0,
            0,0,0,0, 0,0,0,0, 0,0,0,0,
            1047,1109,1175,1245, 1319,1397,1480,1568, 1661,1760,1864,1976,
            ECHON,ECHOFF,STOP,VOL,SLIDE,0};
                    

static void adsr(int a,int d,int s,int r,int mod,int swp,int ins,int wave);
static void cleanshit(char *s);

void syna_init(int freq)
{
    int n,i;

    slen=freq/BFREQ;
    global=freq;
    counter=0;

    /* Generate lower octaves from 6 */
    for(n=4;n>=0;n--)
        for(i=0;i<12;i++)
            notei[n*12+i]=notei[(n+1)*12+i]/2;

    /* Generate the waveforms */
    for(n=0;n<WAVES;n++)
    {
        aalto[n]=malloc(slen*sizeof(double));
        for(i=0;i<slen;i++) aalto[n][i]=0;
        //memset(aalto[n],0,slen*sizeof(double));
    }
    for(n=0;n<slen;n++)
    {
        aalto[KANTTI][n]=(n<slen/2)?-1.0:1.0;
        aalto[SINI][n]  =sin(n*2.0*M_PI/(double)slen);
        aalto[SAHA][n]  =-1.0+fmod(1.0+2.0*n/(double)slen,2.0);
    }

    /* Noise needs to be longer */
    aalto[KOHINA]=malloc(global*sizeof(double));
    for(n=0;n<global;n++)
        aalto[KOHINA][n]=(rand()%2000-1000)/(double)1000.0;

    /* Set starting values */
    for(n=0;n<INSTR;n++)
    {
        off[n]=-1;
        plus[n]=0;
        trak[n][0]=END;
        ti[n]=-1;
        eko[n]=0;
        pan[n]=(n&1)?128-64:128+64;
        pola[n]=0;
        vol[n]=255;
        prev[n]=0;
        slide[n]=0;
    }
}

#ifndef TINY
int syna_load(char *tune)
{
    FILE    *f;
    long    length;

    /* Read the file and call syna_get */
    f=fopen(tune,"rb");
    if(f==NULL)
        return(-1);
    fseek(f,0,SEEK_END);
    length=ftell(f);
    fseek(f,0,SEEK_SET);
    module=malloc(length+1);
    fread(module,1,length,f);
    module[length]=0; /* String ends in zero */
    fclose(f);

    return(syna_get(module));
}
#endif

int syna_get(void *tune)
{
    int     n=0,i,j,tmp,a,d,s,r,mod,sweep,
            wave,patt,track,note;
    static char    *rows[MAXR],*key,*tnp;

    module=tune;
#ifndef TINY
    cleanshit(module);  /* You better have a clean module in intros... */
#endif

    /* Extract rows */
    for(n=0;n<MAXR;n++)
    {
        rows[n]=strtok((n)?NULL:module,"\n");
        if(rows[n]==NULL)
            break;
    }

    /* Extract data from rows */
    for(i=0;i<n;i++)
    {
        if(rows[i][0]=='#')
            continue;

        if(key=strtok(rows[i],":"))
        {
            if(!strcmp(key,"bpm"))
            {
                tmp=atoi(strtok(NULL,":"))*10/25;
                update=6*global/tmp;
                ekolen=update*3;
            }
            if(key[0]=='i')
            {
                /* Get instrument number and wave form */
                tmp=atoi(&key[1]);
                echo[tmp]=malloc(ekolen*sizeof(int));
                memset(echo[tmp],0,ekolen*sizeof(int));
                tnp=strtok(NULL,",");
                if(!strcmp(tnp,"kantti"))
                    wave=0;
                if(!strcmp(tnp,"sini"))
                    wave=1;
                if(!strcmp(tnp,"saha"))
                    wave=2;
                if(!strcmp(tnp,"kohina"))
                    wave=3;
                
                /* Get ADSR */
                a=atoi(strtok(NULL,","));
                d=atoi(strtok(NULL,","));
                s=atoi(strtok(NULL,","));
                r=atoi(strtok(NULL,","));
                len[tmp]=a+d+s+r+1;
                instr[tmp]=malloc(len[tmp]*sizeof(int));
                memset(instr[tmp],0,len[tmp]*sizeof(int));

                mod=atoi(strtok(NULL,","));
                if(tnp=strtok(NULL,","))
                    sweep=atoi(tnp);
                else
                    sweep=0;
                if(tnp=strtok(NULL,","))
                    pan[tmp]=(double)atoi(tnp)*255/100;
                if(tnp=strtok(NULL,","))
                    pola[tmp]=atoi(tnp)*255/100;

                adsr(a,d,s,r,mod,sweep,tmp,wave);
            }

            if(key[0]=='p') /* Handle pattern */
            {
                patt=atoi(&key[1]);
                j=0;
                while(1)
                {
                    tnp=strtok(NULL,",");
                    if(tnp!=NULL)
                    {
                        note=0;
                        for(tmp=0;notes[tmp][0]!='0';tmp++)
                            if(!strcmp(notes[tmp],tnp))
                                note=notei[tmp];
                        ptn[patt][j]=note;
                        if(note==VOL || note==SLIDE)
                        {
                            j++;
                            ptn[patt][j]=atoi(strtok(NULL,","));
                        }

                        j++;
                    }
                    else
                    {
                        ptn[patt][j]=END;
                        break;
                    }
                }
            }

            if(key[0]=='t') /* Handle track */
            {
                track=atoi(&key[1]);
                j=0;
                while(1)
                {
                    tnp=strtok(NULL,",");
                    if(tnp!=NULL)
                    {
                        if(!strcmp(tnp,"loop"))
                            trak[track][j]=LOOP;
                        else
                        {
                            patt=atoi(&tnp[1]);
                            trak[track][j]=patt;
                        }
                        j++;
                    }
                    else
                    {
                        trak[track][j]=END;
                        break;
                    }
                }
            }
        }
    }

    return(0);
}

void syna_play(signed short *dest,int length)
{
    int n,i,note;
    int left,right,smp;
    static int updaat=-1,ekopos;

    if(updaat==-1)
        updaat=update;

    ekopos=counter%ekolen;
    for(n=0;n<length;n++,counter++)
    {
        /* New row? */
        if(updaat--==0)
        {
            updaat=update;
            for(i=1;trak[i][0]!=END;i++)
            {
                if(ti[i]==END)
                    continue;
                pi[i]++;
                if(ti[i]==-1 || ptn[trak[i][ti[i]]][pi[i]]==END)
                {
                    ti[i]++;
                    if(trak[i][ti[i]]==LOOP)
                        ti[i]=0;
                    if(trak[i][ti[i]]==END)
                        ti[i]=END;
                    pi[i]=0;
                }
                if(ti[i]!=END)
                {
                    if(note=ptn[trak[i][ti[i]]][pi[i]])
                    {
                        switch(note)
                        {
                            case STOP  : off[i]=-1; break;
                            case ECHON : eko[i]=1; break;
                            case ECHOFF: eko[i]=0; break;
                            case VOL   : pi[i]++;
                                         vol[i]=ptn[trak[i][ti[i]]][pi[i]]*255/100;
                                         break;
                            case SLIDE : pi[i]++;
                                         slide[i]=ptn[trak[i][ti[i]]][pi[i]]*164/1000;
                                         slide[i]=(slide[i]<<FPSHIFT)/100000;
                                         break;
                            default    : plus[i]=note<<FPSHIFT;
                                         plus[i]/=BFREQ;
                                         off[i]=0;
                        }
                    }
                }
            }
        }

        /* Go through channels */
        left=right=0;
        for(i=1;trak[i][0]!=END;i++)
        {
            smp=echo[i][ekopos];

            echo[i][ekopos]>>=1;
            if(off[i]>=0)
            {
                smp+=instr[i][off[i]>>FPSHIFT];

                if(eko[i])
                    echo[i][ekopos]=smp>>2;

                off[i]+=plus[i];
                plus[i]+=slide[i];
                if((off[i]>>FPSHIFT)>=len[i] || off[i]<0)
                    off[i]=-1;
            }

            if(pola[i])
                smp=(smp*(255-pola[i])>>8)+(prev[i]*pola[i]>>8);
            prev[i]=smp;

            smp=smp*vol[i]>>8;
            left+=(255-pan[i])*smp>>8;
            right+=pan[i]*smp>>8;
        }

        if(left<-4*32767)
            left=-4*32767;
        if(left>4*32767)
            left=4*32767;
        dest[n<<1]=(signed short)(left>>3);

        if(right<-4*32767)
            right=-4*32767;
        if(right>4*32767)
            right=4*32767;
        dest[n*2+1]=(short)(right>>3);

        ekopos++;
        if(ekopos>=ekolen)
            ekopos=0;
    }
}

double sini(double f)
{
    static double luk[1024];
    static int first=1,n;

    if(first)
    {
        first=0;
        for(n=0;n<1024;n++)
            luk[n]=sin(n*2*M_PI/1024);
    }

    n=f*1024.0/(2*M_PI);
    return(luk[n&1023]);
}

/* Make ADSR to instruments */
static void adsr(int a,int d,int s,int r,int mod,int swp,int ins,int wave)
{
    int n,modulo=slen,id=0;
    double  i=0,vol=0.0,dv,oh=0.0,op,ip=1,sweep;

    if(!a) a=1;
    if(!r) r=1;

    if(wave==KOHINA)
        modulo=global;

    if(mod) /* We modulate! */
        op=mod/100.0*2.0*M_PI/(double)slen;
    else
        op=0;

    sweep=(double)swp/1000.0/(double)slen;

    dv=32767.0/(double)a;
    for(n=0;n<a;n++,i+=ip,ip+=sweep,vol+=dv,oh+=op)
        instr[ins][id++]=vol*aalto[wave][((int)i)%modulo]*((mod)?sini(oh):1.0);
    for(n=0;n<d;n++,i+=ip,ip+=sweep,vol-=dv,oh+=op)
        instr[ins][id++]=vol*aalto[wave][((int)i)%modulo]*((mod)?sini(oh):1.0);
    for(n=0;n<s;n++,i+=ip,ip+=sweep,oh+=op)
        instr[ins][id++]=vol*aalto[wave][((int)i)%modulo]*((mod)?sini(oh):1.0);
    dv=vol/(double)r;
    for(n=0;n<r;n++,i+=ip,ip+=sweep,vol-=dv,oh+=op)
        instr[ins][id++]=vol*aalto[wave][((int)i)%modulo]*((mod)?sini(oh):1.0);
}

/* Fix the fscking Windoze/DOS newlines */
#ifndef TINY
void cleanshit(char *s)
{
    char    *d=strdup(s);

    for(;*d;d++)
        if(*d!='\r' && *d!=' ')
            *s++=*d;
    *s=0;
}
#endif
