A Jukebox Style SID Bitmusic Player in Aztec-C65

Started by BillBuckels, April 01, 2008, 10:15 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

BillBuckels

The SID Library Routine for the Jukebox

There is not much to say about this except that this routine and the other modules in the demo program in this thread are bundled with the Aztec64 C-language cross-compiler configured for MS-DOS and Windows XP available for download at myAztec-C website:

http://www.clipshop.ca/Aztec/index.htm#commodore



/* Copyright (C) Bill Buckels 2008 */

/* sid chip sound playback routines */

/*

MEANINGS OF SOUND TERMS

ADSR     -- Attack/Decay/Sustain/Release
Attack   -- Rate sound rises to peak volume
Decay    -- Rate sound falls from peak volume to Sustain level
Sustain  -- Prolong note at certain volume
Release  -- Rate at which volume falls from Sustain level
Waveform -- "Shape" of sound wave
Pulse    -- Tone quality of Pulse Waveform


ADSR is encoded in 2 bytes

A - attack - high nibble (4 bits)
D - decay - low nibble (4 bits)

S - sustain - high nibble (4 bits)
R - release - low nibble (4 bits)

                        +
                       / \
                      /   \
                     /     \
   SUSTAIN LEVEL . ./. . . .+--------+
                   /                  \
                  /                    \
                 /                      \

                 |      |   |        |   |
                 |   A  | D |    S   | R |



These two 1-byte registers control the shape of the wave form
generated by the SID chip. Together with wave type (and pulse
settings if a pulse wave type), these registers define a musical
instrument for each of the three voices of the SID chip.

There are 3 sets of registers for each of the 3 voices offset by 7
bytes from each other.

Attack/Decay and Sustain/Release (ADSR) settings should always be
set in your program BEFORE the Waveform is set.


  +-----+------------------------+--------------------------------+
  |VALUE|ATTACK RATE (TIME/CYCLE)| DECAY/RELEASE RATE (TIME/CYCLE)|
  +-----+------------------------+--------------------------------+
  |  0  |           2 ms         |               6 ms             |
  |  1  |           8 ms         |              24 ms             |
  |  2  |          16 ms         |              48 ms             |
  |  3  |          24 ms         |              72 ms             |
  |  4  |          38 ms         |             114 ms             |
  |  5  |          56 ms         |             168 ms             |
  |  6  |          68 ms         |             204 ms             |
  |  7  |          80 ms         |             240 ms             |
  |  8  |         100 ms         |             300 ms             |
  |  9  |         250 ms         |             750 ms             |
  | 10  |         500 ms         |             1.5 s              |
  | 11  |         800 ms         |             2.4 s              |
  | 12  |           1 s          |               3 s              |
  | 13  |           3 s          |               9 s              |
  | 14  |           5 s          |              15 s              |
  | 15  |           8 s          |              24 s              |
  +-----+------------------------+--------------------------------+


+----------------------------------------------------------------------------+
|SETTING VOLUME -- SAME FOR ALL 3 VOICES                                     |
+--------------+---------+---------------------------------------------------+
|VOLUME CONTROL|POKE54296| Settings from 0 (off) to 15 (loudest)             |
+--------------+---------+---------------------------------------------------+
                              VOICE NUMBER 1
+--------------+---------+---------------------------------------------------+
|TO CONTROL    |POKE THIS|         FOLLOWED BY ONE OF THESE NUMBERS          |
|THIS SETTING: |NUMBER:  | (0 to 15 ... or ... 0 to 255 depending on range)  |
+--------------+---------+---------------------------------------------------+
|TO PLAY A NOTE|      C  | C#| D | D#| E | F | F#| G | G#| A | A#| B | C | C#|
|HIGH FREQUENCY|54273 34 | 36| 38| 40| 43| 45| 48| 51| 54| 57| 61| 64| 68| 72|
|LOW FREQUENCY |54272 75 | 85|126|200| 52|198|127| 97|111|172|126|188|149|169|
+--------------+---------+------------+------------+------------+------------+
|WAVEFORM      |  POKE   |  TRIANGLE  |  SAWTOOTH  |   PULSE    |   NOISE    |
|              |  54276  |     17     |     33     |     65     |    129     |
+--------------+---------+------------+------------+------------+------------+
|PULSE RATE (Pulse Waveform)                                                 |
|HI POLSE      |  54275  |   A value of 0 to 15  (for Pulse waveform only)   |
|LO POLSE      |  54274  |   A value of 0 to 255 (for Pulse waveform only)   |
+--------------+---------+------+------+------+------+-----+-----+-----+-----+
|ATTACK/       |  POKE   | ATK4 | ATK3 | ATK2 | ATK1 | DEC4| DEC3| DEC2| DEC1|
|       DECAY  |  54277  | 128  |  64  |  32  |  16  |  8  |  4  |  2  |  1  |
+--------------+---------+------+------+------+------+-----+-----+-----+-----+
|SUSTAIN/      |  POKE   | SUS4 | SUS3 | SUS2 | SUS1 | REL4| REL3| REL2| REL1|
|       RELEASE|  54278  | 128  |  64  |  32  |  16  |  8  |  4  |  2  |  1  |
+--------------+---------+------+------+------+------+-----+-----+-----+-----+
                              VOICE NUMBER 2
+--------------+---------+---------------------------------------------------+
|TO CONTROL    |POKE THIS|         FOLLOWED BY ONE OF THESE NUMBERS          |
|THIS SETTING: |NUMBER:  | (0 to 15 ... or ... 0 to 255 depending on range)  |
+--------------+---------+---------------------------------------------------+
|TO PLAY A NOTE|      C  | C#| D | D#| E | F | F#| G | G#| A | A#| B | C | C#|
|HIGH FREQUENCY|54280 34 | 36| 38| 40| 43| 45| 48| 51| 54| 57| 61| 64| 68| 72|
|LOW FREQUENCY |54279 75 | 85|126|200| 52|198|127| 97|111|172|126|188|149|169|
+--------------+---------+------------+------------+------------+------------+
|WAVEFORM      |  POKE   |  TRIANGLE  |  SAWTOOTH  |   PULSE    |   NOISE    |
|              |  54283  |     17     |     33     |     65     |    129     |
+--------------+---------+------------+------------+------------+------------+
|PULSE RATE (Pulse Waveform)                                                 |
|HI POLSE      |  54282  |   A value of 0 to 15  (for Pulse waveform only)   |
|LO POLSE      |  54281  |   A value of 0 to 255 (for Pulse waveform only)   |
+--------------+---------+------+------+------+------+-----+-----+-----+-----+
|ATTACK/       |  POKE   | ATK4 | ATK3 | ATK2 | ATK1 | DEC4| DEC3| DEC2| DEC1|
|       DECAY  |  54284  | 128  |  64  |  32  |  16  |  8  |  4  |  2  |  1  |
+--------------+---------+------+------+------+------+-----+-----+-----+-----+
|SUSTAIN/      |  POKE   | SUS4 | SUS3 | SUS2 | SUS1 | REL4| REL3| REL2| REL1|
|       RELEASE|  54285  | 128  |  64  |  32  |  16  |  8  |  4  |  2  |  1  |
+--------------+---------+------+------+------+------+-----+-----+-----+-----+
                              VOICE NUMBER 3
+--------------+---------+---------------------------------------------------+
|TO CONTROL    |POKE THIS|         FOLLOWED BY ONE OF THESE NUMBERS          |
|THIS SETTING: |NUMBER:  | (0 to 15 ... or ... 0 to 255 depending on range)  |
+--------------+---------+---------------------------------------------------+
|TO PLAY A NOTE|      C  | C#| D | D#| E | F | F#| G | G#| A | A#| B | C | C#|
|HIGH FREQUENCY|54287 34 | 36| 38| 40| 43| 45| 48| 51| 54| 57| 61| 64| 68| 72|
|LOW FREQUENCY |54286 75 | 85|126|200| 52|198|127| 97|111|172|126|188|149|169|
+--------------+---------+------------+------------+------------+------------+
|WAVEFORM      |  POKE   |  TRIANGLE  |  SAWTOOTH  |   PULSE    |   NOISE    |
|              |  54290  |     17     |     33     |     65     |    129     |
+--------------+---------+------------+------------+------------+------------+
|PULSE RATE (Pulse Waveform)                                                 |
|HI POLSE      |  54289  |   A value of 0 to 15  (for Pulse waveform only)   |
|LO POLSE      |  54288  |   A value of 0 to 255 (for Pulse waveform only)   |
+--------------+---------+------+------+------+------+-----+-----+-----+-----+
|ATTACK/       |  POKE   | ATK4 | ATK3 | ATK2 | ATK1 | DEC4| DEC3| DEC2| DEC1|
|       DECAY  |  54291  | 128  |  64  |  32  |  16  |  8  |  4  |  2  |  1  |
+--------------+---------+------+------+------+------+-----+-----+-----+-----+
|SUSTAIN/      |  POKE   | SUS4 | SUS3 | SUS2 | SUS1 | REL4| REL3| REL2| REL1|
|       RELEASE|  54292  | 128  |  64  |  32  |  16  |  8  |  4  |  2  |  1  |
+--------------+---------+------+------+------+------+-----+-----+-----+-----+

*/



