/***********************************************************************/
/*                                                                     */
/* str.c - plays sound/noisetracker files on a SparcStation            */
/*                                                                     */
/* Authors  : Liam Corner - zenith@dcs.warwick.ac.uk                   */
/*            Marc Espie - espie@dmi.ens.fr                            */
/* Version  : 1.20 - 3 November 1991                                   */
/*                                                                     */
/* Usage    : str32 <filename>                                         */
/*            [f|z]cat filename | str32                                */
/*                                                                     */
/***********************************************************************/
 
 
#include <stdio.h>
#include <malloc.h>
 
 
/**********************************************************/
/* uS is the number of uSeconds that a byte is played for */
/* Sparc plays at 8000 bytes/sec  =>  1 byte = 125 uSec   */
/* VSYNC is the number of bytes played in 1/50 sec        */
/* ie 0.02/(uS * 10**-6)                                  */
/**********************************************************/
#define uS 125
#define VSYNC 160
#define AUDIO "/dev/audio"
 
#define MIN(A,B) ((A)<(B) ? (A) : (B))
#define MAX(A,B) ((A)>(B) ? (A) : (B))
 
 
typedef struct {    /***********************************/
  char *info;       /* Sample                          */
  int length;       /* Length of sample                */
  float volume;     /* Fractional volume 0-1 (min-max) */
  int rep_start;    /* Byte offset of repeat start     */
  int rep_end;      /* Byte offset of repeat end       */
} Voice;            /***********************************/
 
 
typedef struct {                 /**************************/
  char sample [64][4];           /* Sample number          */
  char effect [64][4];           /* Effect number          */
  unsigned char params [64][4];  /* Effect parameters      */
  int period [64][4];            /* Period (pitch) of note */
} Pattern;                       /**************************/
 
 
typedef struct {         /***********************************************/
  char samp;             /* Sample number of current note               */
  int pitch;             /* Current channel pitch (index to step_table) */
  int slide;             /* Step size of pitch slide (if any)           */
  int doslide;
  unsigned int pointer;  /* Current sample position                     */
  unsigned int step;     /* Sample offset increment (gives pitch)       */
  float volume;          /* Fractional volume of current note           */
  float volslide;
  int doslidevol;
  int doporta;
  int pitchgoal;
  int portarate;
} Channel;               /***********************************************/
 
 
/*****************************************************************************/
/* Skips the next 'n' input bytes - because fseek won't work on stdin        */
/*****************************************************************************/
void byteskip (fp, bytes)
FILE *fp;
int bytes;
    {
    int loop;
 
    for (loop = 0; loop < bytes; loop++)
        getc(fp);
    }
 
 
/************************************************************************/
/*      For routine 'cvt' only                                          */
/************************************************************************/
/*      Copyright 1989 by Rich Gopstein and Harris Corporation          */
/************************************************************************/
 
unsigned int cvt(ch)
int ch;
    {
    int mask;
 
    if (ch < 0)
        {
        ch = -ch;
        mask = 0x7f;
        }
    else
        mask = 0xff;
 
    if (ch < 32)
        {
        ch = 0xF0 | 15 - (ch / 2);
        }
    else if (ch < 96)
        {
        ch = 0xE0 | 15 - (ch - 32) / 4;
        }
    else if (ch < 224)
        {
        ch = 0xD0 | 15 - (ch - 96) / 8;
        }
    else if (ch < 480)
        {
        ch = 0xC0 | 15 - (ch - 224) / 16;
        }
    else if (ch < 992)
        {
        ch = 0xB0 | 15 - (ch - 480) / 32;
        }
    else if (ch < 2016)
        {
        ch = 0xA0 | 15 - (ch - 992) / 64;
        }
    else if (ch < 4064)
        {
        ch = 0x90 | 15 - (ch - 2016) / 128;
        }
    else if (ch < 8160)
        {
        ch = 0x80 | 15 - (ch - 4064) /  256;
        }
    else
        {
        ch = 0x80;
        }
    return (mask & ch);
    }
 
 
