In /emu_65c02 implement SDL2 audio and Apple speaker emulation
authorNick Downing <nick@ndcode.org>
Sun, 26 Jun 2022 06:53:59 +0000 (16:53 +1000)
committerNick Downing <nick@ndcode.org>
Tue, 28 Jun 2022 03:21:38 +0000 (13:21 +1000)
emu_65c02/Makefile
emu_65c02/emu_65c02.c
emu_65c02/vrEmu6502

index 544b369..e2d6edb 100644 (file)
@@ -26,7 +26,7 @@ cg_default:
        ${MAKE} -C $@
 
 emu_65c02: emu_65c02.o stty_sane.o vrEmu6502/src/vrEmu6502.o
-       ${CC} ${LDFLAGS} -o $@ $^ -lSDL2
+       ${CC} ${LDFLAGS} -o $@ $^ -lSDL2 -lm
 
 emu_65c02.o: cg_default
 
index 8c5839c..0f8a1ff 100644 (file)
@@ -33,7 +33,8 @@
 #define WINDOW_WIDTH (PADDED_WIDTH * WINDOW_X_SCALE)
 #define WINDOW_HEIGHT (PADDED_HEIGHT * WINDOW_Y_SCALE)
 
-#define INSTRS_PER_UPDATE 1500
+#define CYCLES_PER_SAMPLE 34 // 44100 Hz @ 1.5 MHz
+#define SAMPLES_PER_UPDATE 500
 
 #define IO_PAGE 0xc000
 #define HW_KBD 0xc000 // R last key pressed + 128
@@ -62,6 +63,7 @@
 #define HW_RDTEXT 0xc01a // R bit 7: using text mode?
 #define HW_RDPAGE2 0xc01c // R bit 7: using page 2?
 #define HW_RD80VID 0xc01f // R bit 7: using 80 columns?
+#define HW_TAPEOUT 0xc020 // RW toggle cassette output
 #define HW_SPKR 0xc030 // RW toggle speaker
 #define HW_TXTCLR 0xc050 // RW
 #define HW_TXTSET 0xc051 // RW
@@ -212,6 +214,16 @@ int usleep_count, exit_flag;
 uint8_t c00x_soft_switches = 0;
 #endif
 
+#define C0X0_SOFT_SWITCH_TAPEOUT 4
+#define C0X0_SOFT_SWITCH_SPKR 8
+uint8_t c0x0_soft_switches = 0;
+
+// every n cycles, sample the tape and speaker outputs to here
+#define C0X0_SOFT_SWITCHES_BUF_SIZE 0x400 // next power of 2 >= 441 * 2
+int c0x0_soft_switches_buf_head;
+int c0x0_soft_switches_buf_count;
+uint8_t c0x0_soft_switches_buf[C0X0_SOFT_SWITCHES_BUF_SIZE];
+
 #define C05X_SOFT_SWITCH_TEXT 1
 #define C05X_SOFT_SWITCH_MIXED 2
 #define C05X_SOFT_SWITCH_PAGE2 4
@@ -793,6 +805,10 @@ uint8_t mem_read(uint16_t addr0, bool isDbg) {
   case HW_RD80VID:
     return ((c00x_soft_switches & C00X_SOFT_SWITCH_80VID) != 0) << 7;
 #endif
+  case HW_TAPEOUT: // 0xc020
+  case HW_SPKR: // 0xc030
+    c0x0_soft_switches ^= 1 << ((addr >> 4) & 7);
+    break;
   case HW_TXTCLR: // 0xc050
   case HW_MIXCLR: // 0xc052
   case HW_PAGE1: // 0xc054
@@ -1015,6 +1031,10 @@ void mem_write(uint16_t addr0, uint8_t val) {
     c00x_soft_switches |= 1 << ((addr >> 1) & 7);
     break;
 #endif
+  case HW_TAPEOUT: // 0xc020
+  case HW_SPKR: // 0xc030
+    c0x0_soft_switches ^= 1 << ((addr >> 4) & 7);
+    break;
   case HW_TXTCLR: // 0xc050
   case HW_MIXCLR: // 0xc052
   case HW_PAGE1: // 0xc054
@@ -1388,7 +1408,7 @@ int main(int argc, char **argv) {
     }
   }
 
-  if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
     fprintf(stderr, "SDL_Init(): %s\n\n", SDL_GetError());
     exit(EXIT_FAILURE);
   }