#define SID 54272

char *_spoke = (char *)0;
char *_spress = (char *)203;


/* the following C64 SID frequencies represent
   general midi octaves -1 through 6 */
/* to find midi note 60 (mid C) the offset is 60 */
/* don't go by the note values in the table above...
   I have adjusted these for pitch. */

/* if the midi note in the SIB file is out of range
   no sound is played in the playback routine */

unsigned int _cfreq[95]={
  274,  291,  308,  326,  345,  366,  388,  411,  435,  461,  488,  517,
  549,  582,  616,  653,  691,  733,  776,  822,  871,  923,  977, 1035,
1099, 1164, 1233, 1306, 1383, 1466, 1553, 1645, 1742, 1846, 1955, 2071,
2198, 2328, 2466, 2613, 2767, 2932, 3106, 3290, 3485, 3692, 3911, 4143,
4396, 4657, 4933, 5226, 5535, 5864, 6212, 6580, 6970, 7384, 7822, 8287,
8793, 9315, 9867,10452,11071,11728,12424,13161,13941,14769,15645,16574,
17557,18600,19704,20874,22113,23426,24818,26292,27853,29508,31261,33118,
35084,37170,39378,41718,44196,46822,49606,52554,55676,58986,62492};


/* a pointer to the base of the SID chip registers */
char *_sid = (char *)SID;

/* frequency registers for all 3 SID voices */
int *_freq1 = (int *)SID;
int *_freq2 = (int *)(SID + 7);
int *_freq3 = (int *)(SID +14);

/* instrument settings array follows */
/* this idea is similar to BASIC 7's envelope on the C128 */

/* the following instruments are the combined values from
   the examples in the C64 user's and programmer's guides
   and the C128 programmer's guide.

   there are lots more sophisticated ways for designing
   instruments even on the C64 and these are just a starting
   point and must never be taken as more than that.

   although wave settings are used properly in this program
   the sound will only be as good as the emulator and the
   host computer that they are run-on.

   I am ignoring filter settings in all of this lot as
   well and really only the very basics of SID sound
   are covered in this program.

   OTOH it is pretty easy to understand for the most part,
   and I had no trouble writing it from the ground-up.

   */