char *getstring(f, len)
FILE *f;
int len;
    {
    static char s[150];
    int i;
 
    for (i = 0; i < len; i++)
        s[i] = fgetc(f);
    s[len] = '\0';
    return s;
    }
 
#define OLD 0
#define NEW 1
 
int main (argc, argv)
int argc;
char **argv;
    {
    FILE *fp, *audio;
    int loop;
    int notes, note, channel, vsync;
    int pat, pat_num;
    int byte, bytes;
    int step_table[1024];
    int speed=6;                      /* Default speed is 6 */
    int end_pattern=0;
    char songlength;
    char tune[128];
    char num_patterns=0;
    unsigned char ulaw;
    float dummy1, dummy2;
    Voice voices[32];
    Pattern patterns[64];
    Channel ch[4];
    int nvoices;
    int effect;
 
    int type;   /* module type: old or new */
    char *command;  /* the actual command name used */
 
    command = argv[0];
    if (strcmp(argv[0], "str32") == 0)
        type = NEW;
    else if (strcmp(argv[0], "str15") == 0)
        type = OLD;
    else
        {
        fprintf(stderr,
            "Error: command should be named either str15 or str32\n");
        exit(1);
        }
 
    if (type == OLD)
        nvoices = 15;
    else
        nvoices = 31;
 
    if (argc>2)
        {
        fprintf(stderr,"Usage: %s [<filename>]\n", command);
        exit(1);
        }
 
/***********************************************************************/
/* Creates a table of the byte_step << 16 for a given pitch            */
/* The step and pointer are stored << 16 to get accuracy without floats*/
/* eg to get double pitch only play every other byte                   */
/* so step of 0x10000 is normal pitch, 0x8000 is half,                 */
/* 0x20000 is double.  Pointer is >> 16 when accessed,                 */
/* so 0x10000 is 1st byte, 0x20000 2nd etc                             */
/* I have no idea where the other numbers are from, I copied them from */
/* a SoundTracker player for the Acorn Archimedes                      */
/*                                                                     */
/* Actually, these other numbers are highly dependent on the amiga hw. */
/***********************************************************************/
    step_table[0] = 0;
    for (loop = 1; loop < 1024; loop++)
        {
        dummy1 = 3575872 / loop;
        dummy2 = (dummy1 / (1000000 /uS) ) * 60000;
        step_table[loop] = (int)dummy2;
        }
 
    if (argc < 2)
        fp = stdin;
    else
        fp = fopen(argv[1], "r");
    if (fp == NULL)
        {
        fprintf(stderr, "%s: unable to open tune file %s\n",
            command, argv[1]);
        exit(1);
        }
 
        /* read song name */
    printf("Module : %s\n\n", getstring(fp, 20));
 
        /* Reads in the sample-information tables */
    for (loop = 1; loop <= nvoices; loop++)
        {
        printf("%6d : %s\n", loop, getstring(fp, 22));
        voices[loop].length = ( (getc(fp) << 8) | getc(fp) ) * 2;
        getc(fp);
        voices[loop].volume = getc(fp);
        voices[loop].volume = MIN(voices[loop].volume, 64);
        voices[loop].volume /= 64;   /* Volume is a fraction */
        voices[loop].rep_start = ( (getc(fp) << 8) | getc(fp) ) * 2;
        voices[loop].rep_end = ( (getc(fp) << 8) | getc(fp) ) * 2;
        if (voices[loop].rep_end <= 4)
            voices[loop].rep_end = 0;
        else
            {
                /* If there is a repeat then end=start+length, but must be */
                /* less than the sample length.  Not sure if this is 100%  */
                /* correct, but it seems to work OK :-)                    */
            if (voices[loop].rep_end + voices[loop].rep_start - 1
                > voices[loop].length)
                voices[loop].rep_start >>= 1;
            voices[loop].rep_end += voices[loop].rep_start;
            voices[loop].rep_end = MIN(voices[loop].rep_end,
                voices[loop].length);
            }
        }
    voices[0].length = 0;
 
    songlength = getc(fp);
    byteskip(fp, 1);
 
        /* Reads in the tune */
    for (loop = 0; loop < 128; loop++)
        {
        tune[loop] = getc(fp);
        if (tune[loop] > num_patterns)
            num_patterns = tune[loop];
        }
    num_patterns++;
 
        /* skip over sig (usually M.K.) */
    if (type == NEW)
        byteskip(fp,4);
 
        /* Reads in the patterns */
    for (pat_num = 0; pat_num < num_patterns; pat_num++)
        {
            /* 64 notes per pattern  */
        for (notes = 0; notes < 64; notes++)
            {
                /* 4 channels per note   */
            for (channel = 0; channel < 4; channel++)
                {
                note = (getc(fp) << 24) | (getc(fp) << 16) |
                    (getc(fp) << 8) | getc(fp);
                (patterns[pat_num]).effect[notes][channel] =
                    (note & 0xF00) >> 8;
                (patterns[pat_num]).params[notes][channel] = note & 0xFF;
                (patterns[pat_num]).sample[notes][channel] =
                    ( (note & 0xF000) >> 12) | ( (note >> 24) & 0x10);
                (patterns[pat_num]).period[notes][channel] =
                    MIN( (note & 0xFFF0000) >> 16, 1023);
                }
            }
        }
 
        /* Stores the samples voices as an array of char */
    for (loop = 1; loop <= nvoices; loop++)
        {
        voices[loop].info = malloc(voices[loop].length);
        if (voices[loop].info == NULL)
            {
            fprintf(stderr, "%s: unable to allocate memory\n, command");
            exit(1);
            }
        fread(voices[loop].info, 1, voices[loop].length, fp);
        }
 
    audio = fopen(AUDIO, "w");
    if (audio == NULL)
        {
        fprintf(stderr, "%s: unable to access %s\n", command, AUDIO);
        exit(1);
        }
 
    for (loop = 0; loop < 4; loop++)
        {
        ch[loop].pointer = 0;
        ch[loop].step = 0;
        ch[loop].volume = 0;
        ch[loop].pitch = 0;
        }
 
    printf("\nPosition (%d):", songlength);
    fflush(stdout);
 
    for (pat_num = 0; pat_num < songlength; pat_num++)
        {
        printf("\r\t\t%3d", pat_num);
        fflush(stdout);
        pat = tune[pat_num];
        end_pattern = 0;
        for (notes = 0; notes < 64; notes++)
            {
            for (channel = 0; channel < 4; channel++)
                {
                int samp, pitch, cmd, para;
 
                samp = patterns[pat].sample[notes][channel];
                pitch = patterns[pat].period[notes][channel];
                cmd = patterns[pat].effect[notes][channel];
                para = patterns[pat].params[notes][channel];
                if (samp)
                    {
                    ch[channel].samp = samp;
                        /* load new instrument */
                    ch[channel].volume = voices[ch[channel].samp].volume;
                    }
                        /* If sample number=0 and no new period */
                        /* continue last note */
                if (pitch && cmd != 3)
                    {
                    ch[channel].pointer = 0;
                    ch[channel].step = step_table[pitch];
                    ch[channel].pitch = pitch;
                    }
                ch[channel].doslide = 0;
                ch[channel].doslidevol = 0;
                ch[channel].doporta = 0;
                switch(cmd)  /* Do effects */
                    {
                case 0xF :
                    speed = para;
                    break;
                case 0xD :
                    end_pattern = 1;
                    break;
                case 0xC :
                    ch[channel].volume= MIN(para, 64);
                    ch[channel].volume /= 64;
                    break;
                    /* volume_slide */
		case 0xB :
		    pat_num = (para & 0xF) + (10 * (para >> 4));
		    break;
                case 0xA :
                    ch[channel].doslidevol = 1;
                    if (para)
                        {
                        if (para & 15)
                            ch[channel].volslide = - para / 64;
                        else
                            ch[channel].volslide = (para >> 4)/64;
                        }
                    break;
                case 3   :
                    ch[channel].doporta = 1;
                    if (para)
                        ch[channel].portarate = para;
                    if (pitch)
                        ch[channel].pitchgoal = pitch;
                    break;
                case 2   :
                    ch[channel].doslide = 1;
                    if (para)
                        ch[channel].slide = para;
                    break;
                case 1   :
                    ch[channel].doslide = 1;
                    if (para)
                        ch[channel].slide = -para;
                    break;
                case 0   :
                    break;
                default  :
                    /* printf(" [%d][%d] ", cmd, para); */
                    break;
                    }
                }
                /* 1 vsync = 0.02 sec */
            for (vsync = 0; vsync < speed; vsync++)
                {
                    /* 160*125uSec = 0.02 */
                for (bytes = 0; bytes < VSYNC; bytes++)
                    {
                    byte = 0;
                    for (channel = 0; channel < 4; channel++)
                        {
                        if (ch[channel].samp == 0)
                            continue;
                            /* If at end of sample jump to rep_start position */
                        if (voices[ch[channel].samp].rep_end)
                            {
                            if ((ch[channel].pointer >> 16) >=
                                voices[ch[channel].samp].rep_end)
                                ch[channel].pointer +=
                                    (voices[ch[channel].samp].rep_start -
                                    voices[ch[channel].samp].length)<< 16;
                            }
                        else
                            if ((ch[channel].pointer >> 16) >=
                                voices[ch[channel].samp].length)
                                continue;
                        /* byte = sum of (sample byte * volume) for each */
                        /* of 4 channels which mixes the sounds          */
                        if (ch[channel].pointer >> 16 <
                            voices[ch[channel].samp].length)
                            {
                            byte += (int) ( (voices[ch[channel].samp]
                                .info[ch[channel].pointer >> 16])
                                * (ch[channel].volume));
                            ch[channel].pointer += ch[channel].step;
                            }
                        }
                        /* Divide by 4 to get the correct volume */
                    byte /= 4;
                    ulaw = (unsigned char) cvt(byte * 16);/* Convert byte */
                    fputc(ulaw, audio);                /* and play the note */
                    }
                    /* Do end of vsync */
                if (vsync == 0)
                    continue;
                for (channel = 0; channel < 4; channel++)
                    {
                    if (ch[channel].doslide)             /* effects */
                        {
                        ch[channel].pitch += ch[channel].slide;
                        ch[channel].pitch = MIN(ch[channel].pitch, 1023);
                        ch[channel].pitch = MAX(ch[channel].pitch, 113);
                        ch[channel].step = step_table[ch[channel].pitch];
                        }
                    if (ch[channel].doslidevol)
                        {
                        ch[channel].volume += ch[channel].volslide;
                        if (ch[channel].volume < 0.0)
                            ch[channel].volume = 0.0;
                        else if (ch[channel].volume >= 1.0)
                            ch[channel].volume = 1.0;
                        }
                    if (ch[channel].doporta)
                        {
                        if (ch[channel].pitch < ch[channel].pitchgoal)
                            {
                            ch[channel].pitch += ch[channel].portarate;
                            if (ch[channel].pitch > ch[channel].pitchgoal)
                                ch[channel].pitch = ch[channel].pitchgoal;
                            }
                        else if (ch[channel].pitch > ch[channel].pitchgoal)
                            {
                            ch[channel].pitch -= ch[channel].portarate;
                            if (ch[channel].pitch < ch[channel].pitchgoal)
                                ch[channel].pitch = ch[channel].pitchgoal;
                            }
                        }
                    }
                }
            if (end_pattern == 1)
                break;
            }
        }
 
    fclose(audio);
    printf("\n");
    return (0);
    }
 
