From a9f5b9f18a6e21be0d6fa23ccd4adcc5dfbc22d4 Mon Sep 17 00:00:00 2001 From: Brad Grantham Date: Wed, 23 Nov 2016 23:29:23 -0800 Subject: [PATCH] Audio. Probably won't work properly in FAST mode. Try COLORBOUNCESOUND.A. --- COLORBOUNCESOUND.A | 38 +++++++++++++++++++++ Makefile | 2 +- README.md | 3 +- apple2e.cpp | 84 +++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 COLORBOUNCESOUND.A diff --git a/COLORBOUNCESOUND.A b/COLORBOUNCESOUND.A new file mode 100644 index 0000000..06a98a7 --- /dev/null +++ b/COLORBOUNCESOUND.A @@ -0,0 +1,38 @@ + 60 Y = 5: REM SET STARTING POSITION OF UP-AND-DOWN VARIABLE + 240 REM SET S TO ADDRESS OF SPEAKER + 260 S = - 16336 + 280 REM SET TEXT MODE + 300 TEXT : HOME + 310 PRINT "TO SELECT A COLOR FOR" + 320 PRINT "THE BOUNCING BALL, FIRST TYPE" + 330 PRINT "IN ANY NUMBER FROM 1 TO 15." + 340 PRINT "THEN PRESS THE KEY LABELLED RETURN.": PRINT + 350 INPUT "WHAT COLOR WOULD YOU LIKE THE BALL TO BE (1-15)?";HUE$ + 352 REM IS ENTRY A NUMBER? + 354 HUE = VAL (HUE$): REM VALUE OF HUE$ BECOMES NUMERIC VARIABLE + 356 IF HUE > 999999 OR HUE < .01 THEN GOTO 300: REM INPUT MESSAGE WILL REAPPEAR + 358 HUE$ = STR$ (HUE): REM HUE CHANGED BACK INTO STRING HUE$ + 370 REM IS HUE OF BALL IN RANGE? + 380 IF HUE > 0 AND HUE < 16 THEN 400 + 390 HOME : PRINT "THAT WASN'T BETWEEN 1 AND 15!": PRINT + 395 GOTO 310 + 400 GR : REM SET COLOR GRAPHICS AREA + 420 HOME : REM CLEAR TEXT AREA + 440 X = 0: REM SET STARTING POSITION OF BACK-AND-FORTH VARIABLE + 460 Y = 5: REM SET STARTING POSITION OF UP-AND-DOWN VARIABLE + 480 XV = 2: REM SET X VELOCITY + 500 YV = 1: REM SET Y VELOCITY + 520 REM CALCULATE NEW POSITION + 540 NX = X + XV:NY = Y + YV + 560 REM IF BALL EXCEEDS SCREEN EDGE, THEN BOUNCE + 580 IF NX > 39 THEN NX = 39:XV = - XV: FOR B = 1 TO 5:BOUNCE = PEEK (S) + PEEK (S) + PEEK (S): NEXT B + 600 IF NX < 0 THEN NX = 0:XV = - XV: FOR B = 1 TO 5:BOUNCE = PEEK (S) + PEEK (S) + PEEK (S): NEXT B + 620 IF NY > 39 THEN NY = 39:YV = - YV: FOR B = 1 TO 5:BOUNCE = PEEK ( S) + PEEK (S) + PEEK (S): NEXT B + 640 IF NY < 0 THEN NY = 0:YV = - YV: FOR B = 1 TO 5:BOUNCE = PEEK (S) + PEEK (S) + PEEK (S): NEXT B + 660 REM PLOT NEW POSITION + 680 COLOR= HUE: PLOT NX,NY + 700 REM ERASE OLD POSITION + 720 COLOR= 0: PLOT X,Y + 740 REM SAVE CURRENT POSITION + 760 X = NX:Y = NY + 780 GOTO 540 diff --git a/Makefile b/Makefile index 1773ed3..ed33624 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ INCFLAGS += -I/opt/local/include CXXFLAGS += $(INCFLAGS) -g -Wall --std=c++11 -O LDFLAGS += -L/opt/local/lib -LDLIBS += -lglfw -framework OpenGL -framework Cocoa -framework IOkit +LDLIBS += -lglfw -lao -framework OpenGL -framework Cocoa -framework IOkit OBJECTS = apple2e.o keyboard.o dis6502.o fake6502.o interface.o diff --git a/README.md b/README.md index 028d66f..409b059 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,12 @@ Thanks to Mike Chambers (miker00lz@gmail.com) for his 6502 CPU emulator, which I Requirements for building: * GLFW +* libao * OpenGL 3.2-compatible system * C++11 * Currently the project only builds on MacOSX because of the linker line in `Makefile`, but the C++ code itself should be cross-platform. -On MacOSX with MacPorts, the GLFW dependency can be satisfied with `sudo port install glfw`. According to https://support.apple.com/en-us/HT202823, almost all modern Macs should have OpenGL 3.2 or later. On my machine, I've been compiling with a g++ that outputs `Apple LLVM version 8.0.0 (clang-800.0.42.1)` for `g++ -v`. +On MacOSX with MacPorts, the GLFW and libao dependency can be satisfied with `glfw` and `libao` ports. According to https://support.apple.com/en-us/HT202823, almost all modern Macs should have OpenGL 3.2 or later. On my machine, I've been compiling with a g++ that outputs `Apple LLVM version 8.0.0 (clang-800.0.42.1)` for `g++ -v`. Usage: diff --git a/apple2e.cpp b/apple2e.cpp index e4480f7..2186da6 100644 --- a/apple2e.cpp +++ b/apple2e.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "fake6502.h" @@ -36,14 +37,17 @@ volatile bool exit_on_memory_fallthrough = true; volatile bool run_fast = false; volatile bool pause_cpu = false; +typedef unsigned long long clk_t; struct system_clock { - unsigned long long value = 0; - operator unsigned long long() const { return value; } - unsigned long long operator+=(unsigned long long i) { return value += i; } - unsigned long long operator++(int) { unsigned long long v = value; value ++; return v; } + clk_t value = 0; + operator clk_t() const { return value; } + clk_t operator+=(clk_t i) { return value += i; } + clk_t operator++(int) { clk_t v = value; value ++; return v; } } clk; +const int machine_clock_rate = 1023000; + struct SoftSwitch { string name; @@ -233,16 +237,45 @@ struct MAINboard : board_base deque keyboard_buffer; + static const int sample_rate = 44100; + static const size_t audio_buffer_size = sample_rate / 10; + char audio_buffer[audio_buffer_size]; + long long audio_buffer_start_sample = 0; + long long audio_buffer_next_sample = -1; + bool speaker_energized = false; + + void fill_flush_audio() + { + long long current_sample = clk * sample_rate / machine_clock_rate; + for(long long i = audio_buffer_next_sample; i < current_sample; i++) { + audio_buffer[i % audio_buffer_size] = speaker_energized ? 128 - 32 : 128 + 32; + if(i - audio_buffer_start_sample == audio_buffer_size - 1) { + audio_flush(audio_buffer, audio_buffer_size); + audio_buffer_start_sample = i + 1; + } + } + audio_buffer_next_sample = current_sample; + } + + // flush anything needing flushing + void sync() + { + fill_flush_audio(); + } + void enqueue_key(unsigned char k) { keyboard_buffer.push_back(k); } typedef std::function display_write_func; display_write_func display_write; - MAINboard(system_clock& clk_, unsigned char rom_image[32768], display_write_func display_write_) : + typedef std::function audio_flush_func; + audio_flush_func audio_flush; + MAINboard(system_clock& clk_, unsigned char rom_image[32768], display_write_func display_write_, audio_flush_func audio_flush_) : clk(clk_), internal_C800_ROM_selected(true), - display_write(display_write_) + display_write(display_write_), + audio_flush(audio_flush_) { std::copy(rom_image + rom_D000.base - 0x8000, rom_image + rom_D000.base - 0x8000 + rom_D000.size, rom_D000.memory.begin()); std::copy(rom_image + rom_E000.base - 0x8000, rom_image + rom_E000.base - 0x8000 + rom_E000.size, rom_E000.memory.begin()); @@ -349,9 +382,10 @@ struct MAINboard : board_base } if(addr == 0xC030) { if(debug & DEBUG_RW) printf("read SPKR, force 0x00\n"); - // printf("%llu\n", clk.value); <-- clock is more or less correct at this moment - // click + + fill_flush_audio(); data = 0x00; + speaker_energized = !speaker_energized; return true; } if(addr == 0xC010) { @@ -1694,6 +1728,31 @@ enum APPLE2Einterface::EventType process_events(MAINboard *board, bus_controller return APPLE2Einterface::NONE; } +ao_device *open_ao() +{ + ao_device *device; + ao_sample_format format; + int default_driver; + + ao_initialize(); + + default_driver = ao_default_driver_id(); + + memset(&format, 0, sizeof(format)); + format.bits = 8; + format.channels = 1; + format.rate = 44100; + format.byte_format = AO_FMT_LITTLE; + + /* -- Open driver -- */ + device = ao_open_live(default_driver, &format, NULL /* no options */); + if (device == NULL) { + fprintf(stderr, "Error opening libao audio device.\n"); + return nullptr; + } + return device; +} + int main(int argc, char **argv) { char *progname = argv[0]; @@ -1749,10 +1808,15 @@ int main(int argc, char **argv) system_clock clk; + ao_device *aodev = open_ao(); + if(aodev == NULL) + exit(EXIT_FAILURE); + MAINboard* mainboard; MAINboard::display_write_func display = [](int addr, unsigned char data)->bool{return APPLE2Einterface::write(addr, data);}; - bus.boards.push_back(mainboard = new MAINboard(clk, b, display)); + MAINboard::audio_flush_func audio = [aodev](char *buf, size_t sz){ao_play(aodev, buf, sz);}; + bus.boards.push_back(mainboard = new MAINboard(clk, b, display, audio)); bus.reset(); CPU6502 cpu(clk); @@ -1812,6 +1876,7 @@ int main(int argc, char **argv) else cpu.cycle(bus); } + mainboard->sync(); APPLE2Einterface::DisplayMode mode = mainboard->TEXT ? APPLE2Einterface::TEXT : (mainboard->HIRES ? APPLE2Einterface::HIRES : APPLE2Einterface::LORES); int page = mainboard->PAGE2 ? 1 : 0; APPLE2Einterface::set_switches(mode, mainboard->MIXED, page); @@ -1871,6 +1936,7 @@ int main(int argc, char **argv) step6502(); else cpu.cycle(bus); + mainboard->sync(); APPLE2Einterface::DisplayMode mode = mainboard->TEXT ? APPLE2Einterface::TEXT : (mainboard->HIRES ? APPLE2Einterface::HIRES : APPLE2Einterface::LORES); int page = mainboard->PAGE2 ? 1 : 0;