diff options
| author | Ian C <ianc@noddybox.co.uk> | 2026-06-29 09:41:42 +0900 |
|---|---|---|
| committer | Ian C <ianc@noddybox.co.uk> | 2026-06-29 09:41:42 +0900 |
| commit | c527acfc79f3bda8cfe13a7b51b0b16a32683960 (patch) | |
| tree | e5bbd119a070848301a03b52f1232a797b35c119 | |
| parent | c31b779c20638ce25f11489c445a53798a663b13 (diff) | |
First partial working attempt at sound. Producing output, but very distorted
| -rw-r--r-- | src/Makefile | 4 | ||||
| -rw-r--r-- | src/audio.c | 30 | ||||
| -rw-r--r-- | src/audio.h | 10 | ||||
| -rw-r--r-- | src/spec.c | 115 | ||||
| -rw-r--r-- | src/z80.c | 19 | ||||
| -rw-r--r-- | src/z80.h | 16 | ||||
| -rw-r--r-- | src/z80_private.h | 10 |
7 files changed, 194 insertions, 10 deletions
diff --git a/src/Makefile b/src/Makefile index 15db934..37e1d26 100644 --- a/src/Makefile +++ b/src/Makefile @@ -85,7 +85,7 @@ clean: main.o: z80.h spec.h gfx.h gui.h memmenu.h config.h kbbmp.h exit.h tape.h audio.h main.o: util.h -spec.o: spec.h z80.h snap.h gfx.h gui.h config.h exit.h tape.h util.h +spec.o: spec.h z80.h snap.h gfx.h gui.h config.h exit.h tape.h util.h audio.h snap.o: snap.h z80.h util.h config.o: exit.h config.h gfx.o: gfx.h exit.h config.h util.h font.h @@ -99,4 +99,4 @@ symtochar.o: symtochar.h font.o: font.h z80.o: z80.h z80_private.h z80_decode.o: z80.h z80_private.h -audio.o: audio.c audio.h +audio.o: audio.c audio.h util.h diff --git a/src/audio.c b/src/audio.c index b5ce1df..7eef433 100644 --- a/src/audio.c +++ b/src/audio.c @@ -27,17 +27,41 @@ #include <SDL.h> #include "audio.h" +#include "util.h" -/* ---------------------------------------- INTERFACES +/* ---------------------------------------- INTERNAL DATA */ +static SDL_AudioSpec actual; +static SDL_AudioDeviceID device; + +/* ---------------------------------------- INTERFACES +*/ int AUDIOInit(void) { - return 0; + SDL_AudioSpec spec = {0}; + + spec.freq = SAMPLE_RATE; + spec.format = AUDIO_S8; + spec.channels = 1; + spec.samples = 4096; + + device = SDL_OpenAudioDevice(NULL, 0, &spec, &actual, 0); + + if (device != 0) + { + SDL_PauseAudioDevice(device, 0); + } + + return device != 0; } -void AUDIOQueue(char *buffer, size_t len) +void AUDIOQueue(Uint8 *buffer, size_t len) { + if (device != 0) + { + SDL_QueueAudio(device, buffer, len); + } } diff --git a/src/audio.h b/src/audio.h index 83980e4..b799abd 100644 --- a/src/audio.h +++ b/src/audio.h @@ -28,6 +28,11 @@ #define ESPEC_AUDIO_H #include <stdlib.h> +#include <SDL.h> + +/* The sample rate +*/ +#define SAMPLE_RATE 48000 /* ---------------------------------------- INTERFACES */ @@ -39,10 +44,11 @@ int AUDIOInit(void); -/* Add the passed buffer to the sound queue. It is a signed 8-bit mono +/* Add the passed buffer to the sound queue. It is an unsigned 8-bit mono sample which will be converted as appropriate by the audio interface. + Well turns out SDL does that for you. */ -void AUDIOQueue(char *buffer, size_t len); +void AUDIOQueue(Uint8 *buffer, size_t len); #endif @@ -26,6 +26,7 @@ #include <stdlib.h> #include <stdio.h> #include <string.h> +#include <SDL.h> #include "spec.h" #include "snap.h" #include "gfx.h" @@ -33,6 +34,7 @@ #include "config.h" #include "exit.h" #include "tape.h" +#include "audio.h" #include "util.h" #ifndef TRUE @@ -77,7 +79,9 @@ static Z80Byte mem[0x10000]; /* Number of cycles per scan lines and scan line control */ -static Z80Val SCAN_CYCLES=224; +#define SCAN_CYCLES 224 +#define FRAME_CYCLES 69888 + static int scanline=0; static int enable_screen=TRUE; @@ -202,6 +206,22 @@ static const MatrixMap keymap[]= {SDLK_UNKNOWN, 0,0,0,0}, }; +static size_t sample_buff_sz; +static double cycles_per_sample; + +typedef struct SoundRecord +{ + Z80Val cycle; + Uint8 level; + struct SoundRecord *next; +} SoundRecord; + +static struct +{ + SoundRecord *head; + SoundRecord *tail; + Uint8 last_level; +} sound; /* ---------------------------------------- DEBUG FUNCTIONS */ @@ -251,6 +271,39 @@ static void ClearKeys(void) } } +static void AddSoundRecord(Z80Val cycle, Uint8 level) +{ + SoundRecord *new = Malloc(sizeof *new); + + new->cycle = cycle; + new->level = level; + new->next = NULL; + + if (sound.tail) + { + sound.tail->next = new; + sound.tail = new; + } + else + { + sound.head = new; + sound.tail = new; + } +} + +static void ClearSoundRecord(void) +{ + while(sound.head) + { + SoundRecord *next = sound.head->next; + free(sound.head); + sound.head = next; + } + + sound.tail = NULL; +} + + static void DrawScanlineAt(int y, int sline) { int aline; @@ -485,6 +538,55 @@ static int CheckTimers(Z80 *z80, Z80Val val) { selected_out_tape--; } + + if (IConfig(CONF_SOUND)) + { + Uint8 *sample = Malloc(sample_buff_sz); + SoundRecord *current = sound.head; + int offset = 0; + + memset(sample, 0, sample_buff_sz); + + if (current) + { + int f; + int first_offset = (int)(current->cycle / cycles_per_sample); + + for(f = 0; f < first_offset; f++) + { + sample[f] = sound.last_level; + } + + while(current) + { + int next_offset; + + if (current->next) + { + next_offset = (int)(current->cycle / cycles_per_sample); + } + else + { + next_offset = sample_buff_sz; + } + + for(f = offset; f < next_offset; f++) + { + sample[f] = current->level; + } + + offset = next_offset; + sound.last_level = current->level; + current = current->next; + } + } + + AUDIOQueue(sample, sample_buff_sz); + + free(sample); + ClearSoundRecord(); + } + } /* Draw scanline @@ -494,7 +596,7 @@ static int CheckTimers(Z80 *z80, Z80Val val) if (y>=0 && y<GFX_HEIGHT && enable_screen) DrawScanline(y); - /* TODO: Process sound emulation */ + Z80SetTimer(z80, eZ80_Timer_1, 0); } return TRUE; @@ -532,6 +634,14 @@ void SPECInit(Z80 *z80) Z80LodgeCallback(z80,eZ80_EDHook,EDCallback); Z80LodgeCallback(z80,eZ80_Instruction,CheckTimers); + /* Calculate the audio buffer sizes + */ + if (IConfig(CONF_SOUND)) + { + sample_buff_sz = SAMPLE_RATE / IConfig(CONF_FRAMES_PER_SEC); + cycles_per_sample = (double)FRAME_CYCLES / (double)SAMPLE_RATE; + } + SPECReset(z80); } @@ -622,6 +732,7 @@ void SPECWritePort(Z80 *z80, Z80Word port, Z80Byte val) { case 0xfe: /* ULA */ border=val&0x07; + AddSoundRecord(Z80GetTimer(z80, eZ80_Timer_1), val & 0x10 ? 64 : 0); break; case 0xfb: /* TODO: ZX Printer */ @@ -149,9 +149,15 @@ Z80 *Z80Init(Z80ReadMemory read_memory, void Z80Reset(Z80 *cpu) { + int f; + cpu->cycle=0; cpu->PC=0; + cpu->timer[eZ80_Timer_1] = 0; + cpu->timer[eZ80_Timer_2] = 0; + cpu->timer[eZ80_Timer_3] = 0; + cpu->AF.w=0xffff; cpu->BC.w=0xffff; cpu->DE.w=0xffff; @@ -362,4 +368,17 @@ const char *Z80Disassemble(Z80 *cpu, Z80Word *pc) #endif } + +Z80Val Z80GetTimer(Z80 *cpu, Z80Timer timer) +{ + return cpu->timer[timer]; +} + + +void Z80SetTimer(Z80 *cpu, Z80Timer timer, Z80Val value) +{ + cpu->timer[timer] = value; +} + + /* END OF FILE */ @@ -151,6 +151,16 @@ typedef struct } Z80Label; +/* The Z80 provides a number of cycle timers +*/ +typedef enum +{ + eZ80_Timer_1, + eZ80_Timer_2, + eZ80_Timer_3 +} Z80Timer; + + /* ---------------------------------------- INTERFACES */ @@ -230,6 +240,12 @@ void Z80GetState(Z80 *cpu, Z80State *state); void Z80SetState(Z80 *cpu, const Z80State *state); +/* Timers that count in cycle counts +*/ +Z80Val Z80GetTimer(Z80 *cpu, Z80Timer timer); +void Z80SetTimer(Z80 *cpu, Z80Timer timer, Z80Val cycles); + + /* Set address to label mappings for the disassembler */ void Z80SetLabels(Z80Label labels[]); diff --git a/src/z80_private.h b/src/z80_private.h index 628e23d..b015d6f 100644 --- a/src/z80_private.h +++ b/src/z80_private.h @@ -96,6 +96,8 @@ struct Z80 Z80Callback callback[eZ80_NO_CALLBACK][MAX_PER_CALLBACK]; int last_cb; + + Z80Val timer[3]; }; @@ -159,7 +161,13 @@ struct Z80 #define IS_IX_IY (cpu->shift==0xdd || cpu->shift==0xfd) #define OFFSET(off) off=(IS_IX_IY ? (Z80Relative)FETCH_BYTE:0) -#define TSTATE(n) cpu->cycle+=n +#define TSTATE(n) do \ + { \ + cpu->cycle+=n; \ + cpu->timer[eZ80_Timer_1]+=n; \ + cpu->timer[eZ80_Timer_2]+=n; \ + cpu->timer[eZ80_Timer_3]+=n; \ + } while(0) #define ADD_R(v) cpu->R=((cpu->R&0x80)|((cpu->R+(v))&0x7f)) #define INC_R ADD_R(1) |
