playgui: better wavescope visuals

This commit is contained in:
minjaesong
2026-06-21 01:49:32 +09:00
parent 6c9a3fdc1e
commit 2209bf1031
3 changed files with 32 additions and 18 deletions

View File

@@ -11,6 +11,6 @@ for f in *.XM; python3 xm2taud.py $f assets/disk0/home/music/(basename $f .XM).t
for f in *.mon; python3 mon2taud.py $f assets/disk0/home/music/(basename $f .mon).taud; end for f in *.mon; python3 mon2taud.py $f assets/disk0/home/music/(basename $f .mon).taud; end
for f in *.MON; python3 mon2taud.py $f assets/disk0/home/music/(basename $f .MON).taud; end for f in *.MON; python3 mon2taud.py $f assets/disk0/home/music/(basename $f .MON).taud; end
for f in *.mid; python3 midi2taud.py $f GeneralUser-GS.sf2 assets/disk0/home/music/(basename $f .mid).taud --force-synth-loop; end for f in *.mid; python3 midi2taud.py $f GeneralUser-GS.sf2 assets/disk0/home/music/(basename $f .mid).taud --force-synth-loop --mixingvol 255; end
for f in *.MID; python3 midi2taud.py $f GeneralUser-GS.sf2 assets/disk0/home/music/(basename $f .MID).taud --force-synth-loop; end for f in *.MID; python3 midi2taud.py $f GeneralUser-GS.sf2 assets/disk0/home/music/(basename $f .MID).taud --force-synth-loop --mixingvol 255; end
for f in *.midi; python3 midi2taud.py $f GeneralUser-GS.sf2 assets/disk0/home/music/(basename $f .midi).taud --force-synth-loop; end for f in *.midi; python3 midi2taud.py $f GeneralUser-GS.sf2 assets/disk0/home/music/(basename $f .midi).taud --force-synth-loop --mixingvol 255; end

View File