@@ -1429,6 +1449,36 @@ int main(int argc, char **argv) {
     exit(EXIT_FAILURE);
   }
 
+  SDL_AudioSpec audio_spec;
+  memset(&audio_spec, 0, sizeof(SDL_AudioSpec));
+  audio_spec.freq = 44100;
+  audio_spec.format = AUDIO_S16SYS;
+  audio_spec.channels = 1;
+  audio_spec.silence = 0;
+  audio_spec.samples = 0x200; // next power of 2 >= 441
+ //SDL_AudioSpec audio_spec_obtained;
+  SDL_AudioDeviceID audio_device_id = SDL_OpenAudioDevice(
+    NULL,
+    0,
+    &audio_spec,
+    NULL, //&audio_spec_obtained,
+    0
+  );
+  if (audio_device_id == 0) {
+    fprintf(stderr, "SDL_OpenAudioDevice(): %s\n", SDL_GetError());
+    exit(EXIT_FAILURE);
+  }
+ //fprintf(
+ // stderr,
+ // "freq %d format %d channels %d silence %d samples %d\r\n",
+ // audio_spec_obtained.freq,
+ // audio_spec_obtained.format,
+ // audio_spec_obtained.channels,
+ // audio_spec_obtained.silence,
+ // audio_spec_obtained.samples
+ //);
+  SDL_PauseAudioDevice(audio_device_id, 0);
+
   cpu = vrEmu6502New(CPU_65C02, mem_read, mem_write);
   if (cpu == NULL) {
     perror("malloc()");
@@ -1451,6 +1501,8 @@ int main(int argc, char **argv) {
 
   // main loop
   long nb_instructions = 0, nb_cycles = 0;
+  int update_count = 0;
+  float spkr_rc = 0.f;
   while (true) {
     SDL_Event event;
     while (SDL_PollEvent(&event))
@@ -1535,307 +1587,367 @@ int main(int argc, char **argv) {
         break;
       }
 
-    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xff);
-    SDL_RenderClear(renderer);
+    // video update
+    if (++update_count >= SAMPLES_PER_UPDATE) {
+      update_count = 0;
 
-    // draw
-    struct timespec timeval;
-    clock_gettime(CLOCK_MONOTONIC, &timeval);
-    bool flash_state = timeval.tv_nsec >= 500000000;
+      SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xff);
+      SDL_RenderClear(renderer);
 
-    // video ROM does inverse/flash by manipulating base address
-    int flash_base[4] = {
-      0,
+      struct timespec timeval;
+      clock_gettime(CLOCK_MONOTONIC, &timeval);
+      bool flash_state = timeval.tv_nsec >= 500000000;
+  
+      // video ROM does inverse/flash by manipulating base address
+      int flash_base[4] = {
+        0,
 #if APPLE_IIE
-      // in alternate character mode, use video ROM directly (mousetext)
-      (c00x_soft_switches & C00X_SOFT_SWITCH_ALTCHAR) ?
-        0x200 :
+        // in alternate character mode, use video ROM directly (mousetext)
+        (c00x_soft_switches & C00X_SOFT_SWITCH_ALTCHAR) ?
+          0x200 :
 #endif
-        flash_state ? 0 : 0x400,
-      0x400,
-      0x600
-    };
-
-    // CG ROM does inverse/flash by XORing onto the value from ROM
-    int flash_xor[4] = {0x7f, flash_state ? 0x7f : 0, 0, 0};
-
-    memset(frame, 0, WINDOW_HEIGHT * WINDOW_WIDTH * sizeof(uint32_t));
-    for (int i = 0; i < APPLE_HEIGHT; ++i) {
-      int base = (i >> 6) * 40 | ((i & 0x38) << 4);
-      int row = i & 7;
-      uint32_t buf[PADDED_WORDS];
-      memset(buf, 0, PADDED_WORDS * sizeof(uint32_t));
-
-      if (c05x_soft_switches & C05X_SOFT_SWITCH_HIRES) {
-        // hires
+          flash_state ? 0 : 0x400,
+        0x400,
+        0x600
+      };
+  
+      // CG ROM does inverse/flash by XORing onto the value from ROM
+      int flash_xor[4] = {0x7f, flash_state ? 0x7f : 0, 0, 0};
+  
+      memset(frame, 0, WINDOW_HEIGHT * WINDOW_WIDTH * sizeof(uint32_t));
+      for (int i = 0; i < APPLE_HEIGHT; ++i) {
+        int base = (i >> 6) * 40 | ((i & 0x38) << 4);
+        int row = i & 7;
+        uint32_t buf[PADDED_WORDS];
+        memset(buf, 0, PADDED_WORDS * sizeof(uint32_t));
+  
+        if (c05x_soft_switches & C05X_SOFT_SWITCH_HIRES) {
+          // hires
 #if APPLE_IIE
-        int line = (
-          (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) &&
-            (c00x_soft_switches & C00X_SOFT_SWITCH_80COL) == 0 ?
-            APPLE_HIRES2 :
-            APPLE_HIRES1
-        ) | base | (row << 10);
+          int line = (
+            (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) &&
+              (c00x_soft_switches & C00X_SOFT_SWITCH_80COL) == 0 ?
+              APPLE_HIRES2 :
+              APPLE_HIRES1
+          ) | base | (row << 10);
 #else
-        int line = (
-          (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) ?
-            APPLE_HIRES2 :
-            APPLE_HIRES1
-        ) | base | (row << 10);
+          int line = (
+            (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) ?
+              APPLE_HIRES2 :
+              APPLE_HIRES1
+          ) | base | (row << 10);
 #endif
 #if APPLE_IIE
-        if (c00x_soft_switches & C00X_SOFT_SWITCH_80VID) {
-          // dhgr -- no support for SETDHIRES soft switch yet
-          int aux_line = line | 0x10000;
-          int l = 1; // 1 pixel delay, similar to hgr with hi-bit set
+          if (c00x_soft_switches & C00X_SOFT_SWITCH_80VID) {
+            // dhgr -- no support for SETDHIRES soft switch yet
+            int aux_line = line | 0x10000;
+            int l = 1; // 1 pixel delay, similar to hgr with hi-bit set
+            for (int j = 0; j < 40; ++j) {
+              int data = mem[aux_line + j];
+              for (int k = 0; k < 7; ++k) {
+                if (data & 1)
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                ++l;
+                data >>= 1;
+              }
+              /*int*/ data = mem[line + j];
+              for (int k = 0; k < 7; ++k) {
+                if (data & 1)
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                ++l;
+                data >>= 1;
+              }
+            }
+          }
+          else
+#endif
           for (int j = 0; j < 40; ++j) {
-            int data = mem[aux_line + j];
+            int data = mem[line + j];
+            int hibit = data >> 7;
             for (int k = 0; k < 7; ++k) {
-              if (data & 1)
+              if (data & 1) {
+                int l = ((j * 7 + k) << 1) + hibit;
                 buf[l >> 5] |= 1 << (l & 0x1f);
-              ++l;
-              data >>= 1;
-            }
-            /*int*/ data = mem[line + j];
-            for (int k = 0; k < 7; ++k) {
-              if (data & 1)
+                ++l;
                 buf[l >> 5] |= 1 << (l & 0x1f);
-              ++l;
+              }
               data >>= 1;
             }
           }
         }
-        else
-#endif
-        for (int j = 0; j < 40; ++j) {
-          int data = mem[line + j];
-          int hibit = data >> 7;
-          for (int k = 0; k < 7; ++k) {
-            if (data & 1) {
-              int l = ((j * 7 + k) << 1) + hibit;
-              buf[l >> 5] |= 1 << (l & 0x1f);
-              ++l;
-              buf[l >> 5] |= 1 << (l & 0x1f);
-            }
-            data >>= 1;
-          }
-        }
-      }
-      else {
-        // text or gr (only text for now)
+        else {
+          // text or gr (only text for now)
 #if APPLE_IIE
-        int line = (
-          (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) &&
-            (c00x_soft_switches & C00X_SOFT_SWITCH_80COL) == 0 ?
-            APPLE_TEXT2 :
-            APPLE_TEXT1
-        ) | base;
+          int line = (
+            (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) &&
+              (c00x_soft_switches & C00X_SOFT_SWITCH_80COL) == 0 ?
+              APPLE_TEXT2 :
+              APPLE_TEXT1
+          ) | base;
 #else
-        int line = (
-          (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) ?
-            APPLE_TEXT2 :
-            APPLE_TEXT1
-        ) | base;
+          int line = (
+            (c05x_soft_switches & C05X_SOFT_SWITCH_PAGE2) ?
+              APPLE_TEXT2 :
+              APPLE_TEXT1
+          ) | base;
 #endif
-        for (int j = 0; j < 40; ++j) {
-          int glyph = mem[line + j];
-          if (video_rom) { // inverted, LSB first
-            int data = video_rom[
-              row | ((glyph & 0x3f) << 3) | flash_base[glyph >> 6]
-            ];
-            for (int k = 0; k < 7; ++k) {
-              if ((data & 1) == 0) {
-                int l = (j * 7 + k) << 1;
-                buf[l >> 5] |= 1 << (l & 0x1f);
-                ++l;
-                buf[l >> 5] |= 1 << (l & 0x1f);
+          for (int j = 0; j < 40; ++j) {
+            int glyph = mem[line + j];
+            if (video_rom) { // inverted, LSB first
+              int data = video_rom[
+                row | ((glyph & 0x3f) << 3) | flash_base[glyph >> 6]
+              ];
+              for (int k = 0; k < 7; ++k) {
+                if ((data & 1) == 0) {
+                  int l = (j * 7 + k) << 1;
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                  ++l;
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                }
+                data >>= 1;
               }
-              data >>= 1;
             }
-          }
-          else { // MSB first
-            int data = cg_rom[row | (glyph << 3)] ^ flash_xor[glyph >> 6];
-            for (int k = 0; k < 7; ++k) {
-              if (data & 0x40) {
-                int l = (j * 7 + k) << 1;
-                buf[l >> 5] |= 1 << (l & 0x1f);
-                ++l;
-                buf[l >> 5] |= 1 << (l & 0x1f);
+            else { // MSB first
+              int data = cg_rom[row | (glyph << 3)] ^ flash_xor[glyph >> 6];
+              for (int k = 0; k < 7; ++k) {
+                if (data & 0x40) {
+                  int l = (j * 7 + k) << 1;
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                  ++l;
+                  buf[l >> 5] |= 1 << (l & 0x1f);
+                }
+                data <<= 1;
               }
-              data <<= 1;
             }
           }
         }
-      }
-
-      if (c05x_soft_switches & C05X_SOFT_SWITCH_TEXT) {
-        // text (no colour burst)
-        int x = 3 * WINDOW_X_SCALE / 2, y = i * WINDOW_Y_SCALE;
-        for (int j = 0; j < APPLE_WIDTH; ++j) {
-          uint32_t v = mono_palette[(buf[j >> 5] >> (j & 0x1f)) & 1];
-          for (int l = 0; l < WINDOW_X_SCALE; ++l) {
-            for (int m = 0; m < WINDOW_Y_SCALE; ++m)
-              frame[y + m][x] = v;
-            ++x;
+  
+        if (c05x_soft_switches & C05X_SOFT_SWITCH_TEXT) {
+          // text (no colour burst)
+          int x = 3 * WINDOW_X_SCALE / 2, y = i * WINDOW_Y_SCALE;
+          for (int j = 0; j < APPLE_WIDTH; ++j) {
+            uint32_t v = mono_palette[(buf[j >> 5] >> (j & 0x1f)) & 1];
+            for (int l = 0; l < WINDOW_X_SCALE; ++l) {
+              for (int m = 0; m < WINDOW_Y_SCALE; ++m)
+                frame[y + m][x] = v;
+              ++x;
+            }
           }
         }
-      }
-      else {
-        // hires or gr (colour burst)
-        int k = 0, x = 0, y = i * WINDOW_Y_SCALE;
-        for (int j = 0; j < PADDED_WIDTH; ++j) {
-          int mask = 1 << (j & 3);
-          k = (k & ~mask) | ((buf[j >> 5] >> (j & 0x1c)) & mask);
-          uint32_t v = palette[k];
-          for (int l = 0; l < WINDOW_X_SCALE; ++l) {
-            for (int m = 0; m < WINDOW_Y_SCALE; ++m)
-              frame[y + m][x] = v;
-            ++x;
+        else {
+          // hires or gr (colour burst)
+          int k = 0, x = 0, y = i * WINDOW_Y_SCALE;
+          for (int j = 0; j < PADDED_WIDTH; ++j) {
+            int mask = 1 << (j & 3);
+            k = (k & ~mask) | ((buf[j >> 5] >> (j & 0x1c)) & mask);
+            uint32_t v = palette[k];
+            for (int l = 0; l < WINDOW_X_SCALE; ++l) {
+              for (int m = 0; m < WINDOW_Y_SCALE; ++m)
+                frame[y + m][x] = v;
+              ++x;
+            }
           }
         }
       }
+      SDL_UpdateTexture(texture, NULL, frame, WINDOW_WIDTH * sizeof(uint32_t));
+      SDL_RenderCopy(renderer, texture, NULL, NULL);
+      SDL_RenderPresent(renderer);
+    }
+    // audio update
+    if (c0x0_soft_switches_buf_count >= 0x100) {
+      int queue_level = SDL_GetQueuedAudioSize(audio_device_id);
+ // queue level should not fall to 0, if it does increase SAMPLES_PER_UPDATE
+ //fprintf(stderr, "%d %d\r\n", c0x0_soft_switches_buf_count, queue_level);
+      if (queue_level < 0x400) {
+        int16_t audio_buf[0x100];
+        for (int i = 0; i < 0x100; ++i) {
+          float spkr = (
+            c0x0_soft_switches_buf[c0x0_soft_switches_buf_head] &
+              C0X0_SOFT_SWITCH_SPKR
+          ) != 0;
+          c0x0_soft_switches_buf_head =
+            (c0x0_soft_switches_buf_head + 1) &
+              (C0X0_SOFT_SWITCHES_BUF_SIZE - 1);
+          --c0x0_soft_switches_buf_count;
+  
+          spkr -= spkr_rc;
+          spkr_rc += spkr * (1.f / 220.5f); // 200 Hz high pass
+          audio_buf[i] = (int16_t)roundf(spkr * 0x3fff);
+        }
+  
+        if (
+          SDL_QueueAudio(
+            audio_device_id,
+            audio_buf,
+            0x100 * sizeof(int16_t)
+          )
+        ) {
+          fprintf(stderr, "SDL_QueueAudio(): %s\n", SDL_GetError());
+          exit(EXIT_FAILURE);
+        }
+      }
     }
-    SDL_UpdateTexture(texture, NULL, frame, WINDOW_WIDTH * sizeof(uint32_t));
-    SDL_RenderCopy(renderer, texture, NULL, NULL);
-    SDL_RenderPresent(renderer);
 
+    // cpu update 
+    if (c0x0_soft_switches_buf_count >= C0X0_SOFT_SWITCHES_BUF_SIZE) {
 #if 1 // for star blazer game which doesn't do usleep
-    SDL_Delay(1);
+ //fprintf(stderr, "sleep\r\n");
+      SDL_Delay(1);
 #else
-    if (usleep_count) {
+      if (usleep_count) {
 #if 1 // hack to avoid lagginess in ribbit game
-      struct pollfd fd = {fd_in, POLLIN | POLLPRI, 0};
-      if (poll(&fd, 1, (usleep_count + 999) / 1000) == -1) {
-        perror("poll()");
-        exit(EXIT_FAILURE);
-      }
+        struct pollfd fd = {fd_in, POLLIN | POLLPRI, 0};
+        if (poll(&fd, 1, (usleep_count + 999) / 1000) == -1) {
+          perror("poll()");
+          exit(EXIT_FAILURE);
+        }
 #else
-      usleep(usleep_count);
+        usleep(usleep_count);
 #endif
-    }
+      }
 #endif
-
-    int i, j;
+    }
+    else {
+      int i, j;
 #if TRACE
-    struct pc_pair pc_pair = {vrEmu6502GetPC(cpu), 0};
-    for (int k = 0; k < INSTRS_PER_UPDATE; ++k) {
-      i = vrEmu6502Run(cpu, 1, &j);
-      nb_instructions += i;
-      nb_cycles += j;
-      if (i == 0)
-        goto jam; //break;
-
-      pc_pair.pc1 = vrEmu6502GetPC(cpu);
-      bool show_trace = false;
-
-      int hash = (19 * pc_pair.pc0 + 37 * pc_pair.pc1) % N_PC_PAIRS;
-      for (int i = 0; i < N_PC_PAIRS; ++i) {
-        if (
-          pc_pairs[hash].pc0 == pc_pair.pc0 &&
-            pc_pairs[hash].pc1 == pc_pair.pc1
-        )
-          break;
-        if (
-          pc_pairs[hash].pc0 == 0xffff &&
-            pc_pairs[hash].pc1 == 0xffff
-        ) {
-          pc_pairs[hash] = pc_pair;
-          show_trace = true;
-          break;
+      struct pc_pair pc_pair = {vrEmu6502GetPC(cpu), 0};
+      for (int k = 0; k < CYCLES_PER_SAMPLE; k += j) {
+        i = vrEmu6502RunInstrs(cpu, 1, &j);
+        nb_instructions += i;
+        nb_cycles += j;
+        if (i == 0)
+          goto jam; //break;
+
+        pc_pair.pc1 = vrEmu6502GetPC(cpu);
+        bool show_trace = false;
+
+        int hash = (19 * pc_pair.pc0 + 37 * pc_pair.pc1) % N_PC_PAIRS;
+        for (int i = 0; i < N_PC_PAIRS; ++i) {
+          if (
+            pc_pairs[hash].pc0 == pc_pair.pc0 &&
+              pc_pairs[hash].pc1 == pc_pair.pc1
+          )
+            break;
+          if (
+            pc_pairs[hash].pc0 == 0xffff &&
+              pc_pairs[hash].pc1 == 0xffff
+          ) {
+            pc_pairs[hash] = pc_pair;
+            show_trace = true;
+            break;
+          }
+          ++hash;
+          if (hash >= N_PC_PAIRS)
+            hash = 0;
         }
-        ++hash;
-        if (hash >= N_PC_PAIRS)
-          hash = 0;
-      }
 
-      uint8_t regs[N_TRACE_REGS] = {
-        vrEmu6502GetAcc(cpu),
-        vrEmu6502GetX(cpu),
-        vrEmu6502GetY(cpu),
-        vrEmu6502GetStackPointer(cpu),
-        vrEmu6502GetStatus(cpu)
-      };
-      for (int i = 0; i < N_TRACE_REGS; ++i) {
-        if (regs[i] < trace[pc_pair.pc1][i].min_unsigned) {
-          trace[pc_pair.pc1][i].min_unsigned = regs[i];
-          show_trace = true;
-        }
-        if ((int8_t)regs[i] < trace[pc_pair.pc1][i].min_signed) {
-          trace[pc_pair.pc1][i].min_signed = (int8_t)regs[i];
-          show_trace = true;
-        }
-        if (~regs[i] & trace[pc_pair.pc1][i].min_bitwise) {
-          trace[pc_pair.pc1][i].min_bitwise &= regs[i];
-          show_trace = true;
-        }
-        if (regs[i] > trace[pc_pair.pc1][i].max_unsigned) {
-          trace[pc_pair.pc1][i].max_unsigned = regs[i];
-          show_trace = true;
-        }
-        if ((int8_t)regs[i] > trace[pc_pair.pc1][i].max_signed) {
-          trace[pc_pair.pc1][i].max_signed = (int8_t)regs[i];
-          show_trace = true;
-        }
-        if (regs[i] & ~trace[pc_pair.pc1][i].max_bitwise) {
-          trace[pc_pair.pc1][i].max_bitwise |= regs[i];
-          show_trace = true;
+        uint8_t regs[N_TRACE_REGS] = {
+          vrEmu6502GetAcc(cpu),
+          vrEmu6502GetX(cpu),
+          vrEmu6502GetY(cpu),
+          vrEmu6502GetStackPointer(cpu),
+          vrEmu6502GetStatus(cpu)
+        };
+        for (int i = 0; i < N_TRACE_REGS; ++i) {
+          if (regs[i] < trace[pc_pair.pc1][i].min_unsigned) {
+            trace[pc_pair.pc1][i].min_unsigned = regs[i];
+            show_trace = true;
+          }
+          if ((int8_t)regs[i] < trace[pc_pair.pc1][i].min_signed) {
+            trace[pc_pair.pc1][i].min_signed = (int8_t)regs[i];
+            show_trace = true;
+          }
+          if (~regs[i] & trace[pc_pair.pc1][i].min_bitwise) {
+            trace[pc_pair.pc1][i].min_bitwise &= regs[i];
+            show_trace = true;
+          }
+          if (regs[i] > trace[pc_pair.pc1][i].max_unsigned) {
+            trace[pc_pair.pc1][i].max_unsigned = regs[i];
+            show_trace = true;
+          }
+          if ((int8_t)regs[i] > trace[pc_pair.pc1][i].max_signed) {
+            trace[pc_pair.pc1][i].max_signed = (int8_t)regs[i];
+            show_trace = true;
+          }
+          if (regs[i] & ~trace[pc_pair.pc1][i].max_bitwise) {
+            trace[pc_pair.pc1][i].max_bitwise |= regs[i];
+            show_trace = true;
+          }
         }
-      }
 
-      if (show_trace)
-        fprintf(
-          stderr,
-          "pc=%04x,%04x a=%02x%02x%02x,%02x%02x%02x x=%02x%02x%02x,%02x%02x%02x y=%02x%02x%02x,%02x%02x%02x s=%02x%02x%02x,%02x%02x%02x p=%02x%02x%02x,%02x%02x%02x\n",
-          pc_pair.pc0,
-          pc_pair.pc1,
-          trace[pc_pair.pc1][TRACE_REG_A].min_unsigned,
-          (uint8_t)trace[pc_pair.pc1][TRACE_REG_A].min_signed,
-          trace[pc_pair.pc1][TRACE_REG_A].min_bitwise,
-          trace[pc_pair.pc1][TRACE_REG_A].max_unsigned,
-          (uint8_t)trace[pc_pair.pc1][TRACE_REG_A].max_signed,
-          trace[pc_pair.pc1][TRACE_REG_A].max_bitwise,
-          trace[pc_pair.pc1][TRACE_REG_X].min_unsigned,
-          (uint8_t)trace[pc_pair.pc1][TRACE_REG_X].min_signed,
-          trace[pc_pair.pc1][TRACE_REG_X].min_bitwise,
-          trace[pc_pair.pc1][TRACE_REG_X].max_unsigned,
-          (uint8_t)trace[pc_pair.pc1][TRACE_REG_X].max_signed,
-          trace[pc_pair.pc1][TRACE_REG_X].max_bitwise,
-          trace[pc_pair.pc1][TRACE_REG_Y].min_unsigned,
-          (uint8_t)trace[pc_pair.pc1][TRACE_REG_Y].min_signed,
-          trace[pc_pair.pc1][TRACE_REG_Y].min_bitwise,
-          trace[pc_pair.pc1][TRACE_REG_Y].max_unsigned,
-          (uint8_t)trace[pc_pair.pc1][TRACE_REG_Y].max_signed,
-          trace[pc_pair.pc1][TRACE_REG_Y].max_bitwise,
-          trace[pc_pair.pc1][TRACE_REG_S].min_unsigned,
-          (uint8_t)trace[pc_pair.pc1][TRACE_REG_S].min_signed,
-          trace[pc_pair.pc1][TRACE_REG_S].min_bitwise,
-          trace[pc_pair.pc1][TRACE_REG_S].max_unsigned,
-          (uint8_t)trace[pc_pair.pc1][TRACE_REG_S].max_signed,
-          trace[pc_pair.pc1][TRACE_REG_S].max_bitwise,
-          trace[pc_pair.pc1][TRACE_REG_P].min_unsigned,
-          (uint8_t)trace[pc_pair.pc1][TRACE_REG_P].min_signed,
-          trace[pc_pair.pc1][TRACE_REG_P].min_bitwise,
-          trace[pc_pair.pc1][TRACE_REG_P].max_unsigned,
-          (uint8_t)trace[pc_pair.pc1][TRACE_REG_P].max_signed,
-          trace[pc_pair.pc1][TRACE_REG_P].max_bitwise
-        );
-      pc_pair.pc0 = pc_pair.pc1;
-    }
+        if (show_trace)
+          fprintf(
+            stderr,
+            "pc=%04x,%04x a=%02x%02x%02x,%02x%02x%02x x=%02x%02x%02x,%02x%02x%02x y=%02x%02x%02x,%02x%02x%02x s=%02x%02x%02x,%02x%02x%02x p=%02x%02x%02x,%02x%02x%02x\n",
+            pc_pair.pc0,
+            pc_pair.pc1,
+            trace[pc_pair.pc1][TRACE_REG_A].min_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_A].min_signed,
+            trace[pc_pair.pc1][TRACE_REG_A].min_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_A].max_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_A].max_signed,
+            trace[pc_pair.pc1][TRACE_REG_A].max_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_X].min_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_X].min_signed,
+            trace[pc_pair.pc1][TRACE_REG_X].min_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_X].max_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_X].max_signed,
+            trace[pc_pair.pc1][TRACE_REG_X].max_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_Y].min_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_Y].min_signed,
+            trace[pc_pair.pc1][TRACE_REG_Y].min_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_Y].max_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_Y].max_signed,
+            trace[pc_pair.pc1][TRACE_REG_Y].max_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_S].min_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_S].min_signed,
+            trace[pc_pair.pc1][TRACE_REG_S].min_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_S].max_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_S].max_signed,
+            trace[pc_pair.pc1][TRACE_REG_S].max_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_P].min_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_P].min_signed,
+            trace[pc_pair.pc1][TRACE_REG_P].min_bitwise,
+            trace[pc_pair.pc1][TRACE_REG_P].max_unsigned,
+            (uint8_t)trace[pc_pair.pc1][TRACE_REG_P].max_signed,
+            trace[pc_pair.pc1][TRACE_REG_P].max_bitwise
+          );
+        pc_pair.pc0 = pc_pair.pc1;
+      }
 jam:
 #else