char _inset[19][5] = {
65,     9,     0,  255, 0,  /* Piano       */
65,     9,     0,  0, 6, /* Piano2      */
17,    88,    89,  0, 0, /* Violin      */
33,     9,    33,  0, 0, /* Guitar      */
17,    96,     0,  0, 0, /* Flute       */
17,   148,    64,  0, 0, /* Flute2      */
33,     9,     0,  0, 0, /* Harpsichord */
65,     9,     0,  0, 2, /* Harpsichord2*/
17,     9,     0,  0, 0, /* Xylophone   */
17,     9,     9,  0, 0, /* Xylophone2  */
17,     0,   240,  0, 0, /* Organ       */
65,     9,   144,  0, 8, /* Organ2      */
17,     0,   240,  0, 0, /* Colliape    */
17,   102,     0,  0, 0, /* Accordian   */
33,   192,   192,  0, 0, /* Accordian2  */
33,    96,     0,  0, 0, /* Trumpet     */
65,   137,    65,  0, 2, /* Trumpet2    */
129,    5,    80,  0, 0, /* Drum        */
129,    5,    80,  0, 0};   /* custom - user specified values */


/* if the second and third voices are set then
   they accompany the first voice one octave higher
   and lower in "harmony" or on the same note in "unison"

   in "harmony" playback the voice2 and voice3 playback
   requires a longer duration than 1/10th of a second.

   this is admittedly an empirical decision that I have
   made on this since there is no real basis in musical
   theory for what I am doing in this lot.

   It's only bit-music and 3 voices and doesn't need to be
   overcomplicated with synopticons and such.

   */

int _hu = 0;
int _tempo = 812;

harmony()
{
_hu = 12;
}

unison()
{
_hu = 0;
}

/* tickvalue(0) resets tempo to default */
/* this is actually not tempo but the length of
   the delay which modifies duration.
   812 is an approximation for 1 tick at 18.2 ticks per second */

/* this value may vary slightly on the C64 and you may wish
   to adjust it for fine-tuning...
   or you may wish to speed-up or slow-down a note playback sequence
   (a song) so can speed-up the song by decreasing this value
   and conversely slow-down a song by increasing this value.
   keep in mind that I do absolutely no range checking for this.
   and an out of range composite of delay * tempo can occur */

tickvalue(tkval)
int tkval;
{
   _tempo = 812;

   if (tkval > 0) {
   _tempo = tkval;

   }
}


/* duration is given in 18.2 ticks per second
   which is based on the IBM-PC 1Ch timer tick interrupt.

   this is a convenient form of time compression for
   several reasons. one of them is to conserve memory
   with a 1 byte duration value. another is that
   this allowed me to translate several songs that I already
   had in IBM format to a C64 equivalent without much effort. */

/* voices are set to valid instrument numbers */

play(song,voice1, voice2, voice3)
char *song;
int voice1, voice2, voice3;
{
   unsigned freq, note, dur, len;
   register idx;

   /* type of wave for all three voices */
   /*  triangle, sawtooth, pulse, noise */
   /*  17, 33, 65, 129 */
   /* these values are set by choosing an instrument */
   char wv1=0, wv2=0, wv3=0;

   for (idx = 0; idx < 25; idx++)_sid[idx] = 0; /* clear sound chip */

   /* make sure all voices are in the range of the instruments */
   if (voice1 < 0 || voice1 > 18)voice1 = 0;
   if (voice2 < 0 || voice2 > 18)voice2 = -1;
   if (voice3 < 0 || voice3 > 18)voice3 = -1;

   /* set-up voice 1 */
   wv1 = _inset[voice1][0];
   _spoke[54277] = _inset[voice1][1]; /* Set Attack/Decay for voice 1 */
   _spoke[54278] = _inset[voice1][2]; /* Set Sustain/Release for voice 1 */
   /* if a pulse wave, set pulse width */
   if (wv1 == 65) {
  _spoke[54274] = _inset[voice1][3];/* low bits - pulse width for voice 1 */
  _spoke[54275] = _inset[voice1][4];/* high bits - pulse width for voice 1 */
   }

   /* set-up voice 2 */
   if (voice2 > -1 ) {
    wv2 = _inset[voice2][0];
    _spoke[54284] = _inset[voice2][1];
    _spoke[54285] = _inset[voice2][2];
    if (wv2 == 65) {
    _spoke[54281] = _inset[voice2][3];
_spoke[54282] = _inset[voice2][4];
}
}
   /* set-up voice 3 */
   if (voice3 > -1) {
    wv3 = _inset[voice3][0];
    _spoke[54291] = _inset[voice3][1];
    _spoke[54292] = _inset[voice3][2];
    if (wv3 == 65) {
    _spoke[54288] = _inset[voice3][3];
_spoke[54289] = _inset[voice3][4];
}

   }

   /* Set volume of at maximum - no filters */
   _spoke[54296] = 15; /* Volume and filter modes */
   idx = 0;

   while ((freq = song[idx]) != 0) {
   idx++;
   dur = song[idx];  idx++;

   if (_spress[0] != 0x40)break; /* check for a key down */
       if (freq > 94) freq = 255;
       if (dur == 0)continue;

   if (freq != 255) {
     _freq1[0] = _cfreq[freq];
   _sid[4] = wv1; /* control register */

   if (voice2 > -1 && (_hu == 0 || dur > 2)) {
   _freq2[0] = _cfreq[freq+_hu];
   _sid[11] = wv2;
   }
   if (voice3 > -1 && (_hu == 0 || dur > 2)) {
   _freq3[0] = _cfreq[freq-_hu];
   _sid[18] = wv3;
   }

   }

   len = (dur * _tempo);        /* timing loop */
   while (len > 0)len--;

   if (freq != 255) {
      _sid[4] = wv1-1;
      if (voice2 > -1 && (_hu == 0 || dur > 2)) _sid[11] = wv2-1;
      if (voice3 > -1 && (_hu == 0 || dur > 2)) _sid[18] = wv3-1;
   }

   }

}


