From 6776951aea42e41787bc0f7370bd5ef549dc6af4 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 29 Nov 2022 23:59:38 -0800 Subject: [PATCH] audio partly working --- Makefile | 15 +- chickens.wav | Bin 0 -> 31832 bytes dither4.js | 660 ---------------------------------------------- dither4.s | 130 +++++++-- pack-wav.js | 60 +++++ package-lock.json | 19 +- package.json | 3 +- 7 files changed, 191 insertions(+), 696 deletions(-) create mode 100644 chickens.wav delete mode 100644 dither4.js create mode 100644 pack-wav.js diff --git a/Makefile b/Makefile index 76b0977..69ad089 100644 --- a/Makefile +++ b/Makefile @@ -8,18 +8,23 @@ all : sample0.xex sample1.xex sample2.xex sample3.xex sample4.xex sample5.xex sa %.s : %.jpg dither-image.js node dither-image.js $< $@ $@.png +chickens.s : chickens.wav pack-wav.js + node pack-wav.js $< $@ + %.o : %.s ca65 -v -t atari -o $@ $< -%.xex : %.o dither4.o - ld65 -v -C atari-asm-xex.cfg -o $@ $< dither4.o +%.xex : %.o dither4.o chickens.o + ld65 -v -C atari-asm-xex.cfg -o $@ $< dither4.o chickens.o clean : rm -f dither4.o rm -f dither4.xex - rm -f sample[0-6].o rm -f sample[0-6].s + rm -f sample[0-6].o rm -f sample[0-6].xex - rm -f sample[0-6].png + rm -f sample[0-6].s.png + rm -f chickens.s + rm -f chickens.o -.dummy: sample0.s sample1.s sample2.s sample3.s sample4.s sample5.s sample6.s \ No newline at end of file +.dummy: sample0.s sample1.s sample2.s sample3.s sample4.s sample5.s sample6.s chickens.s \ No newline at end of file diff --git a/chickens.wav b/chickens.wav new file mode 100644 index 0000000000000000000000000000000000000000..9808d96c8d7eb00b8376a362c36dba20ff96c792 GIT binary patch literal 31832 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 literal 0 HcmV?d00001 diff --git a/dither4.js b/dither4.js deleted file mode 100644 index cb9aa83..0000000 --- a/dither4.js +++ /dev/null @@ -1,660 +0,0 @@ -function toLinear(val) { - // use a 2.4 gamma approximation - // this is BT.1886 compatible - // and simpler than sRGB - let unit = val / 255; - unit **= 2.4; - return unit * 255; -} - -function fromLinear(val) { - let unit = val / 255; - unit **= (1 / 2.4); - return unit * 255; -} - -class RGB { - constructor(r, g, b) { - this.r = r; - this.g = g; - this.b = b; - } - - static fromHex(val) { - let r = val & 0xff; - let g = (val >> 8) & 0xff; - let b = (val >> 16) & 0xff; - return new RGB(r,g,b); - } - - toLinear() { - return new RGB( - toLinear(this.r), - toLinear(this.g), - toLinear(this.b) - ); - } - - fromLinear() { - return new RGB( - fromLinear(this.r), - fromLinear(this.g), - fromLinear(this.b) - ); - } - - cap() { - if (this.r < 0) { - this.r = 0; - } - if (this.g < 0) { - this.g = 0; - } - if (this.b < 0) { - this.b = 0; - } - if (this.r > 255) { - this.r = 255; - } - if (this.g > 255) { - this.g = 255; - } - if (this.b > 255) { - this.b = 255; - } - } - - add(other) { - return new RGB( - this.r + other.r, - this.g + other.g, - this.b + other.b - ); - } - - difference(other) { - return new RGB( - this.r - other.r, - this.g - other.g, - this.b - other.b - ); - } - - magnitude() { - return Math.sqrt( - this.r * this.r + - this.g * this.g + - this.b * this.b - ); - } - - distance(other) { - return this.difference(other).magnitude(); - } -} - -// snarfed from https://lospec.com/palette-list/atari-8-bit-family-gtia -// which was calculated with Retrospecs App's Atari 800 emulator -let palette256 = [ - 0x000000, - 0x111111, - 0x222222, - 0x333333, - 0x444444, - 0x555555, - 0x666666, - 0x777777, - 0x888888, - 0x999999, - 0xaaaaaa, - 0xbbbbbb, - 0xcccccc, - 0xdddddd, - 0xeeeeee, - 0xffffff, - 0x190700, - 0x2a1800, - 0x3b2900, - 0x4c3a00, - 0x5d4b00, - 0x6e5c00, - 0x7f6d00, - 0x907e09, - 0xa18f1a, - 0xb3a02b, - 0xc3b13c, - 0xd4c24d, - 0xe5d35e, - 0xf7e46f, - 0xfff582, - 0xffff96, - 0x310000, - 0x3f0000, - 0x531700, - 0x642800, - 0x753900, - 0x864a00, - 0x975b0a, - 0xa86c1b, - 0xb97d2c, - 0xca8e3d, - 0xdb9f4e, - 0xecb05f, - 0xfdc170, - 0xffd285, - 0xffe39c, - 0xfff4b2, - 0x420404, - 0x4f0000, - 0x600800, - 0x711900, - 0x822a0d, - 0x933b1e, - 0xa44c2f, - 0xb55d40, - 0xc66e51, - 0xd77f62, - 0xe89073, - 0xf9a183, - 0xffb298, - 0xffc3ae, - 0xffd4c4, - 0xffe5da, - 0x410103, - 0x50000f, - 0x61001b, - 0x720f2b, - 0x83203c, - 0x94314d, - 0xa5425e, - 0xb6536f, - 0xc76480, - 0xd87591, - 0xe986a2, - 0xfa97b3, - 0xffa8c8, - 0xffb9de, - 0xffcaef, - 0xfbdcf6, - 0x330035, - 0x440041, - 0x55004c, - 0x660c5c, - 0x771d6d, - 0x882e7e, - 0x993f8f, - 0xaa50a0, - 0xbb61b1, - 0xcc72c2, - 0xdd83d3, - 0xee94e4, - 0xffa5e4, - 0xffb6e9, - 0xffc7ee, - 0xffd8f3, - 0x1d005c, - 0x2e0068, - 0x400074, - 0x511084, - 0x622195, - 0x7332a6, - 0x8443b7, - 0x9554c8, - 0xa665d9, - 0xb776ea, - 0xc887eb, - 0xd998eb, - 0xe9a9ec, - 0xfbbaeb, - 0xffcbef, - 0xffdff9, - 0x020071, - 0x13007d, - 0x240b8c, - 0x351c9d, - 0x462dae, - 0x573ebf, - 0x684fd0, - 0x7960e1, - 0x8a71f2, - 0x9b82f7, - 0xac93f7, - 0xbda4f7, - 0xceb5f7, - 0xdfc6f7, - 0xf0d7f7, - 0xffe8f8, - 0x000068, - 0x000a7c, - 0x081b90, - 0x192ca1, - 0x2a3db2, - 0x3b4ec3, - 0x4c5fd4, - 0x5d70e5, - 0x6e81f6, - 0x7f92ff, - 0x90a3ff, - 0xa1b4ff, - 0xb2c5ff, - 0xc3d6ff, - 0xd4e7ff, - 0xe5f8ff, - 0x000a4d, - 0x001b63, - 0x002c79, - 0x023d8f, - 0x134ea0, - 0x245fb1, - 0x3570c2, - 0x4681d3, - 0x5792e4, - 0x68a3f5, - 0x79b4ff, - 0x8ac5ff, - 0x9bd6ff, - 0xace7ff, - 0xbdf8ff, - 0xceffff, - 0x001a26, - 0x002b3c, - 0x003c52, - 0x004d68, - 0x065e7c, - 0x176f8d, - 0x28809e, - 0x3991af, - 0x4aa2c0, - 0x5bb3d1, - 0x6cc4e2, - 0x7dd5f3, - 0x8ee6ff, - 0x9ff7ff, - 0xb0ffff, - 0xc1ffff, - 0x01250a, - 0x023610, - 0x004622, - 0x005738, - 0x05684d, - 0x16795e, - 0x278a6f, - 0x389b80, - 0x49ac91, - 0x5abda2, - 0x6bceb3, - 0x7cdfc4, - 0x8df0d5, - 0x9effe5, - 0xaffff1, - 0xc0fffd, - 0x04260d, - 0x043811, - 0x054713, - 0x005a1b, - 0x106b1b, - 0x217c2c, - 0x328d3d, - 0x439e4e, - 0x54af5f, - 0x65c070, - 0x76d181, - 0x87e292, - 0x98f3a3, - 0xa9ffb3, - 0xbaffbf, - 0xcbffcb, - 0x00230a, - 0x003510, - 0x044613, - 0x155613, - 0x266713, - 0x377813, - 0x488914, - 0x599a25, - 0x6aab36, - 0x7bbc47, - 0x8ccd58, - 0x9dde69, - 0xaeef7a, - 0xbfff8b, - 0xd0ff97, - 0xe1ffa3, - 0x001707, - 0x0e2808, - 0x1f3908, - 0x304a08, - 0x415b08, - 0x526c08, - 0x637d08, - 0x748e0d, - 0x859f1e, - 0x96b02f, - 0xa7c140, - 0xb8d251, - 0xc9e362, - 0xdaf473, - 0xebff82, - 0xfcff8e, - 0x1b0701, - 0x2c1801, - 0x3c2900, - 0x4d3b00, - 0x5f4c00, - 0x705e00, - 0x816f00, - 0x938009, - 0xa4921a, - 0xb2a02b, - 0xc7b43d, - 0xd8c64e, - 0xead760, - 0xf6e46f, - 0xfffa84, - 0xffff99, -].map((hex) => RGB.fromHex(hex).toLinear()); - -function decimate(input, palette, n) { - // to brute-force, the possible palettes are: - // 255 * 254 * 253 = 16,386,810 - // - // we could brute force it but that's a lot :D - // but can do some bisection :D - // - // need a fitness metric. - // each pixel in the dithered line gives a distance - // sum/average them? median? maximum? - // summing evens out the ups/downs from dithering - // but doesn't distinguish between two close and two distant options - // consider median, 90th-percentile, and max of abs(distance) - // consider doing the distance for each channel? - - let line = input.slice(); - - // Apply dithering with given palette - let dither = (palette) => { - let fitness = new Float64Array(line.length); - let error = { - right: new RGB(0, 0, 0), - red: new Float64Array(line.length), - green: new Float64Array(line.length), - blue: new Float64Array(line.length), - } - - let output = new Int32Array(line.length); - let popularity = new Int32Array(palette.length); - - // Try dithering with this palette. - for (let x = 0; x < line.length; x++) { - let rgb = line[x]; - rgb = rgb.add(error.right); - //rgb.cap(); - - // find the closest possible color - let shortest = Infinity; - let pick = 1; - - for (let i = 0; i < palette.length; i++) { - let diff = rgb.difference(palette[i]); - let dist = diff.magnitude(); - if (dist < shortest) { - nextError = diff; - shortest = dist; - pick = i; - } - } - - output[x] = pick; - popularity[pick]++; - - /* - // horiz only - error.right.r = nextError.r; - error.right.g = nextError.g; - error.right.b = nextError.b; - */ - - /* - error.red[x] += nextError.r; - error.green[x] += nextError.g; - error.blue[x] += nextError.b; - */ - - /* - error.right.r = nextError.r / 2; - error.right.g = nextError.g / 2; - error.right.b = nextError.b / 2; - - error.red[x] += nextError.r / 2; - error.green[x] += nextError.g / 2; - error.blue[x] += nextError.b / 2; - */ - - if (x == 159) { - error.red[x] += error.right.r; - error.green[x] += error.right.g; - error.blue[x] += error.right.b; - } else { - error.right.r = nextError.r / 4; - error.right.g = nextError.g / 4; - error.right.b = nextError.b / 4; - - error.red[x - 1] += nextError.r / 4; - error.green[x - 1] += nextError.g / 4; - error.blue[x - 1] += nextError.b / 4; - - error.red[x] += nextError.r / 4; - error.green[x] += nextError.g / 4; - error.blue[x] += nextError.b / 4; - - error.red[x + 1] += nextError.r / 4; - error.green[x + 1] += nextError.g / 4; - error.blue[x + 1] += nextError.b / 4; - } - - /* - error.right.r = nextError.r / 4; - error.right.g = nextError.g / 4; - error.right.b = nextError.b / 4; - - error.red[x - 1] += nextError.r / 4; - error.green[x - 1] += nextError.g / 4; - error.blue[x - 1] += nextError.b / 4; - - error.red[x] += nextError.r / 4; - error.green[x] += nextError.g / 4; - error.blue[x] += nextError.b / 4; - - error.red[x + 1] += nextError.r / 4; - error.green[x + 1] += nextError.g / 4; - error.blue[x + 1] += nextError.b / 4; - */ - - // 442 is the 3d distance across the rgb cube - //fitness[x] = 442 - (nextError.magnitude()); - //fitness[x] = 442 / (442 - nextError.magnitude()); - fitness[x] = 255 / (256 - Math.max(0, nextError.r, nextError.g, nextError.b)); - - /* - fitness[x] = Math.max( - 255 - Math.abs(nextError.r), - 255 - Math.abs(nextError.g), - 255 - Math.abs(nextError.b), - ); - */ - } - return { - output, - palette, - fitness, - popularity, - error - }; - }; - - - // black, red, blue, white - let rbw = [ - palette256[0x00], - palette256[0x87], - palette256[0xf7], - palette256[0x0f], - ]; - - let rgb = [ - palette256[0x00], - palette256[0x87], - palette256[0xc7], - palette256[0xf7], - ]; - - // grayscale - let gray = [ - palette256[0x00], - palette256[0x05], - palette256[0x0a], - palette256[0x0f], - ]; - - //palette = rgb; - //palette = rbw; - //palette = gray; - - let start = Date.now(); - let decimated = palette.slice(); - - while (decimated.length > n) { - let {popularity, fitness, output} = dither(decimated); - - // Try dropping least used color on each iteration - let least = Infinity; - let pick = -1; - for (let i = 1; i < decimated.length; i++) { - if (decimated[i] === palette256[0]) { - continue; // keep black always - } - if (decimated[i] === palette256[0x0f]) { - continue; // keep white always - } - - //let coolFactor = popularity[i]; - - let coolFactor = 0; - if (popularity[i]) { - for (let x = 0; x < line.length; x++) { - if (output[x] == i) { - //coolFactor += (fitness[x] ** 2); - coolFactor += (fitness[x] ** 4); - } - } - } - - if (coolFactor < least) { - pick = i; - least = coolFactor; - } - } - let old = decimated.length; - //decimated.splice(pick, 1); - decimated = decimated.filter((rgb, i) => { - if (i == pick) { - return false; - } - if (rgb !== palette256[0] && popularity[i] == 0) { - return false; - } - if (rgb !== palette256[0x0f] && popularity[i] == 0) { - return false; - } - return true; - }); - if (decimated.length >= old) { - console.log(decimated); - debugger; - throw new Error('logic error'); - } - } - let delta = Date.now() - start; - console.log(`${delta}ms for line`); - - // Palette fits - if (decimated.length > 4) { - debugger; - } - console.log(decimated); - return dither(decimated); -} - -function convert(source, sink) { - - let width = 320; - let height = 192; - - let canvas = sink; - let ctx = canvas.getContext('2d'); - - // Draw the source image down, then grab it - // and re-draw it with custom palette & dither. - - ctx.drawImage(source, 0, 0); - - let imageData = ctx.getImageData(0, 0, width, height); - let {data} = imageData; - let nextError; - - for (let y = 0; y < height; y++) { - let line = new Uint8Array(data.buffer, y * width * 4, width * 4); - let input = []; - - // Note we take two pixels because we're using the 160-wide 4-color mode - for (let x = 0; x < width; x += 2) { - let i = x >> 1; - let rgb = new RGB( - (line[x * 4 + 0] + line[x * 4 + 4]) / 2, - (line[x * 4 + 1] + line[x * 4 + 5]) / 2, - (line[x * 4 + 2] + line[x * 4 + 6]) / 2 - ).toLinear(); - if (nextError) { - rgb.r += nextError.red[i]; - rgb.g += nextError.green[i]; - rgb.b += nextError.blue[i]; - //rgb.cap(); - } - input.push(rgb); - } - - let {output, palette, error} = decimate(input, palette256, 4); - nextError = error; - - for (let x = 0; x < width; x++) { - let rgb = palette[output[x >> 1]].fromLinear(); - line[x * 4 + 0] = rgb.r; - line[x * 4 + 1] = rgb.g; - line[x * 4 + 2] = rgb.b; - line[x * 4 + 3] = 0xff; - } - } - - ctx.putImageData(imageData, 0, 0); -} - -function run() { - for (let i = 0; i < 7; i++) { - let source = document.querySelector('#source' + i); - let sink = document.querySelector('#sink' + i); - - let doit = () => convert(source, sink); - - if (source.complete) { - doit(); - } else { - source.addEventListener('load', doit); - } - } -} - -if (document.readyState === 'loading') { - addEventListener('DOMContentLoaded', run); -} else { - run(); -} diff --git a/dither4.s b/dither4.s index 43ba541..5e0495d 100644 --- a/dither4.s +++ b/dither4.s @@ -1,13 +1,11 @@ SAVMSC = $58 -;SDMCTL = $22F -;SDLSTL = $230 -;SDLSTH = $231 COLPF0 = $D016 COLPF1 = $D017 COLPF2 = $D018 COLPF3 = $D019 COLBK = $D01A +AUDC1 = $D201 DMACTL = $D400 DLISTL = $D402 DLISTH = $D403 @@ -24,10 +22,14 @@ temp1 = temp1l temp2l = $82 temp2h = $83 temp2 = temp2l +sample_ptrl = $84 +sample_ptrh = $85 +sample_ptr = sample_ptrl height = 192 bytes_per_line = 40 pages_per_frame = 32 +lines_per_frame = 262 .data @@ -35,6 +37,8 @@ pages_per_frame = 32 .import palette2 .import palette3 .import bitmap +.import audio_samples +.import audio_samples_end displaylist: ; 24 lines overscan @@ -70,18 +74,24 @@ displaylist: lda #.hibyte(bitmap) sta temp1h lda #.lobyte(framebuffer) - sta temp2h + sta temp2l lda #.hibyte(framebuffer) sta temp2h jsr copy_half_frame ; Second half of bitmap has to be separately aligned lda #.lobyte(framebuffer2) - sta temp2h + sta temp2l lda #.hibyte(framebuffer2) sta temp2h jsr copy_half_frame + ; Set up the audio sample buffer + lda #.lobyte(audio_samples) + sta sample_ptrl + lda #.hibyte(audio_samples) + sta sample_ptrh + ; Disable display DMA lda #$00 sta DMACTL @@ -106,40 +116,102 @@ wait_vblank: lda #$22 sta DMACTL -wait_loop: - sta WSYNC +; Wait for the next even scanline (VCOUNT is scan line / 2, so look for a change) wait_start: lda VCOUNT - cmp #15 - bne wait_loop - ldy #0 + sta temp1 +wait_loop: + lda VCOUNT + cmp temp1 + beq wait_loop each_scanline: - tya - pha - - lda palette1,y - pha - ldx palette2,y - lda palette3,y + lda VCOUNT + ; Resynchronize the scanline counter + ; it'll fire on unused lines, but harmlessly + clc + sbc #15 + asl tay - pla - ; Wait for horizontal blank - sta WSYNC + .macro audio_prep + ; audio sample; low nybble + ldx #0 + lda (sample_ptr,x) + sta temp2 - ; Update color registers as fast as possible - sta COLPF0 - stx COLPF1 - sty COLPF2 + ; high nybble + lsr a + lsr a + lsr a + lsr a + ; set the volume-only bit + ora #$10 + pha - pla - tay + ; low nybble + lda temp2 + and #$0f + ; set the volume-only bit + ora #$10 + pha + .endmacro + + .macro audio_inc + ; Increment sample ptr + clc + lda sample_ptrl + adc #1 + sta sample_ptrl + lda sample_ptrh + adc #0 + sta sample_ptrh + + lda sample_ptrh + cmp #.hibyte(audio_samples_end) + bne audio_cont + lda sample_ptrl + cmp #.lobyte(audio_samples_end) + bne audio_cont + lda #.lobyte(audio_samples) + sta sample_ptrl + lda #.hibyte(audio_samples) + sta sample_ptrh + + audio_cont: + .endmacro + + .macro inner_scanline + ; Leisurely memory fetches + lda palette1,y + pha + ldx palette2,y + lda palette3,y + tay + pla + + ; Wait for horizontal blank + sta WSYNC + + ; Update color registers as fast as possible + sta COLPF0 + stx COLPF1 + sty COLPF2 + + ; Audio sample + pla + sta AUDC1 + .endmacro + + audio_prep + inner_scanline + + audio_inc iny - cpy #height - bne each_scanline + inner_scanline - jmp wait_start + ;jmp wait_start + jmp each_scanline .endproc diff --git a/pack-wav.js b/pack-wav.js new file mode 100644 index 0000000..0d2e9f9 --- /dev/null +++ b/pack-wav.js @@ -0,0 +1,60 @@ +import wavefile from 'wavefile'; +let WaveFile = wavefile.WaveFile; + +import { + readFileSync, + writeFileSync +} from 'fs'; + +function byte2byte(arr) { + let lines = []; + for (let i=0; i < arr.length; i++) { + lines.push(`.byte ${arr[i]}`); + } + return lines.join('\n'); +} + +function to4bit(val8) { + let val = val8 + 7 >> 4; + if (val > 15) { + return 15; + } + return val; +} + +function pack(audio) { + let packed = []; + for (let i = 0; i < audio.length; i += 2) { + let low = to4bit(audio[i]); + let high = to4bit(audio[i + 1]); + let byte = low | (high << 4); + packed.push(byte); + } + return packed; +} + +function audio2assembly(audio) { + return `.data + .export audio_samples + .export audio_samples_end + +audio_samples: + ${byte2byte(pack(audio))} +audio_samples_end: + +`; +} + +function wav2assembly(buffer) { + let wav = new WaveFile(buffer); + let samples = wav.getSamples(); + return audio2assembly(samples); +} + +let infile = process.argv[2]; +let outfile = process.argv[3]; + +let buffer = readFileSync(infile); +let asm = wav2assembly(buffer); +writeFileSync(outfile, asm, 'utf-8'); + diff --git a/package-lock.json b/package-lock.json index 250f164..d812ae5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,8 @@ "packages": { "": { "dependencies": { - "jimp": "^0.16.2" + "jimp": "^0.16.2", + "wavefile": "^11.0.0" } }, "node_modules/@babel/runtime": { @@ -740,6 +741,17 @@ "pako": "^1.0.5" } }, + "node_modules/wavefile": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/wavefile/-/wavefile-11.0.0.tgz", + "integrity": "sha512-/OBiAALgWU24IG7sC84cDO/KfFuvajWc5Uec0oV2zrpOOZZDgGdOwHwgEzOrwh8jkubBk7PtZfQBIcI1OaE5Ng==", + "bin": { + "wavefile": "bin/wavefile.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/xhr": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", @@ -1343,6 +1355,11 @@ "pako": "^1.0.5" } }, + "wavefile": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/wavefile/-/wavefile-11.0.0.tgz", + "integrity": "sha512-/OBiAALgWU24IG7sC84cDO/KfFuvajWc5Uec0oV2zrpOOZZDgGdOwHwgEzOrwh8jkubBk7PtZfQBIcI1OaE5Ng==" + }, "xhr": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", diff --git a/package.json b/package.json index cb9010d..fc83ca8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "type": "module", "dependencies": { - "jimp": "^0.16.2" + "jimp": "^0.16.2", + "wavefile": "^11.0.0" } }