From 15ac1a8086971cb500bd09dd97bf62e111c2be90 Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Tue, 13 Sep 2016 04:49:13 -0400 Subject: pokey emulation, plus more labels --- pokeytest/Makefile | 15 + pokeytest/playsdl2.c | 153 +++++++++ pokeytest/playsnd.c | 114 +++++++ pokeytest/pokey.c | 764 +++++++++++++++++++++++++++++++++++++++++++ pokeytest/pokey.h | 77 +++++ pokeytest/pokey11.txt | 277 ++++++++++++++++ pokeytest/pokey11_readme.txt | 119 +++++++ pokeytest/readme.txt | 25 ++ 8 files changed, 1544 insertions(+) create mode 100644 pokeytest/Makefile create mode 100644 pokeytest/playsdl2.c create mode 100644 pokeytest/playsnd.c create mode 100644 pokeytest/pokey.c create mode 100644 pokeytest/pokey.h create mode 100644 pokeytest/pokey11.txt create mode 100644 pokeytest/pokey11_readme.txt create mode 100644 pokeytest/readme.txt (limited to 'pokeytest') diff --git a/pokeytest/Makefile b/pokeytest/Makefile new file mode 100644 index 0000000..e629344 --- /dev/null +++ b/pokeytest/Makefile @@ -0,0 +1,15 @@ + +all: + gcc -O2 -Wall -o playsdl2 playsdl2.c pokey.c `pkg-config sdl2 --cflags --libs` + +#gcc -O2 -Wall -o playsnd playsnd.c pokey.c + +#test: all +#./playsnd > 1.raw +#aplay -r 44100 -c 1 -t raw -f u8 1.raw + +test: all + ./playsdl2 $(TEST_SFX) + +clean: + rm -f *.o playsnd playsdl2 *.raw core diff --git a/pokeytest/playsdl2.c b/pokeytest/playsdl2.c new file mode 100644 index 0000000..3970a2e --- /dev/null +++ b/pokeytest/playsdl2.c @@ -0,0 +1,153 @@ +#include + +#include +#include + +#include "pokey.h" + +#define ROM_FILE "../jumpmanjr.rom" +#define ROM_SIZE 0x4000 + +#define FREQ 44100 + +#define PAL_BUF_SIZE 882 +#define NTSC_BUF_SIZE 735 + +#define PAL_DELAY 20 +#define NTSC_DELAY 17 + +unsigned char sndbuf[PAL_BUF_SIZE + 1]; + +int buf_size = NTSC_BUF_SIZE; +int delay = NTSC_DELAY; + +unsigned char rom[ROM_SIZE]; + +void usage(void) { + fprintf(stderr, "usage: playsnd [-p] 0xaddr\n"); + exit(1); +} + +void load_rom(void) { + int size; + FILE *f; + + f = fopen(ROM_FILE, "rb"); + if(!f) { + perror(ROM_FILE); + usage(); + } + + size = fread(rom, 1, ROM_SIZE, f); + if(size != ROM_SIZE) { + fprintf(stderr, "file %s should be %d bytes long, not %d\n", + ROM_FILE, ROM_SIZE, size); + usage(); + } + + fprintf(stderr, "loaded ROM %s, %d bytes\n", ROM_FILE, size); +} + +void play_frames(unsigned char count) { + fprintf(stderr, "playing for %d frames\n", count); + while(count--) { + SDL_Delay(delay); + } +} + +void play_sfx(int addr) { + unsigned char *p = rom + (addr - 0x8000); + + while(1) { + fprintf(stderr, "$%04lx: $%02x\n", (p - rom + 0x8000), *p); + switch(*p) { + case 0: + fprintf(stderr, "opcode 0 (snd_end)\n"); + return; + case 1: + fprintf(stderr, "opcode 1 (snd_audc)\n"); + Update_pokey_sound(audc1, *++p); + play_frames(*++p); + ++p; + break; + case 2: + fprintf(stderr, "opcode 2 (snd_jump)\n"); + addr = *++p; + addr |= ( (*++p) << 8); + addr -= 0x8000; + p = rom + addr; + break; + case 3: + fprintf(stderr, "opcode 3 (snd_rest)\n"); + Update_pokey_sound(audc1, 0); + play_frames(*++p); + ++p; + break; + default: + fprintf(stderr, "opcode $%02x (snd_note)\n", *p); + Update_pokey_sound(audf1, *p); + play_frames(*++p); + ++p; + break; + } + } +} + +void sdl_audio_callback(void *userdata, Uint8* stream, int len) { + Pokey_process(stream, len); +} + +void init_sdl_audio(void) { + SDL_AudioSpec want, have; + SDL_AudioDeviceID dev; + + if(SDL_Init(SDL_INIT_AUDIO) < 0) { + fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError()); + exit(-1); + } + atexit(SDL_Quit); + + SDL_memset(&want, 0, sizeof(want)); + want.freq = FREQ; + want.format = AUDIO_U8; + want.channels = 1; + want.samples = buf_size; + want.callback = sdl_audio_callback; + + dev = SDL_OpenAudioDevice(0, 0, &want, &have, 0); + if(!dev) { + fprintf(stderr, "Failed to open audio: %s\n", SDL_GetError()); + exit(-1); + } + + SDL_PauseAudioDevice(dev, 0); +} + +int main(int argc, char **argv) { + int addr; + + if(argc < 2 || argc > 3) usage(); + + if(argc == 3) { + buf_size = PAL_BUF_SIZE; + delay = PAL_DELAY; + addr = (int)strtol(argv[2], 0, 16); + } else { + addr = (int)strtol(argv[1], 0, 16); + } + + if(addr < 0x8000 || addr > 0xbfff) { + fprintf(stderr, "invalid address, must be 0x8fff thru 0xbfff\n"); + usage(); + } + + load_rom(); + + fprintf(stderr, "playing sfx at $%04x, buf_size %d\n", addr, buf_size); + + Pokey_sound_init(FREQ_17_APPROX, FREQ); + init_sdl_audio(); + play_sfx(addr); + + return(0); +} diff --git a/pokeytest/playsnd.c b/pokeytest/playsnd.c new file mode 100644 index 0000000..0f76879 --- /dev/null +++ b/pokeytest/playsnd.c @@ -0,0 +1,114 @@ +#include +#include + +#include "pokey.h" + +#define ROM_FILE "../jumpmanjr.rom" +#define ROM_SIZE 0x4000 + +#define PAL_BUF_SIZE 882 +#define NTSC_BUF_SIZE 735 + +unsigned char sndbuf[PAL_BUF_SIZE + 1]; + +unsigned char rom[ROM_SIZE]; + +void usage(void) { + fprintf(stderr, "usage: playsnd [-p] 0xaddr\n"); + exit(1); +} + +void load_rom(void) { + int size; + FILE *f; + + f = fopen(ROM_FILE, "rb"); + if(!f) { + perror(ROM_FILE); + usage(); + } + + size = fread(rom, 1, ROM_SIZE, f); + if(size != ROM_SIZE) { + fprintf(stderr, "file %s should be %d bytes long, not %d\n", + ROM_FILE, ROM_SIZE, size); + usage(); + } + + fprintf(stderr, "loaded ROM %s, %d bytes\n", ROM_FILE, size); +} + +void play_frames(unsigned char count, int buf_size) { + fprintf(stderr, "playing for %d frames\n", count); + while(count--) { + Pokey_process(sndbuf, buf_size); + fwrite(sndbuf, buf_size, 1, stdout); + } +} + +void play_sfx(int addr, int buf_size) { + unsigned char *p = rom + (addr - 0x8000); + + while(1) { + fprintf(stderr, "$%04lx: $%02x\n", (p - rom + 0x8000), *p); + switch(*p) { + case 0: + fprintf(stderr, "opcode 0 (snd_end)\n"); + return; + case 1: + fprintf(stderr, "opcode 1 (snd_audc)\n"); + Update_pokey_sound(audc1, *++p); + play_frames(*++p, buf_size); + ++p; + break; + case 2: + fprintf(stderr, "opcode 2 (snd_jump)\n"); + addr = *++p; + addr |= ( (*++p) << 8); + addr -= 0x8000; + p = rom + addr; + break; + case 3: + fprintf(stderr, "opcode 3 (snd_rest)\n"); + Update_pokey_sound(audc1, 0); + play_frames(*++p, buf_size); + ++p; + break; + default: + fprintf(stderr, "opcode $%02x (snd_note)\n", *p); + Update_pokey_sound(audf1, *p); + play_frames(*++p, buf_size); + ++p; + break; + } + } +} + +int main(int argc, char **argv) { + int buf_size = NTSC_BUF_SIZE; + int addr; + + if(argc < 2 || argc > 3) usage(); + + if(argc == 3) { + buf_size = PAL_BUF_SIZE; + addr = (int)strtol(argv[2], 0, 0); + } else { + addr = (int)strtol(argv[1], 0, 0); + } + + if(addr < 0x8000 || addr > 0xbfff) { + fprintf(stderr, "invalid address, must be 0x8fff thru 0xbfff\n"); + usage(); + } + + load_rom(); + + fprintf(stderr, "playing sfx at $%04x, buf_size %d\n", addr, buf_size); + + Pokey_sound_init(FREQ_17_APPROX, 44100); + play_sfx(addr, buf_size); + + return(0); +} + diff --git a/pokeytest/pokey.c b/pokeytest/pokey.c new file mode 100644 index 0000000..11a51af --- /dev/null +++ b/pokeytest/pokey.c @@ -0,0 +1,764 @@ +/*****************************************************************************/ +/* */ +/* Module: POKEY Chip Simulator, V1.1 */ +/* Purpose: To emulate the sound generation hardware of the Atari POKEY chip.*/ +/* Author: Ron Fries */ +/* Date: September 22, 1996 */ +/* */ +/*****************************************************************************/ +/* */ +/* License Information and Copyright Notice */ +/* ======================================== */ +/* */ +/* PokeySound is Copyright(c) 1996 by Ron Fries */ +/* */ +/* This library is free software; you can redistribute it and/or modify it */ +/* under the terms of version 2 of the GNU Library General Public License */ +/* as published by the Free Software Foundation. */ +/* */ +/* This library 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 Library */ +/* General Public License for more details. */ +/* To obtain a copy of the GNU Library General Public License, write to the */ +/* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* */ +/* Any permitted reproduction of these routines, in whole or in part, must */ +/* bear this legend. */ +/* */ +/*****************************************************************************/ + +#include +#include +#include + +#include "pokey.h" + +/* CONSTANT DEFINITIONS */ + +/* definitions for AUDCx (D201, D203, D205, D207) */ +#define NOTPOLY5 0x80 /* selects POLY5 or direct CLOCK */ +#define POLY4 0x40 /* selects POLY4 or POLY17 */ +#define PURE 0x20 /* selects POLY4/17 or PURE tone */ +#define VOL_ONLY 0x10 /* selects VOLUME OUTPUT ONLY */ +#define VOLUME_MASK 0x0f /* volume mask */ + +/* definitions for AUDCTL (D208) */ +#define POLY9 0x80 /* selects POLY9 or POLY17 */ +#define CH1_179 0x40 /* selects 1.78979 MHz for Ch 1 */ +#define CH3_179 0x20 /* selects 1.78979 MHz for Ch 3 */ +#define CH1_CH2 0x10 /* clocks channel 1 w/channel 2 */ +#define CH3_CH4 0x08 /* clocks channel 3 w/channel 4 */ +#define CH1_FILTER 0x04 /* selects channel 1 high pass filter */ +#define CH2_FILTER 0x02 /* selects channel 2 high pass filter */ +#define CLOCK_15 0x01 /* selects 15.6999kHz or 63.9210kHz */ + +#define AUDF1_C 0xd200 +#define AUDC1_C 0xd201 +#define AUDF2_C 0xd202 +#define AUDC2_C 0xd203 +#define AUDF3_C 0xd204 +#define AUDC3_C 0xd205 +#define AUDF4_C 0xd206 +#define AUDC4_C 0xd207 +#define AUDCTL_C 0xd208 + +/* for accuracy, the 64kHz and 15kHz clocks are exact divisions of + the 1.79MHz clock */ +#define DIV_64 28 /* divisor for 1.79MHz clock to 64 kHz */ +#define DIV_15 114 /* divisor for 1.79MHz clock to 15 kHz */ + +/* the size (in entries) of the 4 polynomial tables */ +#define POLY4_SIZE 0x000f +#define POLY5_SIZE 0x001f +#define POLY9_SIZE 0x01ff + +#ifdef COMP16 /* if 16-bit compiler */ +#define POLY17_SIZE 0x00007fffL /* reduced to 15 bits for simplicity */ +#else +#define POLY17_SIZE 0x0001ffffL /* else use the full 17 bits */ +#endif + +/* channel definitions */ +#define CHAN1 0 +#define CHAN2 1 +#define CHAN3 2 +#define CHAN4 3 +#define SAMPLE 4 + + +#define FALSE 0 +#define TRUE 1 + + +/* GLOBAL VARIABLE DEFINITIONS */ + +/* structures to hold the 9 pokey control bytes */ +static uint8 AUDF[4]; /* AUDFx (D200, D202, D204, D206) */ +static uint8 AUDC[4]; /* AUDCx (D201, D203, D205, D207) */ +static uint8 AUDCTL; /* AUDCTL (D208) */ + +static uint8 Outbit[4]; /* current state of the output (high or low) */ + +static uint8 Outvol[4]; /* last output volume for each channel */ + + +/* Initialze the bit patterns for the polynomials. */ + +/* The 4bit and 5bit patterns are the identical ones used in the pokey chip. */ +/* Though the patterns could be packed with 8 bits per byte, using only a */ +/* single bit per byte keeps the math simple, which is important for */ +/* efficient processing. */ + +static uint8 bit4[POLY4_SIZE] = + { 1,1,0,1,1,1,0,0,0,0,1,0,1,0,0 }; + +static uint8 bit5[POLY5_SIZE] = + { 0,0,1,1,0,0,0,1,1,1,1,0,0,1,0,1,0,1,1,0,1,1,1,0,1,0,0,0,0,0,1 }; + +static uint8 bit17[POLY17_SIZE]; /* Rather than have a table with 131071 */ + /* entries, I use a random number generator. */ + /* It shouldn't make much difference since */ + /* the pattern rarely repeats anyway. */ + +static uint32 Poly17_size; /* global for the poly 17 size, since it can */ + /* be changed from 17 bit to 9 bit */ + +static uint32 Poly_adjust; /* the amount that the polynomial will need */ + /* to be adjusted to process the next bit */ + +static uint32 P4=0, /* Global position pointer for the 4-bit POLY array */ + P5=0, /* Global position pointer for the 5-bit POLY array */ + P17=0; /* Global position pointer for the 17-bit POLY array */ + +static uint32 Div_n_cnt[4], /* Divide by n counter. one for each channel */ + Div_n_max[4]; /* Divide by n maximum, one for each channel */ + +static uint32 Samp_n_max, /* Sample max. For accuracy, it is *256 */ + Samp_n_cnt[2]; /* Sample cnt. */ + +static uint32 Base_mult; /* selects either 64Khz or 15Khz clock mult */ + +/*****************************************************************************/ +/* In my routines, I treat the sample output as another divide by N counter */ +/* For better accuracy, the Samp_n_cnt has a fixed binary decimal point */ +/* which has 8 binary digits to the right of the decimal point. I use a two */ +/* byte array to give me a minimum of 40 bits, and then use pointer math to */ +/* reference either the 24.8 whole/fraction combination or the 32-bit whole */ +/* only number. This is mainly used to keep the math simple for */ +/* optimization. See below: */ +/* */ +/* xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | xxxxxxxx xxxxxxxx xxxxxxxx.xxxxxxxx */ +/* unused unused unused whole whole whole whole fraction */ +/* */ +/* Samp_n_cnt[0] gives me a 32-bit int 24 whole bits with 8 fractional bits, */ +/* while (uint32 *)((uint8 *)(&Samp_n_cnt[0])+1) gives me the 32-bit whole */ +/* number only. */ +/*****************************************************************************/ + + +/*****************************************************************************/ +/* Module: Pokey_sound_init() */ +/* Purpose: to handle the power-up initialization functions */ +/* these functions should only be executed on a cold-restart */ +/* */ +/* Author: Ron Fries */ +/* Date: September 22, 1996 */ +/* */ +/* Inputs: freq17 - the value for the '1.79MHz' Pokey audio clock */ +/* playback_freq - the playback frequency in samples per second */ +/* */ +/* Outputs: Adjusts local globals - no return value */ +/* */ +/*****************************************************************************/ + +void Pokey_sound_init (uint32 freq17, uint16 playback_freq) +{ + uint8 chan; + int32 n; + + /* fill the 17bit polynomial with random bits */ + for (n=0; n> 8))) + { + /* then set the channel to the selected volume */ + Outvol[chan] = AUDC[chan] & VOLUME_MASK; + /* and set channel freq to max to reduce processing */ + Div_n_max[chan] = 0x7fffffffL; + } + } + } +} + + +/*****************************************************************************/ +/* Module: Pokey_process_2() */ +/* Purpose: To fill the output buffer with the sound output based on the */ +/* pokey chip parameters. This routine has not been optimized. */ +/* Though it is not used by the program, I've left it for reference.*/ +/* */ +/* Author: Ron Fries */ +/* Date: September 22, 1996 */ +/* */ +/* Inputs: *buffer - pointer to the buffer where the audio output will */ +/* be placed */ +/* n - size of the playback buffer */ +/* */ +/* Outputs: the buffer will be filled with n bytes of audio - no return val */ +/* */ +/*****************************************************************************/ + +void Pokey_process_2 (register unsigned char *buffer, register uint16 n) +{ + register uint32 *samp_cnt_w_ptr; + register uint32 event_min; + register uint8 next_event; + register uint8 cur_val; + register uint8 chan; + + /* set a pointer to the whole portion of the samp_n_cnt */ + samp_cnt_w_ptr = (uint32 *)((uint8 *)(&Samp_n_cnt[0])+1); + + /* loop until the buffer is filled */ + while (n) + { + /* Normally the routine would simply decrement the 'div by N' */ + /* counters and react when they reach zero. Since we normally */ + /* won't be processing except once every 80 or so counts, */ + /* I've optimized by finding the smallest count and then */ + /* 'accelerated' time by adjusting all pointers by that amount. */ + + /* find next smallest event (either sample or chan 1-4) */ + next_event = SAMPLE; + event_min = *samp_cnt_w_ptr; + + for (chan = CHAN1; chan <= CHAN4; chan++) + { + if (Div_n_cnt[chan] <= event_min) + { + event_min = Div_n_cnt[chan]; + next_event = chan; + } + } + + + /* decrement all counters by the smallest count found */ + for (chan = CHAN1; chan <= CHAN4; chan++) + { + Div_n_cnt[chan] -= event_min; + } + + *samp_cnt_w_ptr -= event_min; + + /* since the polynomials require a mod (%) function which is + division, I don't adjust the polynomials on the SAMPLE events, + only the CHAN events. I have to keep track of the change, + though. */ + Poly_adjust += event_min; + + /* if the next event is a channel change */ + if (next_event != SAMPLE) + { + /* shift the polynomial counters */ + P4 = (P4 + Poly_adjust) % POLY4_SIZE; + P5 = (P5 + Poly_adjust) % POLY5_SIZE; + P17 = (P17 + Poly_adjust) % Poly17_size; + + /* reset the polynomial adjust counter to zero */ + Poly_adjust = 0; + + /* adjust channel counter */ + Div_n_cnt[next_event] += Div_n_max[next_event]; + + /* From here, a good understanding of the hardware is required */ + /* to understand what is happening. I won't be able to provide */ + /* much description to explain it here. */ + + /* if the output is pure or the output is poly5 and the poly5 bit */ + /* is set */ + if ((AUDC[next_event] & NOTPOLY5) || bit5[P5]) + { + /* if the PURE bit is set */ + if (AUDC[next_event] & PURE) + { + /* then simply toggle the output */ + Outbit[next_event] = !Outbit[next_event]; + } + /* otherwise if POLY4 is selected */ + else if (AUDC[next_event] & POLY4) + { + /* then use the poly4 bit */ + Outbit[next_event] = bit4[P4]; + } + else + { + /* otherwise use the poly17 bit */ + Outbit[next_event] = bit17[P17]; + } + } + + /* At this point I haven't emulated the filters. Though I don't + expect it to be complicated, I don't believe this feature is + used much anyway. I'll work on it later. */ + if ((next_event == CHAN1) || (next_event == CHAN3)) + { + /* INSERT FILTER HERE */ + } + + /* if the current output bit is set */ + if (Outbit[next_event]) + { + /* then set to the current volume */ + Outvol[next_event] = AUDC[next_event] & VOLUME_MASK; + } + else + { + /* set the volume to zero */ + Outvol[next_event] = 0; + } + } + else /* otherwise we're processing a sample */ + { + /* adjust the sample counter - note we're using the 24.8 integer + which includes an 8 bit fraction for accuracy */ + *Samp_n_cnt += Samp_n_max; + + cur_val = 0; + + /* add the output values of all 4 channels */ + for (chan = CHAN1; chan <= CHAN4; chan++) + { + cur_val += Outvol[chan]; + } + + /* multiply the volume by 4 and add 8 to center around 128 */ + /* NOTE: this statement could be eliminated for efficiency, */ + /* though the volume would be lower. */ + cur_val = (cur_val << 2) + 8; + + /* add the current value to the output buffer */ + *buffer++ = cur_val; + + /* and indicate one less byte in the buffer */ + n--; + } + } +} + + +/*****************************************************************************/ +/* Module: Pokey_process() */ +/* Purpose: To fill the output buffer with the sound output based on the */ +/* pokey chip parameters. This routine has not been optimized. */ +/* Though it is not used by the program, I've left it for reference.*/ +/* */ +/* Author: Ron Fries */ +/* Date: September 22, 1996 */ +/* */ +/* Inputs: *buffer - pointer to the buffer where the audio output will */ +/* be placed */ +/* n - size of the playback buffer */ +/* */ +/* Outputs: the buffer will be filled with n bytes of audio - no return val */ +/* */ +/*****************************************************************************/ + +void Pokey_process (register unsigned char *buffer, register uint16 n) +{ + register uint32 *div_n_ptr; + register uint32 *samp_cnt_w_ptr; + register uint32 event_min; + register uint8 next_event; + register uint8 cur_val; + register uint8 *out_ptr; + register uint8 audc; + register uint8 toggle; + + + /* set a pointer to the whole portion of the samp_n_cnt */ + samp_cnt_w_ptr = (uint32 *)((uint8 *)(&Samp_n_cnt[0])+1); + + /* set a pointer for optimization */ + out_ptr = Outvol; + + /* The current output is pre-determined and then adjusted based on each */ + /* output change for increased performance (less over-all math). */ + /* add the output values of all 4 channels */ + cur_val = 2; /* start with a small offset */ + cur_val += *out_ptr++; + cur_val += *out_ptr++; + cur_val += *out_ptr++; + cur_val += *out_ptr++; + + /* loop until the buffer is filled */ + while (n) + { + /* Normally the routine would simply decrement the 'div by N' */ + /* counters and react when they reach zero. Since we normally */ + /* won't be processing except once every 80 or so counts, */ + /* I've optimized by finding the smallest count and then */ + /* 'accelerated' time by adjusting all pointers by that amount. */ + + /* find next smallest event (either sample or chan 1-4) */ + next_event = SAMPLE; + event_min = *samp_cnt_w_ptr; + + /* Though I could have used a loop here, this is faster */ + div_n_ptr = Div_n_cnt; + if (*div_n_ptr <= event_min) + { + event_min = *div_n_ptr; + next_event = CHAN1; + } + div_n_ptr++; + if (*div_n_ptr <= event_min) + { + event_min = *div_n_ptr; + next_event = CHAN2; + } + div_n_ptr++; + if (*div_n_ptr <= event_min) + { + event_min = *div_n_ptr; + next_event = CHAN3; + } + div_n_ptr++; + if (*div_n_ptr <= event_min) + { + event_min = *div_n_ptr; + next_event = CHAN4; + } + + /* decrement all counters by the smallest count found */ + /* again, no loop for efficiency */ + *div_n_ptr -= event_min; + div_n_ptr--; + *div_n_ptr -= event_min; + div_n_ptr--; + *div_n_ptr -= event_min; + div_n_ptr--; + *div_n_ptr -= event_min; + + *samp_cnt_w_ptr -= event_min; + + /* since the polynomials require a mod (%) function which is + division, I don't adjust the polynomials on the SAMPLE events, + only the CHAN events. I have to keep track of the change, + though. */ + Poly_adjust += event_min; + + /* if the next event is a channel change */ + if (next_event != SAMPLE) + { + /* shift the polynomial counters */ + P4 = (P4 + Poly_adjust) % POLY4_SIZE; + P5 = (P5 + Poly_adjust) % POLY5_SIZE; + P17 = (P17 + Poly_adjust) % Poly17_size; + + /* reset the polynomial adjust counter to zero */ + Poly_adjust = 0; + + /* adjust channel counter */ + Div_n_cnt[next_event] += Div_n_max[next_event]; + + /* get the current AUDC into a register (for optimization) */ + audc = AUDC[next_event]; + + /* set a pointer to the current output (for opt...) */ + out_ptr = &Outvol[next_event]; + + /* assume no changes to the output */ + toggle = FALSE; + + /* From here, a good understanding of the hardware is required */ + /* to understand what is happening. I won't be able to provide */ + /* much description to explain it here. */ + + /* if the output is pure or the output is poly5 and the poly5 bit */ + /* is set */ + if ((audc & NOTPOLY5) || bit5[P5]) + { + /* if the PURE bit is set */ + if (audc & PURE) + { + /* then simply toggle the output */ + toggle = TRUE; + } + /* otherwise if POLY4 is selected */ + else if (audc & POLY4) + { + /* then compare to the poly4 bit */ + toggle = (bit4[P4] == !(*out_ptr)); + } + else + { + /* otherwise compare to the poly17 bit */ + toggle = (bit17[P17] == !(*out_ptr)); + } + } + + /* At this point I haven't emulated the filters. Though I don't + expect it to be complicated, I don't believe this feature is + used much anyway. I'll work on it later. */ + if ((next_event == CHAN1) || (next_event == CHAN3)) + { + /* INSERT FILTER HERE */ + } + + /* if the current output bit has changed */ + if (toggle) + { + if (*out_ptr) + { + /* remove this channel from the signal */ + cur_val -= *out_ptr; + + /* and turn the output off */ + *out_ptr = 0; + } + else + { + /* turn the output on */ + *out_ptr = audc & VOLUME_MASK; + + /* and add it to the output signal */ + cur_val += *out_ptr; + } + } + } + else /* otherwise we're processing a sample */ + { + /* adjust the sample counter - note we're using the 24.8 integer + which includes an 8 bit fraction for accuracy */ + *Samp_n_cnt += Samp_n_max; + + /* add the current value to the output buffer */ + *buffer++ = cur_val << 2; + + /* and indicate one less byte in the buffer */ + n--; + } + } +} + + diff --git a/pokeytest/pokey.h b/pokeytest/pokey.h new file mode 100644 index 0000000..ee2c6e3 --- /dev/null +++ b/pokeytest/pokey.h @@ -0,0 +1,77 @@ +/*****************************************************************************/ +/* */ +/* Module: POKEY Chip Simulator Includes, V1.1 */ +/* Purpose: To emulate the sound generation hardware of the Atari POKEY chip.*/ +/* Author: Ron Fries */ +/* Date: September 22, 1996 */ +/* */ +/*****************************************************************************/ +/* */ +/* License Information and Copyright Notice */ +/* ======================================== */ +/* */ +/* PokeySound is Copyright(c) 1996 by Ron Fries */ +/* */ +/* This library is free software; you can redistribute it and/or modify it */ +/* under the terms of version 2 of the GNU Library General Public License */ +/* as published by the Free Software Foundation. */ +/* */ +/* This library 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 Library */ +/* General Public License for more details. */ +/* To obtain a copy of the GNU Library General Public License, write to the */ +/* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* */ +/* Any permitted reproduction of these routines, in whole or in part, must */ +/* bear this legend. */ +/* */ +/*****************************************************************************/ + +#ifndef _POKEYSOUND_H +#define _POKEYSOUND_H + +#include + +#define int8 int8_t +#define int16 int16_t +#define int32 int32_t +#define uint8 uint8_t +#define uint16 uint16_t +#define uint32 uint32_t + +#define audf1 (0xd200 + 0x00) +#define audc1 (0xd200 + 0x01) +#define audf2 (0xd200 + 0x02) +#define audc2 (0xd200 + 0x03) +#define audf3 (0xd200 + 0x04) +#define audc3 (0xd200 + 0x05) +#define audf4 (0xd200 + 0x06) +#define audc4 (0xd200 + 0x07) +#define audctl (0xd200 + 0x08) + +/* CONSTANT DEFINITIONS */ + +/* As an alternative to using the exact frequencies, selecting a playback + frequency that is an exact division of the main clock provides a higher + quality output due to less aliasing. For best results, a value of + 1787520 MHz is used for the main clock. With this value, both the + 64 kHz and 15 kHz clocks are evenly divisible. Selecting a playback + frequency that is also a division of the clock provides the best + results. The best options are FREQ_64 divided by either 2, 3, or 4. + The best selection is based on a trade off between performance and + sound quality. + + Of course, using a main clock frequency that is not exact will affect + the pitch of the output. With these numbers, the pitch will be low + by 0.127%. (More than likely, an actual unit will vary by this much!) */ + +#define FREQ_17_EXACT 1789790 /* exact 1.79 MHz clock freq */ +#define FREQ_17_APPROX 1787520 /* approximate 1.79 MHz clock freq */ + +void Pokey_sound_init (uint32 freq17, uint16 playback_freq); +void Update_pokey_sound (uint16 addr, uint8 val); +void Pokey_process_2 (register unsigned char *buffer, register uint16 n); +void Pokey_process (register unsigned char *buffer, register uint16 n); + +#endif diff --git a/pokeytest/pokey11.txt b/pokeytest/pokey11.txt new file mode 100644 index 0000000..498d768 --- /dev/null +++ b/pokeytest/pokey11.txt @@ -0,0 +1,277 @@ + Atari POKEY Chip Simulator V1.1 + =============================== + by Ron Fries + 23 Sep 96 + +The POKEY Chip Simulator is designed to emulate the functionality of the +Atari POKEY Chip Hardware through 'C' Sourcecode. I have seen very good +results. The simulator is able to produce sounds which are essentially +identical to the original Atari, including the exact distortions and +pitches. + +The simulator is designed to run in a 32-bit environment. Though it can +compiled and run in a 16-bit environment, it is slow. + + +Features: +--------- + +Version 1.1 of the 'POKEY' simulator supports the following functions: + +1) All polynomial sound generators: + a) 4-bit poly - actual bit pattern determined from sampled sound + b) 5-bit poly - actual bit pattern determined from sampled sound + c) 17-bit poly - simulated random bit pattern + d) 9-bit poly - derived from simulated 17-bit poly + +2) Full support of all 'Divide by N' counter clocks: + a) 1.79 MHz (high limited to playback sample rate) + b) 64 KHz (high limited to playback sample rate) + c) 15 KHz + +3) Full support of all 'Divide by N' resolutions: + a) 8-bit - single channel + b) 16-bit - double channel + +4) Full support of all distortions + a) 5-bit poly, then 17-bit poly + b) 5-bit poly only + c) 5-bit poly, then 4-bit poly + d) 17-bit poly only + e) no poly counters (pure tone) + f) 5-bit poly only + +5) Full support of volume control + +6) Full support of all pitches - distortions will vary exactly as the + original Atari based on different pitches + +7) Accurate pitch generation + +8) Support of any playback sample rate (e.g. 22050) + + +The 'POKEY' simulator does not currently support the following functions: + +1) High pass filters + + +Though I don't believe adding support for the High-Pass filters is very +complicated, I decided not to add support right now because I don't +believe this feature is used much. I'm also not sure how much impact it +would have on performance. + +You'll notice in the code there are two separate versions. The original +process function, now called Pokey_process_2(), is the non-optimized +version. I've left it in for reference. The other function, +Pokey_process(), is optimized. In my tests using the 'Maximize Speed' +option of the compiler, I've seen very good performance. On my 486DX2-66, +it typically takes about 0.15 seconds to produce about 3 seconds of audio. +These times were calculated under WIN95. + +One of the unique features of the optimized version is that the processing +time will vary based on the frequency. Since the routine only calculates +new output values when a change is sensed, the lower frequencies (which +change less frequently) will require less processing time. + + +Differences Between the Simulator and the Actual POKEY Chip: +------------------------------------------------------------ + +The biggest difference between the simulator and the original hardware is +that the simulator emulates an 'ideal' POKEY chip. All output from the +simulator is a based on a precise square wave, whereas the output from the +original chip has decay. Though the output is slightly different, I +don't believe this difference is easily discernible. + +Another slight difference is the 17-bit/9-bit poly. Since the polynomial +is large (2^17 bits), I choose to create the sample using a random number +generator rather than a table. I don't believe this difference is +significant. + +There are also a few differences which are introduced by aliasing. This is +a direct result of using an output sampling rate which is not identical to +the original sound rate. It is most evident with high frequencies. + +A final difference is the lack of support for the High-Pass Filter +functionality. I plan to add this in a future release if necessary. + + +Sample/Test Application: +------------------------ + +The test program I've distributed is a 16-bit DOS application created with +the Borland 'C' compiler. The only reason I used 16-bit was because I +already had a set of working SB drivers in 16-bit. Since the test system +is dedicated to generating sounds, the performance in 16-bit is more than +adequate. + + +POKEY11.C +========== + +The POKEY11.C file is the heart of the POKEY Sound Emulation program. +Although the routines in the file must work together, no other files are +modules are required for operation. A header file, 'POKEY11.H', has +been included for use in other modules, and provides the necessary +function prototypes. I've attempted to make the routines as portable as +possible, so the file should compile on almost any compiler with little +or no modification. + +I have made some attempts at optimizing the routines, though I am sure +more optimization can be done. They are currently only available in 'C'. +I'll be happy to convert them to assembly language if desired. Please feel +free to send me e-mail at rfries@tcmail.frco.com. + +The routines are easy to use. Detailed descriptions on the function calls +are listed below. + +The POKEY11.C module can be compiled in a 32-bit or 16-bit environment. +Since these routines are optimized for 32-bit use, the code will default +to 32-bit. To compile in 16-bits, use a command line option to define +the variable COMP16. + + +GENERAL OVERVIEW +---------------- + +On start-up of the system, a single call should be made to Pokey_sound_init. +This routine will prepare the structures for sound output. This routine +can be called again if necessary during warm-start or other reset. + +Once in the main loop, there are two other functions that will be used. +Whenever the system needs to write to either the AUDC or AUDF values, +a call should be made to the Update_pokey_sound routine. This routine will +take care of updating the internal registers. It will pre-calculate several +values to help with optimization. + +The only other routine that is called is the Pokey_process function. This +function will fill a audio buffer with a specified number of bytes. This +function should be called whenever a new audio buffer is required. + +For best results, I recommend using at least two output buffers. Using this +scheme, the sound card can be playing one buffer while the system is filling +the other. + + +DETAILED FUNCTION DESCRIPTIONS +------------------------------ + +Pokey_sound_init(uint32 freq17, uint16 playback_freq) +-------------------------------------------------------- + +This function initializes the structures used by the PokeySound routines. +This function takes two parameters: the main clock frequency and the +playback frequency. + +The main clock frequency is the frequency of the 1.79MHz source clock. +To provide exact results, freq17 should be set equal to 1789790 Hz. As an +alternative, freq17 can be set to an approximate frequency of 1787520 Hz. +Using this approximate frequency will reduce aliasing and thus produce a +clearer output signal. + +A constant has been defined for both of these values for your convenience. +The names are FREQ_17_EXACT and FREQ_17_APPROX. + +The playback frequency is the frequency of the sound playback (the frequency +used by the sound card). For best results, the playback frequency should +be an even division of the main clock frequency. Since most of the sounds +will be generated using the 64kHz clock, I also recommend making the +playback frequency an even division of the 64kHz clock. + +The 64kHz clock is exactly equal to the main clock divided by 28. For +the playback frequency, I recommend one of the following values: + +1) FREQ_17_APPROX / (28*1), which is equal to 63840. Of course, most sound + cards can't reproduce this frequency. + +2) FREQ_17_APPROX / (28*2), which is equal to 31920. All of the newer cards + will support this frequency. + +3) FREQ_17_APPROX / (28*3), which is equal to 21280. All of the SB + compatibles should support this frequency. + +4) FREQ_17_APPROX / (28*4), which is equal to 15960. This may be the + best choice, as it offers good sound reproduction with good performance. + +Of course, these options also assume you are using the approximate +frequency for the main clock as well. Any of these choices will offer the +best results when the main 64kHz clock is used, reasonable results when the +15kHz clock is selected, and marginal results when the 1.79MHz clock is +selected (the only way to produce good results in all cases is to set the +playback frequency to 1.79MHz!) + +Feel free to experiment to find other alternatives as well. + +This function has no return value (void). + + +Update_pokey_sound (uint16 addr, uint8 val) +----------------------------------------- + +This function should be called each time an AUDC, AUDF or AUDCTL value +changes. This function takes two parameters: the address to change and +the new value. The address should be one of the following values: + + Addr Description + ------ ----------- + 0xd200 AUDF1 + 0xd201 AUDC1 + 0xd202 AUDF2 + 0xd203 AUDC2 + 0xd204 AUDF3 + 0xd205 AUDC3 + 0xd206 AUDF4 + 0xd207 AUDC4 + 0xd208 AUDCTL + +Any values outside of this range will be ignored. + +The routine pre-calculates several values that are needed by the +processing function. This is done to optimize performance. + +This function has no return value (void). + + +Pokey_process (unsigned char *buffer, uint16 n) +--------------------------------------------- + +This function calculates and fills a buffer with unsigned 8-bit mono audio. +This function takes two parameters: a pointer to the buffer to fill and +the size of the buffer (limited to 65535). This function fills the +buffer based on the requested size and returns. It automatically +updates the pointers for the next call, so subsequent calls to this function +will provide a continuous stream of data. + +The size of the buffer that is needed depends on the playback frequency. +It is best to keep the buffer as small as possible to maximize response time +to changes in the sound. Of course, the minimum size is dependent on +system and emulator performance. + +Selecting the correct buffer size is a careful balance. Selecting a buffer +size that is too small will produce noticeable clicks in the output, though +selecting a size that is too large will cause a poor response time and +possible delays in the system when the new buffer is filled. + +This function has no return value (void). + + +License Information and Copyright Notice +======================================== + +PokeySound is Copyright(c) 1996 by Ron Fries + +This library is free software; you can redistribute it and/or modify it under +the terms of version 2 of the GNU Library General Public License as published +by the Free Software Foundation. + +This library 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 Library General Public License for more +details. + +To obtain a copy of the GNU Library General Public License, write to the Free +Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Any permitted reproduction of these routines, in whole or in part, must bear +this legend. diff --git a/pokeytest/pokey11_readme.txt b/pokeytest/pokey11_readme.txt new file mode 100644 index 0000000..31eac81 --- /dev/null +++ b/pokeytest/pokey11_readme.txt @@ -0,0 +1,119 @@ + ATARI 800 - POKEY SOUND EMULATION LIBRARY V1.1 + ============================================== + September 23, 1996 + +The Pokey Sound Emulation Library consists of source code and example +executables that are designed to emulate the sound output characteristics +of the Atari 800 Pokey chip. Contained in this distribution, you should +also have the following files: + + +README.TXT - This file +FILE_ID.DIZ - A quick description of the release. +SOUND.C - The SB Driver Source Code. +SOUND.H - The SB Driver Header File which contains the function + prototypes. +SOUND.TXT - A text file which explains the operation of the SB Driver + module. + +POKEY11.C - The Pokey Sound Emulation Source Code. +POKEY11.H - The Pokey Sound Emulation Header File which contains the + function prototypes. +POKEY11.TXT - A text file which explains the operation of the Pokey Sound + Emulation module. +SNDTEST.C - The Main Test routine Source Code. Provided as an example + of how the routines can be used. +SNDTEST.EXE - An pre-compiled executable based on the code provided. + + +These routines are available for your use free of charge, provided you give +me proper recognition, and also provided you don't make a profit off of my +work. See the GNU Library Public License for more information. + +If the final product you are creating will be sold for a profit, please +contact me at rfries@frmail.frco.com so we can discuss terms. + + +THE LIBRARY +----------- + +The Pokey Sound Emulation software consists of two main files. The +functions have been divided into these two files depending on your needs. +If you only need the sound generation routines, the only file you'll need +to compile and link is the POKEY11.C file. If you need full SB drivers as +well, then link both the POKEY11.C and the SOUND.C files into your +application. + +The POKEY11.C file was designed to be as portable as possible. You should +be able to use the file with little or no modification on any 'C' compiler. +It is currently only available in 'C', though I'll be happy to convert the +file to Assembly Language if necessary. For best results, this file should +be compiled in a 32-bit environment with the 'maximize speed' option. + +The SOUND.C file was designed to work with the Borland 'C' compiler. It +may also work directly with other compilers, though some modification may +be required. The SB driver routines in this file have several limitations +which are described in detail in the SOUND.TXT file. NOTE: The SB driver +routines are written in 16-bit. + +The POKEY11.C routines should be very stable, though I'm not as confident +with the SB routines. They work correctly on my system, though I offer no +guarantees. + +Depending on your needs, you will use one of the following algorithms: + + +1) POKEY11.C only - in this case, it is assumed you already have audio + driver routines + + The general design is as follows: + + a) During startup, make a single call to Pokey_sound_init() + b) Whenever an AUDC or AUDF value changes, call Update_pokey_sound() + c) When a new buffer needs to be filled, call Pokey_process() + + +2) POKEY11.C and SOUND.C - using the SB driver routines I've provided + (note: the SB driver routines are written for a 16-bit compiler) + + The general design is as follows: + + a) During startup, make a single call to OpenSB() + b) During startup, make a single call to Pokey_sound_init() + c) Whenever an AUDC or AUDF value changes, call Update_pokey_sound() + d) Periodically call FillBuffer() + e) During shutdown, make a single call to CloseSB() + + +Each function call has its own set of parameters. Check with the +POKEY11.TXT and SOUND.TXT files for more information. + +The SNDTEST.C file has also been provided as an example. I wrote it mainly +for my testing, but it should give you a little insight on how to correctly +use the routines. You can also run the SNDTEST.EXE to play with the sounds. + +Since the SNDTEST.EXE program uses my SOUND SB driver routines, it is subject +to the same limitations. It only supports IRQs 0-7 and DMAs 0-3. It also +only supports low-speed audio output (max freq is approx. 22KHz), and requires +that you have the BLASTER environment variable configured. + + +License Information and Copyright Notice +======================================== + +PokeySound is Copyright(c) 1996 by Ron Fries + +This library is free software; you can redistribute it and/or modify it under +the terms of version 2 of the GNU Library General Public License as published +by the Free Software Foundation. + +This library 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 Library General Public License for more +details. + +To obtain a copy of the GNU Library General Public License, write to the Free +Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Any permitted reproduction of these routines, in whole or in part, must bear +this legend. diff --git a/pokeytest/readme.txt b/pokeytest/readme.txt new file mode 100644 index 0000000..be49295 --- /dev/null +++ b/pokeytest/readme.txt @@ -0,0 +1,25 @@ + +This is an attempt at a sound player for Jumpman Junior, using SDL2 and +the pokey sound library by Ron Fries. + +Usage: ./playsdl2 [-p] addr + +Default is to play at NTSC speed, 60 frames/sec. -p means play at PAL +speed, 50 frames/sec. + +addr is a 4-digit hex address from the ROM (../jumpmanjr.rom). Look for +labels beginning with sfx_ in ../jumpmanjr.dasm. Don't use a leading $, +although a leading 0x is allowed (but not needed). + +Right now this only plays one sound, so you can't play all the parts to +the 2- and 4-part harmony music yet. Also, parts that repeat will repeat +forever (press ^C in that case). Eventually this will evolve into the +sound engine for the SDL2 source port of Jumpman Junior, meaning it'll +handle up to 4 voices, with priority levels, etc (same as the original). + +I tried to use the later 2.3 version of the pokey library, both the +standalone one and the modified from from the atari800 emulator, but +couldn't get either to work (no doubt due to my own ignorance). So +this uses version 1.1 of the library, which should be fine for this +game since we don't need support for things like volume-only sound or +high-pass filters. -- cgit v1.2.3