/* midi note number, duration in milliseconds */
/* valid instruments for this function are defined in the _inset table.
   there are 18 of them (0-17).

   while this is nowhere close to GM (general midi)
   this is probably adequate to provide quasi-musical sound effects
   within the waveforms in my presets.

   I have left an open slot (18) for a custom instrument
   which you must define properly if used since I do limited range
   checking.

   */
playnote(note,duration,voice1,voice2,voice3)
int note,duration,voice1,voice2,voice3;
{
char song[4];


duration = duration / 55; /* convert to 18.2 ticks per second */

song[0] = note & 0xff;
song[1] = duration & 0xff;
song[2] = 0;
song[3] = 0;

    play(song,voice1,voice2,voice3);

}


/* sets an instrument with new values */
/* set idx to -1 for custom */
/* use the tables above for reference */
setinst(idx, wv, a, d, s, r, lp, hp)
int idx, wv, a, d, s, r, lp, hp;
{

int ad, sr;

a = a & 0xf;
d = d & 0xf;
s = s & 0xf;
r = r & 0xf;

ad = (a * 16) + d; /* attack velocity */
sr = (s * 16) + r; /* sustain velocity */


    if (idx < 0 || idx > 17)idx = 18; /* default custom instrument */

    switch(wv) {
case 0:
case 17: wv = 17; break; /* triangle */

case 1:
case 33: wv = 33; break; /* sawtooth */

case 2:
case 65: wv = 65; break; /* pulse */

case 3:
case 129:
default:
         wv = 129; break; /* noise */
}

    _inset[idx][0] = wv;
    _inset[idx][1] = ad & 0xff; /* Set Attack/Decay */
    _inset[idx][2] = ad & 0xff; /* Set Sustain/Release */

    if (wv != 65) {
lp = 0;
hp = 0;
}
_inset[idx][3] = lp & 0xff; /* low bits - pulse width */
    _inset[idx][4] = hp & 0xf;  /* high bits - pulse width */

}


BillBuckels

SONGDIR - song playback - advanced user interface



This program has a full featured text interface with quasi-midi-sequencer style controls and voice change capabilities. You don't need to download the compiler to try it out:

http://www.clipshop.ca/c64/index.htm#sdemo

The downloadable d64 diskimage at the link above is filled with bit-music SID songs as well as a working version of this program.

The tools to create the user interface and the library routines for most of this program have already been posted by me in other threads. This thread will contain the source for some additional utilities for converting sound files from the IBM-PC to a C64 compatible format for use with this program. But if you really want the "whole meal deal" download the Aztec C64 cross-compiler for XP which contains all this and more.

The Program


/* songdir.c (C) Copyright Bill Buckels 2008 */

/* This program is an original Aztec C65 SID chip demonstration
program. It provides several preset instruments with additional
settings and loads sound files from disk.

It has been built under Windows XP in the Aztec C65
cross-development environment for the C64.

This program links with my B64NAT.LIB and the standard
native mode C64NAT.LIB provided by Manx.

Like any C64 native mode program, this program is loaded from BASIC.

This program uses the 3 voice SID chip that the C64 sported. This
was a fairly complex bit of hardware and you should probably study
the Commodore 64 Programmer's Reference Guide if you require more
clarification. You should also probably understand some of the
physics of sound and perhaps some musical theory, and maybe even
some electrical engineering to really grasp what is going on here.

None of that is a pre-requisite to having some fun with this of
course.

As far as the sound files go they are a series of 2 byte
note,duration pairs that I have converted from olde MS-DOS bit
music of frequency, duration pairs. The note values are given
using the general midi note, but I have stayed with the orginal
ibm duration of 18.2 ticks per second.

The program screen was created in the IBM-PC program TheDraw then
converted to C64 format.

The utilities to do all this conversion are in the TOOLS
directory in this environment complete with source code and
can be referred to for more info.

This is a relatively non-trivial program, and some of the other
samples should be worked through first unless you know C pretty
well and have mucked-with MS-DOS and Commodore 64's in one of
your past-lives. OTOH there are lots of people out there who
are smarter than I who will probably take one look at this
and understand what I am doing and just shake their heads and
wonder why.

For the rest of us, there are manuals and samples, and mastercard.

'Nuff Said.

*/

#define CMAIN 1

#include <poke.h>
#include <colors.h>
#include <sid.h>

/* constants must be short and kept to a minimum */
#define TRUE 1
#define FALSE 0
#define FOFF 17


/* navigation echo to screen */
/* the following coordinates are based on SCR.BSV
   which is the source file for the BLOADed screen */

/* the vars are the same format for each of the
   4 areas of the screen which control song play...

   they consist of a starting row, col,
   the new index, the old index, and the length of the
   menu bar.
*/

/* songs */
int srow = 12, scol = 5, slen = 16;
int sidx = 0;
int sodx = 0;

/* instruments */
int irow = 4, icol = 26, ilen = 13;
int iidx = 0;
int iodx = 0;

/* tempo */
int trow = 23, tcol = 26, tlen = 6;
int tidx = 0;
int todx = 0;

/* voice settings */
int vrow = 4, vcol = 10, vlen = 10;
int vidx = 0;
int vodx = 0;

/* harmony settings */
int hrow = 8, hcol = 10, hlen = 10;
int hidx = 0;
int hodx = 0;

char buffer[40];
char cursong[FOFF];
char songs[10][FOFF];

