diff --git a/Makefile b/Makefile index b69f7c5..5e57313 100644 --- a/Makefile +++ b/Makefile @@ -34,27 +34,12 @@ never-gonna-give-you-up.wav : never-gonna-give-you-up.mp4 -ar 15704 \ -y $@ -rickroll-17s.wav.s : rickroll-17s.wav pack-vocoder.js - node pack-vocoder.js $< $@ - -rickroll-17s.wav : rickroll-17s.mp4 - ffmpeg -i "rickroll-17s.mp4" \ - -vn \ - -t 2.0 \ - -acodec pcm_u8 \ - -ac 1 \ - -ar 15000 \ - -y $@ - %.o : %.s ca65 -v -t atari -o $@ $< -rickroll.xex : rickroll.o dither4.o never-gonna-give-you-up.wav.o atari-asm-xex.cfg +%.xex : %.o dither4.o never-gonna-give-you-up.wav.o atari-asm-xex.cfg ld65 -v -C ./atari-asm-xex.cfg -o $@ dither4.o never-gonna-give-you-up.wav.o $< -vocoder.xex : rickroll.o vocoder.o rickroll-17s.wav.o atari-asm-xex.cfg - ld65 -v -C ./atari-asm-xex.cfg -o $@ vocoder.o rickroll-17s.wav.o $< - clean : rm -f *.o rm -f *.s.png diff --git a/dither-image.js b/dither-image.js index f311ffd..f14f1d8 100644 --- a/dither-image.js +++ b/dither-image.js @@ -79,19 +79,8 @@ class RGB { let lm = val & 15; let crlv = cr ? 50 : 0; - let phase = ((cr - 1) * 25 - 33) * (2 * Math.PI / 360); - - let y = 255 * (lm + 1) / 16; - let i = crlv * Math.cos(phase); - let q = crlv * Math.sin(phase); - - let r = y + 0.956 * i + 0.621 * q; - let g = y - 0.272 * i - 0.647 * q; - let b = y - 1.107 * i + 1.704 * q; - /* - // PAL - let phase = ((cr - 1) * 25.7 - 15) * (2 * Math.PI / 360); + let phase = ((cr - 1) * 25 - 58) * (2 * Math.PI / 360); let y = 255 * (lm + 1) / 16; let i = crlv * Math.cos(phase); @@ -102,7 +91,18 @@ class RGB { let b = y - 1.107 * i + 1.704 * q; */ - return new RGB(r, g, b).clamp().fromNTSC(); + // PAL + let phase = ((cr - 1) * 25.7 - 15) * (2 * Math.PI / 360); + + let y = 255 * (lm + 1) / 16; + let i = crlv * Math.cos(phase); + let q = crlv * Math.sin(phase); + + let r = y + 0.956 * i + 0.621 * q; + let g = y - 0.272 * i - 0.647 * q; + let b = y - 1.107 * i + 1.704 * q; + + return new RGB(r, g, b).clamp().fromSRGB(); } map(callback) { @@ -1004,9 +1004,11 @@ ${byte2byte(odd(frame.palette3))} .align 1024 displaylist: ; 24 lines overscan - .repeat 3 + .repeat 2 .byte $70 ; 8 blank lines .endrep + ; include a DLI to mark us as frame 0 + .byte $f0 ; 8 blank lines ; ${height} lines graphics ; ANTIC mode e (160px 2bpp, 1 scan line per line) diff --git a/dither4.s b/dither4.s index 5c942f8..cdb2371 100644 --- a/dither4.s +++ b/dither4.s @@ -27,6 +27,7 @@ sample_ptrh = $85 sample_ptr = sample_ptrl scanline = $86 audiotemp = $87 +frame_counter = $89 ;height = 160 height = 192 @@ -65,7 +66,6 @@ audio_high_byte: byteseq $3 byteseq $4 byteseq $5 - byteseq $6 byteseq $7 byteseq $8 byteseq $9 @@ -102,6 +102,12 @@ audio_high_byte: lda #.hibyte(displaylist) sta DLISTH + ; Set up the DLI handler + lda #.lobyte(dli_handler) + sta VDSLSTL + lda #.hibyte(dli_handler) + sta VDSLSTH + ; Disable VBI but allow Reset and DLI lda #$a0 sta NMIEN @@ -152,24 +158,25 @@ wait_loop: sta AUDC1 ; 4 cyc .endmacro - .macro audio_prep ; 8-9 cycles + .macro audio_prep ; Y is VCOUNT at entry lda (sample_ptr),y ; 5/6 cyc sta audiotemp ; 3 cyc .endmacro - .macro audio_play_lo ; 8 cycles - ; A is loaded with packed audio byte at entry - and #$0f ; 2 cyc - ora #$10 ; 2 cyc - sta AUDC1 ; 4 cyc + ; call with A pre-loaded to audiotemp + .macro audio_play_lo + ;lda audiotemp ; 3 cyc + and #$0f ; 2 cyc + ora #$10 ; 2 cyc + sta AUDC1 ; 4 cyc .endmacro ; clobbers Y .macro audio_play_hi ; 12 cycles - ldy audiotemp ; 3 cyc + ldy audiotemp ; 3 cyc lda audio_high_byte,y ; 5 cyc - sta AUDC1 ; 4 cyc + sta AUDC1 ; 4 cyc .endmacro .macro audio_inc @@ -184,12 +191,17 @@ wait_loop: cmp #.hibyte(audio_samples_end) ; 2 cyc bmi audio_cont ; 2 cyc + sta WSYNC + ; 10 cycles, optional lda #.lobyte(audio_samples) ; 2 sta sample_ptrl ; 3 lda #.hibyte(audio_samples) ; 2 sta sample_ptrh ; 3 + sta WSYNC + ldy VCOUNT ; 4 cycles + audio_cont: .endmacro @@ -203,22 +215,26 @@ wait_loop: sty scanline ; 3 cycles inner_scanline frame_offset, 0 ; 23-26 cycles before break, 12 cycles after - ldy scanline ; 3 cycles - audio_prep ; 8-9 cycles - audio_play_lo ; 8 cycles + ldy scanline ; 3 cycles + audio_prep + audio_play_lo ldy scanline ; 3 cycles inner_scanline frame_offset, 128 ; 23-26 cycles before break, 12 cycles after - audio_play_hi ; 12 cycles + audio_play_hi + ; pair cleanup: 6 cycles ldy VCOUNT ; 4 cycles bne each_scanline_pair ; 2 cycles - ; Do bookkeeping during vblank! audio_inc ; 22-32 cycles - ;ldy VCOUNT ; 4 cycles + ; frame cleanup: 11 cycles + lda frame_counter ; 3 cycles + eor #1 ; 2 cycles + sta frame_counter ; 3 cycles + ;jmp wait_start ; 3 cycles jmp each_frame ; 3 cycles .endscope .endmacro @@ -228,3 +244,10 @@ run_frame1: run_frame 0 .endproc + + +.proc dli_handler + lda #0 + sta frame_counter + rti +.endproc diff --git a/pack-vocoder.js b/pack-vocoder.js deleted file mode 100644 index 22be90f..0000000 --- a/pack-vocoder.js +++ /dev/null @@ -1,102 +0,0 @@ -import wavefile from 'wavefile'; -let WaveFile = wavefile.WaveFile; - -import {default as dct} from 'dct'; - -const sampleRate = 15000; -let frameRate = 60; -let samplesPerFrame = sampleRate / frameRate; - -import { - readFileSync, - writeFileSync -} from 'fs'; - -function audio2voices(samples) { - let voices = []; - - let floatSamples = samples.map((byte) => ((byte / 256) - 0.5) * 2); - let transformed = dct(floatSamples); - console.log('audio2voices'); - console.log(floatSamples); - console.log(transformed); - //throw new Error('xxxxx'); - - let freqs = transformed.map((_f, i) => i * frameRate); - - let bands = new Float64Array(256); - for (let i = 0; i < transformed.length; i++) { - let amplitude = transformed[i]; - let freq = freqs[i]; - if (freq == 0) { - continue; - } - let divisor = Math.floor(sampleRate / freq) - 1; - if (divisor > 255) { - continue; - } - bands[divisor] += amplitude; - } - console.log(bands); - - for (let i = 0; i < 4; i++) { - let divisor = 0; - let max = 0; - for (let j = 0; j < bands.length; j++) { - if (bands[j] > max) { - divisor = j; - max = bands[j]; - } - } - let amplitude16 = Math.floor(max * 7) + 8; - voices.push(divisor, amplitude16); - } - return voices; -} - -function byte2byte(arr) { - let lines = []; - for (let i=0; i < arr.length; i++) { - lines.push(`.byte ${arr[i]}`); - } - return lines.join('\n'); -} - -function output2assembly(output) { - return ` -.segment "AUDIO" - - .export audio_samples - .export audio_samples_end - -audio_samples: - ${byte2byte(output)} -audio_samples_end: - .byte 24 - -`; -} - -function wav2assembly(buffer) { - let wav = new WaveFile(buffer); - let samples = wav.getSamples(); - let seconds = samples.length / sampleRate; - let frames = Math.floor(seconds * frameRate); - - let output = []; - for (let i = 0; i < frames * samplesPerFrame; i += samplesPerFrame) { - let voices = audio2voices(samples.slice(i, i + samplesPerFrame)); - output.push(...voices); - } - - console.log(output); - return output2assembly(output); -} - -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/pack-wav.js b/pack-wav.js index 9e87df9..50379a0 100644 --- a/pack-wav.js +++ b/pack-wav.js @@ -20,7 +20,7 @@ class Dither { } to4bit(val8) { - let val = (val8 / 255) + this.err; + let val = (val8 / 255) - this.err; if (val < 0) { val = 0; } @@ -29,7 +29,7 @@ class Dither { } let val4 = Math.round(val * 15); let dithered = (val4 / 15); - this.err = (val - dithered); + this.err = (dithered - val); return val4; } } diff --git a/package-lock.json b/package-lock.json index 1a1c1bd..19cd297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "wavefile": "^11.0.0" }, "devDependencies": { - "dct": "^0.1.0", "eslint": "^8.36.0" } }, @@ -815,16 +814,6 @@ "node": ">= 8" } }, - "node_modules/dct": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dct/-/dct-0.1.0.tgz", - "integrity": "sha512-/uUtEniuMq1aUxvLAoDtAduyl12oM1zhA/le2f83UFN/9+4KDHXFB6znEfoj5SDDLiTpUTr26NpxC7t8IFOYhQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2614,12 +2603,6 @@ "which": "^2.0.1" } }, - "dct": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dct/-/dct-0.1.0.tgz", - "integrity": "sha512-/uUtEniuMq1aUxvLAoDtAduyl12oM1zhA/le2f83UFN/9+4KDHXFB6znEfoj5SDDLiTpUTr26NpxC7t8IFOYhQ==", - "dev": true - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 755fbef..9d78029 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "wavefile": "^11.0.0" }, "devDependencies": { - "dct": "^0.1.0", "eslint": "^8.36.0" } } diff --git a/vocoder.s b/vocoder.s deleted file mode 100644 index 5c942f8..0000000 --- a/vocoder.s +++ /dev/null @@ -1,230 +0,0 @@ -SAVMSC = $58 -VDSLST = $200 -VDSLSTL = $200 -VDSLSTH = $201 -COLPF0 = $D016 -COLPF1 = $D017 -COLPF2 = $D018 -COLPF3 = $D019 -COLBK = $D01A - -AUDC1 = $D201 -DMACTL = $D400 -DLISTL = $D402 -DLISTH = $D403 -WSYNC = $D40A -VCOUNT = $D40B -NMIEN = $D40E - -temp1l = $80 -temp1h = $81 -temp1 = temp1l -temp2l = $82 -temp2h = $83 -temp2 = temp2l -sample_ptrl = $84 -sample_ptrh = $85 -sample_ptr = sample_ptrl -scanline = $86 -audiotemp = $87 - -;height = 160 -height = 192 -bytes_per_line = 40 -pages_per_frame = 32 -lines_per_frame = 262 -;scanline_offset = 31 + (40 - 24) / 2 -scanline_offset = 30 -scanline_max = (lines_per_frame - scanline_offset) / 2 - -.data - -.import audio_samples -.import audio_samples_end - -.import frame1_top -.import frame1_bottom -.import frame1_palette1_even -.import frame1_palette1_odd -.import frame1_palette2_even -.import frame1_palette2_odd -.import frame1_palette3_even -.import frame1_palette3_odd -.import displaylist - -audio_high_byte: - .scope - .macro byteseq val - .repeat 16 - .byte val | $10 - .endrep - .endmacro - byteseq $0 - byteseq $1 - byteseq $2 - byteseq $3 - byteseq $4 - byteseq $5 - byteseq $6 - byteseq $7 - byteseq $8 - byteseq $9 - byteseq $a - byteseq $b - byteseq $c - byteseq $d - byteseq $e - byteseq $f - .endscope - -.code - -.export start - -.proc start - ; 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 - - ; Disable VBI and DLI but allow Reset - lda #$20 - sta NMIEN - - ; Set up the display list - lda #.lobyte(displaylist) - sta DLISTL - lda #.hibyte(displaylist) - sta DLISTH - - ; Disable VBI but allow Reset and DLI - lda #$a0 - sta NMIEN - - ; Manually wait for first scan line -wait_vblank: - sta WSYNC - lda VCOUNT - bne wait_vblank - - ; Re-enable display DMA - lda #$22 - sta DMACTL - -wait_start: - ; Wait for the vblank - ; Resynchronize the scanline counter -wait_loop: - ldy VCOUNT ; 4 cycles - bne wait_loop ; 2 cycles - - .macro inner_scanline frame_offset, line_offset - ; Y should be VCOUNT at entry - ; it'll fire on unused lines, but harmlessly - - ; 23-26 cycles before break - ; Leisurely memory fetches - lda frame1_palette1_even + frame_offset + line_offset - scanline_offset / 2,y ; 4/5 @FIXME alternate - pha ; 3 - ldx frame1_palette2_even + frame_offset + line_offset - scanline_offset / 2,y ; 4/5 - lda frame1_palette3_even + frame_offset + line_offset - scanline_offset / 2,y ; 4/5 - tay ; 2 - pla ; 3 - - ; Wait for horizontal blank - sta WSYNC ; 4 - - ; 12 cycles after break - ; Update color registers as fast as possible - sta COLPF0 ; 4 - stx COLPF1 ; 4 - sty COLPF2 ; 4 - .endmacro - - .macro audio_play_raw - ;ldy VCOUNT ; set on entry - lda (sample_ptr),y ; 5/6 cyc - sta AUDC1 ; 4 cyc - .endmacro - - .macro audio_prep ; 8-9 cycles - ; Y is VCOUNT at entry - lda (sample_ptr),y ; 5/6 cyc - sta audiotemp ; 3 cyc - .endmacro - - .macro audio_play_lo ; 8 cycles - ; A is loaded with packed audio byte at entry - and #$0f ; 2 cyc - ora #$10 ; 2 cyc - sta AUDC1 ; 4 cyc - .endmacro - - ; clobbers Y - .macro audio_play_hi ; 12 cycles - ldy audiotemp ; 3 cyc - lda audio_high_byte,y ; 5 cyc - sta AUDC1 ; 4 cyc - .endmacro - - .macro audio_inc - ; 22 cycles - lda sample_ptrl ; 3 cyc - clc ; 2 cyc - adc #131 ; 2 cyc - sta sample_ptrl ; 3 cyc - lda sample_ptrh ; 3 cyc - adc #0 ; 2 cyc - sta sample_ptrh ; 3 cyc - cmp #.hibyte(audio_samples_end) ; 2 cyc - bmi audio_cont ; 2 cyc - - ; 10 cycles, optional - lda #.lobyte(audio_samples) ; 2 - sta sample_ptrl ; 3 - lda #.hibyte(audio_samples) ; 2 - sta sample_ptrh ; 3 - - audio_cont: - .endmacro - - .macro run_frame frame_offset - .scope - ; each scanline is 228 color clocks - ; that's 114 CPU cycles - ; minus 41-43 for DMA leaves 71-73 clock cycles per line - - each_scanline_pair: - sty scanline ; 3 cycles - inner_scanline frame_offset, 0 ; 23-26 cycles before break, 12 cycles after - - ldy scanline ; 3 cycles - audio_prep ; 8-9 cycles - audio_play_lo ; 8 cycles - - ldy scanline ; 3 cycles - inner_scanline frame_offset, 128 ; 23-26 cycles before break, 12 cycles after - - audio_play_hi ; 12 cycles - - ldy VCOUNT ; 4 cycles - bne each_scanline_pair ; 2 cycles - - ; Do bookkeeping during vblank! - audio_inc ; 22-32 cycles - ;ldy VCOUNT ; 4 cycles - - jmp each_frame ; 3 cycles - .endscope - .endmacro - -each_frame: -run_frame1: - run_frame 0 - -.endproc