@@ -654,9 +654,13 @@ function aa_alowed(i) {
const c = i & 0xff const c = i & 0xff
const attr = (i >>> 8) const attr = (i >>> 8)
if (attr >= AA_NATTRS) return false if (attr >= AA_NATTRS) return false
// printable ASCII, space, or extended (>160) — keep AA_EIGHT chars so the // Printable ASCII + space ONLY. Excluding the CP437 shade / solid-block /
// glyph palette includes the TSVM ROM's box-drawing / shade / dot range. // half-block range (▒ ▓ █ ▄ ▌ ▀, codes 0xB0-0xDF) is what keeps the
if (!(c >= 33 && c <= 126) && c !== 0x20 && !(c > 160)) return false // wavescope trace thin: a fully-lit cell now resolves to a dense *ASCII*
// glyph (# a 6 J) whose inter-stroke gaps read as a fine scope line rather
// than a filled bar. The mini-AAlib has no other consumer, so this only
// affects the wavescope.
if (!(c >= 33 && c <= 126) && c !== 0x20) return false
return true return true
} }
@@ -914,10 +918,11 @@ function aa_render(img, scrW, scrH, tbOut, attrOut) {
// then converted to ASCII glyphs by the mini-AAlib above. Mid-signal only — // then converted to ASCII glyphs by the mini-AAlib above. Mid-signal only —
// stereo info lives on the bottom bar. // stereo info lives on the bottom bar.
// //
// Three monochrome intensities pick out the wave's body / peaks: DIM cells // The mini-AAlib palette is ASCII-only (no CP437 block glyphs), so the trace
// are the dim trace, NORMAL cells are the bulk of the waveform, BOLD cells // stays a fine line instead of a filled bar. Colour is by DENSITY, not AA
// land on the brightest patches (full-blocked peaks). Amber → white ramp // weight: each cell's lit-pixel count drives a blue→orange ramp — sparse
// mimics phosphor bloom. // fringes read blue, the solid body reads orange — matching the VISUALS
// section's cool-ground / warm-beam language.
const AA_WAVE_W = AG_LANE_W // 78 cells const AA_WAVE_W = AG_LANE_W // 78 cells
const AA_WAVE_H = AG_ROW_WAVE_BOT - AG_ROW_WAVE_TOP + 1 // 3 cells const AA_WAVE_H = AG_ROW_WAVE_BOT - AG_ROW_WAVE_TOP + 1 // 3 cells
@@ -928,8 +933,11 @@ const ag_waveImg = new Uint8Array(AA_WAVE_IW * AA_WAVE_IH)
const ag_waveTb = new Uint8Array(AA_WAVE_W * AA_WAVE_H) const ag_waveTb = new Uint8Array(AA_WAVE_W * AA_WAVE_H)
const ag_waveAttr = new Uint8Array(AA_WAVE_W * AA_WAVE_H) const ag_waveAttr = new Uint8Array(AA_WAVE_W * AA_WAVE_H)
// AA_NORMAL=0, AA_DIM=1, AA_BOLD=2 → amber phosphor palette. // Per-cell colour by trace DENSITY (lit source-pixels in the cell, 0..4),
const AG_WAVE_FG = [166, 130, AG_COL_LABEL] // blue→orange exactly like the VISUALS section: sparse fringes read blue (the
// cool "ground" from AG_STEREO_COL), the solid body reads orange/gold (the
// warm peak from AG_BEAM_PAL). Index 0 is background (empty cell).
const AG_WAVE_DENS_FG = [AG_COL_BG, 94, 130, 166, 220]
function ag_drawWavescope() { function ag_drawWavescope() {
const N = AG_SNAPSHOT_N const N = AG_SNAPSHOT_N
@@ -960,16 +968,21 @@ function ag_drawWavescope() {
aa_render(img, AA_WAVE_W, AA_WAVE_H, ag_waveTb, ag_waveAttr) aa_render(img, AA_WAVE_W, AA_WAVE_H, ag_waveTb, ag_waveAttr)
// Blit, skipping cells whose packed (attr<<8 | glyph) key is unchanged. // Blit, skipping cells whose packed (density<<8 | glyph) key is unchanged.
for (let r = 0; r < AA_WAVE_H; r++) { for (let r = 0; r < AA_WAVE_H; r++) {
for (let c = 0; c < AA_WAVE_W; c++) { for (let c = 0; c < AA_WAVE_W; c++) {
const idx = r * AA_WAVE_W + c const idx = r * AA_WAVE_W + c
const att = ag_waveAttr[idx]
const ch = ag_waveTb[idx] const ch = ag_waveTb[idx]
const key = (att << 8) | ch // Density = lit source-pixels in this cell's 2×2 block (0..4) →
// blue (sparse) … orange (dense).
const px = (2 * r) * IW + (2 * c)
const lit = (img[px] ? 1 : 0) + (img[px + 1] ? 1 : 0)
+ (img[px + IW] ? 1 : 0) + (img[px + IW + 1] ? 1 : 0)
const fg = AG_WAVE_DENS_FG[lit]
const key = (lit << 8) | ch
if (ag_waveGlyph[idx] === key) continue if (ag_waveGlyph[idx] === key) continue
ag_waveGlyph[idx] = key ag_waveGlyph[idx] = key
ag_color(AG_WAVE_FG[att] || AG_COL_LABEL, AG_COL_BG) ag_color(fg, AG_COL_BG)
ag_mvprn(AG_ROW_WAVE_TOP + r, AG_COL_INSIDE_L + c, ch) ag_mvprn(AG_ROW_WAVE_TOP + r, AG_COL_INSIDE_L + c, ch)
} }
} }

View File

@@ -513,6 +513,7 @@ class IOSpace(val vm: VM) : PeriBase("io"), InputProcessor {
private class Beeper { private class Beeper {
companion object { companion object {
private const val ARPRATE = 60
private const val SAMPLE_RATE = 48000 private const val SAMPLE_RATE = 48000
// SN76489 NTSC colourburst clock (3579545 Hz) after the chip's internal /32 // SN76489 NTSC colourburst clock (3579545 Hz) after the chip's internal /32
// prescaler. The square wave toggles every `divider` master ticks, so one full // prescaler. The square wave toggles every `divider` master ticks, so one full
@@ -520,8 +521,8 @@ private class Beeper {
// (divider 127 -> 440.4 Hz.) // (divider 127 -> 440.4 Hz.)
private const val MASTER_CLOCK = 3579545.4545454545 / 32.0 private const val MASTER_CLOCK = 3579545.4545454545 / 32.0
// Arpeggio note-effects step at 60 Hz: 48000 / 60 = 800 samples per step. // Arpeggio note-effects step at 60 Hz: 48000 / 60 = 800 samples per step.
private const val SAMPLES_PER_ARP_TICK = SAMPLE_RATE / 60 private const val SAMPLES_PER_ARP_TICK = SAMPLE_RATE / ARPRATE
private const val CHUNK = 512 private const val CHUNK = SAMPLES_PER_ARP_TICK
private const val AMPLITUDE = 8192 // ~ -12 dBFS; square waves are loud private const val AMPLITUDE = 8192 // ~ -12 dBFS; square waves are loud
} }