main()
{
   int idx, fileidx;
   int ch, playkey, curidx;

   int voice = 1; /* default to select voice 1 */
   int voice1 = 0;
   int voice2 = -1;
   int voice3 = -1;
   int tempo = 812;

   scr_mixed(); /* put the C64 in mixed case mode */
   scr_clear(); /* clear screen */

   /* set background to black */
   /* set border to green */
   scr_back(C64_BLACK,C64_GREEN);

   fileidx = dlist("SONGDIR.PRG","SIB");
   if (fileidx == 0) {
   printf("nO sib fILES fOUND!\n");
       /* clear all keypressess */
       while (kbhit());
       /* clear the buffer */
       kbflush();
   goto BOTTOM;
   }

   /* load a binary screen - use default load addresses */
   /* second arg is set to 0 - otherwise override addr */
   bload("SCR.BXC",0);
   bload("SCR.BXD",0);

   /* set-up the defaults */
   /* the displays reflect the initial presets
      of a single voice piano */

   /* use default color already in the BLoaded Screen */
   curidx = 0;
   cursong[0] = 0;
   /* the first 10 songs are loaded into the
      display... they can press spacebar for
      the next 10 */
   for (idx = 0; idx < 10; idx++) {
   if (curidx == fileidx)curidx = 0;
   strcpy(songs[idx],&dirptr[curidx*FOFF]); /* file */
   sprintf(buffer,"%-16s",songs[idx]); /* screen */
       outtext(buffer,srow+idx,scol,-1,0);
       curidx++;
   }
   /* reverse initial song */
   revtext(srow+sidx,scol,slen,1);

   /* reverse initial instrument */
   revtext(irow+iidx,icol,ilen,1);

   /* reverse initial voice setting */
   outtext("A",vrow,vcol+8,-1,0);
   revtext(vrow+vidx,vcol,vlen,1);

   /* reverse initial harmony settings */
   revtext(hrow+hidx,hcol,hlen,1);

   /* reverse initial tempo setting */
   revtext(trow+tidx,tcol,tlen,1);

   /* clear all keypressess */
   while (kbhit());
   /* clear the buffer */
   kbflush();

/* the code below is mostly cosmetic p**p to enhance the
user experience. having said that, communicating the
interface of these jukebox programs has always been a chore
so s*ck-it-up little bear:) this is the way it was done
in MS-DOS and on the Apple II back in those days as well.

No worries tho'. My email works if you get really lost.
*/

TOP:;

    playkey = FALSE;

    ch = toupper(getch());

    if (ch == 'X')goto BOTTOM;

    /* hotkeys for instrument selection - A-R */
    if (ch > 64 && ch < 83) {
        iidx = ch-65;
        revtext(irow+iodx,icol,ilen,0);
        iodx = iidx;
        buffer[0] = ch;
        buffer[1] = 0;
        outtext(buffer,vrow+vidx,vcol+8,-1,1);
        switch(voice) {
  case 2:  voice2 = ch-65; break;
  case 3:  voice3 = ch-65; break;
  case 1:  voice1 = ch-65; break;
  default: break;
    }
    revtext(irow+iodx,icol,ilen,1);
}
/* hotkeys for song selection '1'-'0' */
    /* these keys are for immediate-mode playback */
    /* the arrow key and enter key work as well */
else if (ch > 47 && ch < 58 ) {
  if (ch==48) sidx = 9;
  else sidx = ch-49;
  revtext(srow+sodx,scol,slen,0);
  sodx=sidx;
  revtext(srow+sodx,scol,slen,1);
  playkey = TRUE;
}
/* hotkeys for voice selection and reset */
/* these are the C64 keycodes for F1-F4 */
else if (ch == 133 || ch == 137 || ch == 134 || ch == 138) {
/* reset */
if (ch==138) {
vidx = 3;
    revtext(vrow+vodx,vcol,vlen,0);
        vodx=vidx;
        revtext(vrow+vodx,vcol,vlen,1);
tempo = 812; tidx = 0;
    revtext(trow+todx,tcol,tlen,0);
        todx=tidx;
        revtext(trow+todx,tcol,tlen,1);
unison(); hidx = 0;
revtext(hrow+hodx,hcol,hlen,0);
hodx=hidx;
revtext(hrow+hodx,hcol,hlen,1);
    vidx = 0;
    voice1 = 0;
    voice2 = -1;
    voice3 = -1;
    iidx = 0;
            revtext(irow+iodx,icol,ilen,0);
            iodx = iidx;
            revtext(irow+iodx,icol,ilen,1);
            outtext("A",vrow,vcol+8,-1,0);
            outtext(" ",vrow+1,vcol+8,-1,0);
            outtext(" ",vrow+2,vcol+8,-1,0);
    }
if (ch == 133) vidx = 0;
if (ch == 137) vidx = 1;
if (ch == 134) vidx = 2;
voice = vidx+1;
revtext(vrow+vodx,vcol,vlen,0);
    vodx=vidx;
    revtext(vrow+vodx,vcol,vlen,1);
}
/* hotkeys for harmony-unison toggle F5-F6 */
else if (ch == 135 || ch == 139) {
          if (ch == 135) {
  hidx = 0;
  unison(); /* unison */
  }
          else {
  hidx = 1;
  harmony(); /* harmony */
  }
  revtext(hrow+hodx,hcol,hlen,0);
      hodx=hidx;
      revtext(hrow+hodx,hcol,hlen,1);
    }
    /* hot keys for playback speed */
else if (ch == 'S' || ch == 'T') {
  /* tempo */
          /* adjust timing loop count for song play */
  /* this is really the inverse of tempo */
          if (ch == 'S') {
             tidx = 0;
             if (tempo < 1612)tempo += 100;
  }
          else {
tidx = 1;
if (tempo > 812)tempo -= 100;
  }
  revtext(trow+todx,tcol,tlen,0);
      todx=tidx;
      revtext(trow+todx,tcol,tlen,1);
}
/* arrow keys for selecting a song visually */
else if (ch == 145 || ch == 17) {
      /* up arrow, down arrow */
      if (ch == 145)sidx -= 1;
      else sidx += 1;
      if (sidx > 9)sidx = 0;
      if (sidx < 0)sidx = 9;
      /* since arrows are used for selection
         this is display only...
         an enter key press or a number key is required to
         play a song */
  revtext(srow+sodx,scol,slen,0);
  sodx=sidx;
  revtext(srow+sodx,scol,slen,1);

}
/* the enter key will play the currently selected song */
else if (ch == 13) {
/* enterkey */
playkey = TRUE;
}
/* a spacebar press will page down to the next 10 songs */
/* and starts over at the beginning when the end is reached */
else if (ch == 32) {
        /* space bar */
   for (idx = 0; idx < 10; idx++) {
   if (curidx == fileidx)curidx = 0;
   strcpy(songs[idx],&dirptr[curidx*FOFF]); /* file */
   sprintf(buffer,"%-16s",songs[idx]); /* screen */
   outtext(buffer,srow+idx,scol,-1,0);
   curidx++;
   }
   /* reverse initial song */
   revtext(srow+sidx,scol,slen,1);
}

    /* crux of the biscuit */
    /* play the song */
if (playkey == TRUE) {
/* while we are loading, the song goes red */
/* we are certainly not going to interrupt
   the file reading process */
attext(srow+sidx,scol,slen,10);
        attext(vrow+vodx,vcol,vlen,7);
    attext(trow+todx,tcol,tlen,7);
    attext(hrow+hodx,hcol,hlen,7);
        attext(irow+iodx,icol,ilen,7);
    if (songload(sidx) == 0) {
/* if someone is banging away on a key
   by the time we are finished loading
   we just don't play the song */
if (!kbhit()) {
   /* the song is white while playing and
      the other stuff selected in the lists are green */
   attext(srow+sidx,scol,slen,1);
   tickvalue(tempo);
   play(&poke[dirtop],voice1,voice2,voice3);
    }
}
    playkey = FALSE;
attext(srow+sidx,scol,slen,7);
        attext(vrow+vodx,vcol,vlen,1);
    attext(trow+todx,tcol,tlen,1);
    attext(hrow+hodx,hcol,hlen,1);
        attext(irow+iodx,icol,ilen,1);

        /* if they are banging, wait until they finish */
        while (kbhit());
        /* clear the keyboard buffer before returning to the menu */
        kbflush();


}

    /* any other keys are thrown away */
    goto TOP;


BOTTOM:;


   scr_clear(); /* clear screen */
   scr_upper(); /* reset from mixed case mode */
   /* set background to blue */
   /* set border to lblue */
   scr_back(C64_BLUE,C64_LBLUE);
   /* stuff something in the keyboard to make Aztec C happy */
   kbuf[0] = 'X';
   kbuflen[0]=1;
   getchar();
   scr_clear(); /* clear screen to erase the X */

   exit(0);

}