-    i = vrEmu6502Run(cpu, INSTRS_PER_UPDATE, &j);
-    nb_instructions += i;
-    nb_cycles += j;
-    //if (i < INSTRS_PER_UPDATE)
-    //  break;
+      i = vrEmu6502RunCycles(cpu, CYCLES_PER_SAMPLE, &j);
+      nb_instructions += i;
+      nb_cycles += j;
+      //if (j < CYCLES_PER_UPDATE)
+      //  break;
 #endif
-    if (exit_flag)
-      break;
-    vrEmu6502Unjam(cpu);
+      if (exit_flag)
+        break;
+      vrEmu6502Unjam(cpu);
+
+      // sample tape and speaker outputs at CYCLES_PER_SAMPLE rate
+#if 0
+      if (c0x0_soft_switches_buf_count >= C0X0_SOFT_SWITCHES_BUF_SIZE) {
+        // buffer full, drop oldest sample
+        c0x0_soft_switches_buf_head =
+          (c0x0_soft_switches_buf_head + 1) &
+            (C0X0_SOFT_SWITCHES_BUF_SIZE - 1);
+        --c0x0_soft_switches_buf_count;
+      }
+#endif
+      c0x0_soft_switches_buf[
+        (c0x0_soft_switches_buf_head + c0x0_soft_switches_buf_count++) &
+          (C0X0_SOFT_SWITCHES_BUF_SIZE - 1)
+      ] = c0x0_soft_switches;
+    }
   }
 
 quit:
   vrEmu6502Destroy(cpu);
 
+  SDL_CloseAudioDevice(audio_device_id);
   SDL_DestroyRenderer(renderer);
   SDL_DestroyWindow(window);
   SDL_Quit();
index bcc7cd3..98e10cf 160000 (submodule)
@@ -1 +1 @@
-Subproject commit bcc7cd30781afd101b9b83358d161d59cbf62d7b
+Subproject commit 98e10cfc12560a94a17e43b868b07872dbc7b4fc