songload(choice)
{
/* we are using a static area from the end of the
   file list to the end of the graphics screen
   to load the songs. if lots of songs are
   on the disk and some of them are too large,
   the large ones will be clipped. */

int fh;
int length = 16192 - dirtop;

    /* no need to reload song if already loaded */
if (strcmp(songs[choice],cursong)== 0)return 0;
fh = open(songs[choice],0);
if (fh != -1) {
     read(fh, &poke[dirtop],length);
     close(fh);
     strcpy(cursong,songs[choice]);
     return 0;
}
return 1;
}



The MAKEFILE


# -----------------------------------------
# Aztec C64 makefile by bill buckels 2007
# -----------------------------------------

PRG=songdir

$(PRG).B64: $(PRG).asm
   as65 $(PRG).asm
   del $(PRG).asm
   copy $(CLIB65)B64NAT.LIB .
   copy $(CLIB65)C64NAT.LIB .
   ln65 $(PRG).rel B64NAT.LIB C64NAT.LIB -b 810 -d 4080 -c 4580
   del $(PRG).rel
   del C64NAT.LIB
   del B64NAT.LIB
   MKBASIC $(PRG) $(PRG).prg
   del $(PRG)
   
   
$(PRG).asm: $(PRG).c MAKEFILE
    copy $(INCL65)poke.h .
    copy $(INCL65)colors.h .
    copy $(INCL65)sid.h .
    c65 $(PRG).c
    del poke.h
    del colors.h 
    del sid.h


BillBuckels

A Utility to Convert IBM-PC Speaker Sound Files to a C64 compatible format

This utility is a companion module bundled with the Aztec-C C64 compiler. It reformats the little speaker sound files that we used on the IBM-PC back in the 1980's and 1990's into something that is useful on the C64.

The Utility - SND2C64


/* snd2c64 (C) Copyright Bill Buckels 2008 */


/* converts binary musical files of type .SND   */
/* which are IBM files containing frequency, duration pairs in IBM format */
/* to character arrays for embedding in Commodore 64 C code
   additionally creates a binary file of the same format for optional
   playing from disk.
*/

/* the output file is midi note number, duration in 18.2 ticks per second  */
/* the use of these need not be confined to the C64 and although they are
   fairly primitive bitmusic they can be enhanced to play reasonably
   well in other programs */

/* on the C64 I define my own ADSR sound waves to a variety
  of simple intruments which are presets and then convert
  the midi note that this program resolves to a C64 frequency in a
  xref table.

  I just use a relative timing loop for duration which has
  turned-out to be reasonably accurate in translating from 18.2 tps.
  Since these SND files are time based rather than tempo based
  it is trivial to translate these to a midi note on, midi note off
  in a more sophisticated platform.


  I have also defined my own C64 fequency table. The table in the
  C64 user's guide is 100% inaccurate as near as I can tell.

  */



/*
IBM format (similar to GWBASIC sound statement)

frequency    The frequency of the sound in hertz; a value in the range
   37 through 32,767.
duration     The number of system clock ticks the sound lasts; a value
   in the range 0 through 65,535. There are 18.2 clock ticks
   per second.

*/

/*

Octave # Note Numbers

C C# D D# E F F# G G# A A# B
-1 0 1 2 3 4 5 6 7 8 9 10 11
0 12 13 14  15 16 17 18 19 20 21 22  23
1 24 25 26 27 28 29 30 31 32 33 34 35
2 36 37 38 39 40 41 42 43 44 45 46 47
3 48 49 50 51 52 53 54  55 56 57 58 59
4 60 61 62 63 64 65 66 67 68 69 70 71
5 72 73 74 75 76 77 78 79 80 81 82 83
6 84 85 86 87 88 89 90 91 92 93 94 95
7 96 97 98 99 100 101 102 103 104 105 106 107
8 108 109 110 111 112 113 114 115 116 117 118 119
9 120 121 122 123 124 125  126 127


Note: The MIDI specification only defines note number 60 as "Middle C",
and all other notes are relative. The absolute octave number designations
shown here are based on Middle C = C4, which is an arbitrary assignment.


*/

#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <io.h>
#include <fcntl.h>


/* note although i do not use this table in this program
   i left it here for reference */
/* the following C64 SID frequencies represent
   general midi octaves -1 through 6 */
/* to find midi note 60 (mid C) the offset is 60 */

unsigned int cfreq[95]={
  274,  291,  308,  326,  345,  366,  388,  411,  435,  461,  488,  517,
  549,  582,  616,  653,  691,  733,  776,  822,  871,  923,  977, 1035,
1099, 1164, 1233, 1306, 1383, 1466, 1553, 1645, 1742, 1846, 1955, 2071,
2198, 2328, 2466, 2613, 2767, 2932, 3106, 3290, 3485, 3692, 3911, 4143,
4396, 4657, 4933, 5226, 5535, 5864, 6212, 6580, 6970, 7384, 7822, 8287,
8793, 9315, 9867,10452,11071,11728,12424,13161,13941,14769,15645,16574,
17557,18600,19704,20874,22113,23426,24818,26292,27853,29508,31261,33118,
35084,37170,39378,41718,44196,46822,49606,52554,55676,58986,62492};


/*

Relationships of Notes and Frequencies on the IBM-PC

Note Frequency
C 130.810
D 146.830
E 164.810
F 174.610
G 196.000
A 220.000
B 246.940
C 261.630
D 293.660
E 329.630
F 349.230
G 392.000
A 440.000
B 493.880
C 523.250 middle C (midi note #60)
D 587.330
E 659.260
F 698.460
G 783.990
A 880.000
B 987.770
C 1046.500
D 1174.700
E 1318.500
F 1396.900
G 1568.000
A 1760.000
B 1975.500

*/


/* ------------------------------------------------------------------- */
/* Translates a frequency within a range to a corresponding midi note. */
/* ------------------------------------------------------------------- */
int xplay(int xfrequency)
{

  /* octaves out of C64 range are ignored */
  if(xfrequency < 32 || xfrequency > 8192 )
  {
    /* if we are out of range don't play anything */
    return -1;
  }

  /* octave 0 */
  if(xfrequency<63)
  {
  if(xfrequency<34 ) return  12;
  if(xfrequency<36 ) return  13;
  if(xfrequency<38 ) return  14;
  if(xfrequency<40 ) return  15;
  if(xfrequency<42 ) return  16;
  if(xfrequency<45 ) return  17;
  if(xfrequency<48 ) return  18;
  if(xfrequency<51)  return  19;
  if(xfrequency<54)  return  20;
  if(xfrequency<57)  return  21;
  if(xfrequency<60)  return  22;
                     return  23;
  }

  /* first octave */
  if(xfrequency<127)
  {
  if(xfrequency<67 ) return  24;
  if(xfrequency<71 ) return  25;
  if(xfrequency<76 ) return  26;
  if(xfrequency<80 ) return  27;
  if(xfrequency<85 ) return  28;
  if(xfrequency<90 ) return  29;
  if(xfrequency<96 ) return  30;
  if(xfrequency<101) return  31;
  if(xfrequency<107) return  32;
  if(xfrequency<114) return  33;
  if(xfrequency<120) return  34;
                     return  35;
  }

  /* second octave */
  if(xfrequency<255)
  {
  if(xfrequency<136) return  36;
  if(xfrequency<143) return  37;
  if(xfrequency<152) return  38;
  if(xfrequency<160) return  39;
  if(xfrequency<170) return  40;
  if(xfrequency<180) return  41;
  if(xfrequency<190) return  42;
  if(xfrequency<202) return  43;
  if(xfrequency<215) return  44;
  if(xfrequency<227) return  45;
  if(xfrequency<240) return  46;
                     return  47;
  }

  /* third octave */
  if(xfrequency<512)
  {
   if(xfrequency<270)return  48;
   if(xfrequency<286)return  49;
   if(xfrequency<304)return  50;
   if(xfrequency<322)return  51;
   if(xfrequency<340)return  52;
   if(xfrequency<360)return  53;
   if(xfrequency<382)return  54;
   if(xfrequency<406)return  55;
   if(xfrequency<428)return  56;
   if(xfrequency<454)return  57;
   if(xfrequency<480)return  58;
                     return  59;
   }

  /* fourth octave */
  if(xfrequency<1024)
  {
  if(xfrequency<540)return 60; /* middle C (C4) */
  if(xfrequency<572)return 61;
  if(xfrequency<605)return 62;
  if(xfrequency<643)return 63;
  if(xfrequency<680)return 64;
  if(xfrequency<720)return 65;
  if(xfrequency<765)return 66;
  if(xfrequency<810)return 67;
  if(xfrequency<855)return 68;
  if(xfrequency<910)return 69;
  if(xfrequency<965)return 70;
                    return 71;
  }

  /* fifth octave */
  if(xfrequency<2048)
  {
  if(xfrequency<1080) return 72;
  if(xfrequency<1144) return 73;
  if(xfrequency<1210) return 74;
  if(xfrequency<1286) return 75;
  if(xfrequency<1340) return 76;
  if(xfrequency<1440) return 77;
  if(xfrequency<1530) return 78;
  if(xfrequency<1620) return 79;
  if(xfrequency<1710) return 80;
  if(xfrequency<1820) return 81;
  if(xfrequency<1930) return 82;
                      return 83;
  }

  if (xfrequency < 4096)
  {

   /* sixth octave */
   /* seventh, eight, and ninth octave ignored */
   if(xfrequency<2160)return 84;
   if(xfrequency<2288)return 85;
   if(xfrequency<2420)return 86;
   if(xfrequency<2572)return 87;
   if(xfrequency<2680)return 88;
   if(xfrequency<2880)return 89;
   if(xfrequency<3060)return 90;
   if(xfrequency<3240)return 91;
   if(xfrequency<3420)return 92;
   if(xfrequency<3640)return 93;
   if(xfrequency<3860)return 94;
                      return 95;
  }

   /* seventh octave */
   /* eight, and ninth octave ignored */
   if(xfrequency<4320)return 96;
   if(xfrequency<4576)return 97;
   if(xfrequency<4840)return 98;
   if(xfrequency<5144)return 99;
   if(xfrequency<5360)return 100;
   if(xfrequency<5760)return 101;
   if(xfrequency<6120)return 102;
   if(xfrequency<6480)return 103;
   if(xfrequency<6840)return 104;
   if(xfrequency<7280)return 105;
   if(xfrequency<7720)return 106;
   return 107;
}

main(int argc, char *argv[])
{
   FILE *fp,*fp2,*fp3;
   char *wordptr;
   unsigned freq, dur;

   int frequency,duration,counter=0;
   char scratchbuffer[128],writebuffer[128], binbuffer[128];

   if(argc!=2)
   {
    printf("SND filename required...");
    exit(0);
    }

   if((fp=fopen(argv[1],"rb"))==NULL)
   {
    perror(argv[1]);
    exit(0);
    }

   strcpy(scratchbuffer,argv[1]);
   wordptr=strtok(scratchbuffer,".");
   sprintf(writebuffer,"%s.TXT",scratchbuffer);
   sprintf(binbuffer,"%s.SIB",scratchbuffer);
   fp2=fopen(writebuffer,"w");
   fp3=fopen(binbuffer,"wb");

   fprintf(fp2,
   "/* musical array created from IBM snd file %s */\n",argv[1]);
   fprintf(fp2,
   "/* array structure is midi note number, duration (18.2 ticks sec) */\n");
   fprintf(fp2,
   "char %s[]={\n",scratchbuffer);

    while((frequency=getw(fp))!=-1){
         duration=fgetc(fp);
         if (duration == 0) continue; /* ignore all notes of 0 duration */

         dur = duration;

         if((frequency=xplay(frequency))<0)
         {
            freq = 255; /* silence */
         }
         else
         {

    freq = frequency;
         }
         fprintf(fp2,"%3d, %3d,",freq,dur);

         fputc(freq,fp3);
         fputc(dur,fp3);

         counter++;
         if(counter==8){         /* wide enough */
            fprintf(fp2,"\n");
            counter=0;
         }
    }
  /* use 0 as the terminator */
  fprintf(fp2,"%3d, %3d};\n",0,0);


    fputc(0,fp3);
    fputc(0,fp3);

   fclose(fp);
   fclose(fp2);
   fclose(fp3);
   exit(0);

}


The MAKEFILE

Note: My Aztec64 distribution comes with many tools and extras. This makefile builds 3 of them including SND2C64.


PGMNAME=sound
PG2=sndfrag
PG3=snd2c64
all: ..\$(PGMNAME).exe ..\$(PG2).exe ..\$(PG3).exe

$(PGMNAME).obj: $(PGMNAME).c
      cl -c -AL -Zp1 /D _BUILDEXE_ $(PGMNAME).c

..\$(PGMNAME).exe: $(PGMNAME).obj $(PGMNAME).c MAKEFILE
     link $(PGMNAME).obj,..\$(PGMNAME).exe, NUL, /ST:8192 /NOE /NOD oldnames llibce, NUL
     del $(PGMNAME).obj

$(PG2).obj: $(PG2).c
      cl -c -AL -Zp1 /D _BUILDEXE_ $(PG2).c

..\$(PG2).exe: $(PG2).obj $(PG2).c MAKEFILE
     link $(PG2).obj,..\$(PG2).exe, NUL, /ST:8192 /NOE /NOD oldnames llibce, NUL
     del $(PG2).obj

$(PG3).obj: $(PG3).c
      cl -c -AL -Zp1 /D _BUILDEXE_ $(PG3).c

..\$(PG3).exe: $(PG3).obj $(PG3).c MAKEFILE
     link $(PG3).obj,..\$(PG3).exe, NUL, /ST:8192 /NOE /NOD oldnames llibce, NUL
     del $(PG3).obj