mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-28 08:50:22 +00:00
apple2: delta modulation demo
This commit is contained in:
parent
73b766f1a3
commit
ba2c288e8d
218
presets/apple2/deltamod.dasm
Normal file
218
presets/apple2/deltamod.dasm
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
|
||||||
|
processor 6502
|
||||||
|
|
||||||
|
;;;
|
||||||
|
;;; Apple ][ Delta Modulation Sample Playback
|
||||||
|
;;;
|
||||||
|
;;; This is the same technique used in the NES
|
||||||
|
;;; for DMC samples, but adapted for the Apple ][.
|
||||||
|
;;; Basically: Every 1 bit increases the signal
|
||||||
|
;;; by 1 step, every 0 bit decreases by 1 step.
|
||||||
|
;;; The signal is clipped on the top and bottom.
|
||||||
|
;;;
|
||||||
|
;;; The NES has a 6-bit DAC, but we only have a
|
||||||
|
;;; PWM-encoded signal. We have 13 different
|
||||||
|
;;; routines to generate pulses ranging from 4 to
|
||||||
|
;;; 14 microseconds long, with a 29-30 microsecond
|
||||||
|
;;; interval (when we reach 50% duty cycle we just
|
||||||
|
;;; invert the signal)
|
||||||
|
;;;
|
||||||
|
;;; Every 8 bits we have to fetch a new byte
|
||||||
|
;;; and increment the pointer. To avoid a high-
|
||||||
|
;;; frequency tone, we generate another pulse
|
||||||
|
;;; (the same width as the last) in this macro.
|
||||||
|
;;;
|
||||||
|
;;; also see http://michaeljmahon.com/RTSynth.html
|
||||||
|
;;; and https://github.com/oliverschmidt/Play-BTc
|
||||||
|
;;;
|
||||||
|
|
||||||
|
; uninitialized zero-page variables
|
||||||
|
seg.u ZEROPAGE
|
||||||
|
org $0
|
||||||
|
PWMBUF .ds 2
|
||||||
|
PWMLEN .ds 1
|
||||||
|
TRASH .ds 1
|
||||||
|
|
||||||
|
; speaker toggle
|
||||||
|
SPKR equ $c030
|
||||||
|
|
||||||
|
; max pulse width = PULWID*2+4 cycles
|
||||||
|
PULWID = 6
|
||||||
|
|
||||||
|
; sleep {1} cycles
|
||||||
|
; must be multiple of 2
|
||||||
|
MAC SLEEP2
|
||||||
|
.CYCLES SET {1}
|
||||||
|
IF .CYCLES == 1
|
||||||
|
ECHO "MACRO ERROR: 'SLEEP': Duration must be > 1"
|
||||||
|
ERR
|
||||||
|
ENDIF
|
||||||
|
IF .CYCLES >= 12
|
||||||
|
jsr HandyRTS
|
||||||
|
.CYCLES SET .CYCLES - 12
|
||||||
|
ENDIF
|
||||||
|
REPEAT .CYCLES / 2
|
||||||
|
nop
|
||||||
|
REPEND
|
||||||
|
ENDM
|
||||||
|
ENDM
|
||||||
|
|
||||||
|
; pulse macro
|
||||||
|
; flip speaker, wait N*2 cycles
|
||||||
|
; flip again, wait (MAX-N)*2 cycles
|
||||||
|
; total = 8 + PULWID*2 cycles
|
||||||
|
MAC PULSE
|
||||||
|
sta SPKR
|
||||||
|
SLEEP2 {1}*2
|
||||||
|
sta SPKR
|
||||||
|
SLEEP2 (PULWID-{1})*2
|
||||||
|
ENDM
|
||||||
|
|
||||||
|
; shift next bit into carry flag
|
||||||
|
; load next byte and increment
|
||||||
|
; pointer if needed
|
||||||
|
; {1} : index of previous routine
|
||||||
|
; {2} : index of current routine
|
||||||
|
; we also interleave a speaker pulse in this routine
|
||||||
|
; using macros (these are the IF/ENDIF blocks)
|
||||||
|
MAC NEXTPULSE
|
||||||
|
lsr ; next bit -> carry flag
|
||||||
|
dex ; bit count == 0?
|
||||||
|
bne .noinc ; skip inc/reload cycle
|
||||||
|
; total pulse = 15 + PULWID*2 cycles
|
||||||
|
IF {2} > 0
|
||||||
|
sta SPKR
|
||||||
|
ELSE
|
||||||
|
SLEEP2 8 ; no SPKR toggle, take up 8 cycles
|
||||||
|
ENDIF
|
||||||
|
IF {2} == 1
|
||||||
|
sta SPKR
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
; increment pointer lo byte
|
||||||
|
iny ; 2-1 = 1
|
||||||
|
|
||||||
|
IF {2} == 2
|
||||||
|
sta SPKR
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
bne .noinchi ; 4
|
||||||
|
; increment pointer hi byte
|
||||||
|
inc PWMBUF+1
|
||||||
|
dec PWMLEN
|
||||||
|
bne .noinchi
|
||||||
|
rts ; end of sound data
|
||||||
|
.noinchi
|
||||||
|
|
||||||
|
IF {2} == 3
|
||||||
|
sta SPKR
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
; reset X counter
|
||||||
|
ldx #8 ; 6
|
||||||
|
|
||||||
|
IF {2} == 4 || {2} == 5
|
||||||
|
sta SPKR
|
||||||
|
ENDIF
|
||||||
|
|
||||||
|
; fetch new byte
|
||||||
|
lda (PWMBUF),y ; 11
|
||||||
|
|
||||||
|
; a few NOPs to match the normal pulse width
|
||||||
|
; (inx, lsr, branch)
|
||||||
|
IF {2} == 6
|
||||||
|
sta SPKR
|
||||||
|
nop
|
||||||
|
ENDIF
|
||||||
|
IF {2} >= 7
|
||||||
|
nop
|
||||||
|
sta SPKR
|
||||||
|
ENDIF
|
||||||
|
; make up cycles we missed
|
||||||
|
SLEEP2 PULWID*2-6
|
||||||
|
|
||||||
|
.noinc
|
||||||
|
; carry flag still valid from lsr
|
||||||
|
bcc LVLS{1}
|
||||||
|
ENDM
|
||||||
|
|
||||||
|
;;; start of code
|
||||||
|
seg CODE
|
||||||
|
org $803 ; starting address
|
||||||
|
|
||||||
|
Start
|
||||||
|
lda #>(SAMPLE_END-SAMPLES)+1
|
||||||
|
sta PWMLEN
|
||||||
|
lda #<SAMPLES
|
||||||
|
sta PWMBUF
|
||||||
|
lda #>SAMPLES
|
||||||
|
sta PWMBUF+1
|
||||||
|
jsr Play
|
||||||
|
jmp Start
|
||||||
|
; handy RTS for wasting 6+6 cycles
|
||||||
|
HandyRTS
|
||||||
|
rts
|
||||||
|
|
||||||
|
; each pulse takes PULWID*2+18(-1) cycles
|
||||||
|
; every 8 bits, the last pulse is repeated
|
||||||
|
; 1022727 / 29.5 / (9/8) = 30817 Hz
|
||||||
|
; Y = PWMBUF lo address
|
||||||
|
; PWMBUF+0 = 0
|
||||||
|
Play
|
||||||
|
ldy PWMBUF
|
||||||
|
lda #0
|
||||||
|
sta PWMBUF
|
||||||
|
ldx #8
|
||||||
|
lda (PWMBUF),y
|
||||||
|
; each label emits a pulse at a duty cycle
|
||||||
|
; from 0 (silent) to 7
|
||||||
|
; if bit is 1, falls through to next highest level
|
||||||
|
; otherwise, jumps back to previous level
|
||||||
|
LVLS0
|
||||||
|
SLEEP2 PULWID*2+8 ; sleep, don't click
|
||||||
|
NEXTPULSE 0,0
|
||||||
|
LVLS1
|
||||||
|
PULSE 0
|
||||||
|
NEXTPULSE 0,1
|
||||||
|
LVLS2
|
||||||
|
PULSE 1
|
||||||
|
NEXTPULSE 1,2
|
||||||
|
LVLS3
|
||||||
|
PULSE 2
|
||||||
|
NEXTPULSE 2,3
|
||||||
|
LVLS4
|
||||||
|
PULSE 3
|
||||||
|
NEXTPULSE 3,4
|
||||||
|
LVLS5
|
||||||
|
PULSE 4
|
||||||
|
NEXTPULSE 4,5
|
||||||
|
LVLS6
|
||||||
|
LVLS6INV equ *+3 ; skip STA SPKR to invert signal
|
||||||
|
PULSE 5
|
||||||
|
NEXTPULSE 5,6
|
||||||
|
bcs LVLS7INV ; 1 = invert signal
|
||||||
|
LVLS7
|
||||||
|
LVLS7INV equ *+3 ; skip STA SPKR to invert signal
|
||||||
|
PULSE 4
|
||||||
|
NEXTPULSE 6INV,5 ; 0 = invert signal
|
||||||
|
LVLS8
|
||||||
|
PULSE 3
|
||||||
|
NEXTPULSE 7,4
|
||||||
|
LVLS9
|
||||||
|
PULSE 2
|
||||||
|
NEXTPULSE 8,3
|
||||||
|
LVLS10
|
||||||
|
PULSE 1
|
||||||
|
NEXTPULSE 9,2
|
||||||
|
LVLS11
|
||||||
|
PULSE 0
|
||||||
|
NEXTPULSE 10,1
|
||||||
|
LVLS12
|
||||||
|
SLEEP2 PULWID*2+8 ; sleep, don't click
|
||||||
|
NEXTPULSE 11,0
|
||||||
|
bcs LVLS12 ; if 1, stay at highest level
|
||||||
|
|
||||||
|
; delta-encoded samples
|
||||||
|
SAMPLES
|
||||||
|
.incbin "springchicken.dat12.bin" ; "Spring Chicken" by BryanTeoh
|
||||||
|
SAMPLE_END
|
BIN
presets/apple2/springchicken.dat12.bin
Normal file
BIN
presets/apple2/springchicken.dat12.bin
Normal file
Binary file not shown.
@ -482,10 +482,12 @@ export var SampleAudio = function(clockfreq) {
|
|||||||
this.feedSample = function(value, count) {
|
this.feedSample = function(value, count) {
|
||||||
accum += value * count;
|
accum += value * count;
|
||||||
sfrac += sinc * count;
|
sfrac += sinc * count;
|
||||||
if (sfrac > 1) {
|
if (sfrac >= 1) {
|
||||||
accum /= sfrac;
|
accum /= sfrac;
|
||||||
|
while (sfrac >= 1) {
|
||||||
this.addSingleSample(accum * sinc);
|
this.addSingleSample(accum * sinc);
|
||||||
sfrac -= Math.floor(sfrac);
|
sfrac -= 1;
|
||||||
|
}
|
||||||
accum *= sfrac;
|
accum *= sfrac;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,6 @@ import { Bus, BasicScanlineMachine, xorshift32, SavesState } from "../common/dev
|
|||||||
import { KeyFlags } from "../common/emu"; // TODO
|
import { KeyFlags } from "../common/emu"; // TODO
|
||||||
import { hex, lzgmini, stringToByteArray, RGBA, printFlags } from "../common/util";
|
import { hex, lzgmini, stringToByteArray, RGBA, printFlags } from "../common/util";
|
||||||
|
|
||||||
const cpuFrequency = 1022727;
|
|
||||||
const cpuCyclesPerLine = 65; // approx: http://www.cs.columbia.edu/~sedwards/apple2fpga/
|
|
||||||
const cpuCyclesPerFrame = 65*262;
|
|
||||||
|
|
||||||
// TODO: read prodos/ca65 header?
|
// TODO: read prodos/ca65 header?
|
||||||
const VM_BASE = 0x803; // where to JMP after pr#6
|
const VM_BASE = 0x803; // where to JMP after pr#6
|
||||||
const LOAD_BASE = VM_BASE;
|
const LOAD_BASE = VM_BASE;
|
||||||
@ -39,10 +35,11 @@ interface SlotDevice extends Bus {
|
|||||||
|
|
||||||
export class AppleII extends BasicScanlineMachine {
|
export class AppleII extends BasicScanlineMachine {
|
||||||
|
|
||||||
cpuFrequency = 1023000;
|
// approx: http://www.cs.columbia.edu/~sedwards/apple2fpga/
|
||||||
|
cpuFrequency = 1022727;
|
||||||
sampleRate = this.cpuFrequency;
|
sampleRate = this.cpuFrequency;
|
||||||
cpuCyclesPerLine = 65; // approx: http://www.cs.columbia.edu/~sedwards/apple2fpga/
|
cpuCyclesPerLine = 912/14; // approx: http://www.cs.columbia.edu/~sedwards/apple2fpga/
|
||||||
cpuCyclesPerFrame = 65*262;
|
cpuCyclesPerFrame = this.cpuCyclesPerLine * 262;
|
||||||
canvasWidth = 280;
|
canvasWidth = 280;
|
||||||
numVisibleScanlines = 192;
|
numVisibleScanlines = 192;
|
||||||
numTotalScanlines = 262;
|
numTotalScanlines = 262;
|
||||||
|
@ -16,6 +16,7 @@ const APPLE2_PRESETS = [
|
|||||||
{id:'hgrtest.a', name:"HGR Test (ASM)"},
|
{id:'hgrtest.a', name:"HGR Test (ASM)"},
|
||||||
{id:'conway.a', name:"Conway's Game of Life (ASM)"},
|
{id:'conway.a', name:"Conway's Game of Life (ASM)"},
|
||||||
{id:'lz4fh.a', name:"LZ4FH Decompressor (ASM)"},
|
{id:'lz4fh.a', name:"LZ4FH Decompressor (ASM)"},
|
||||||
|
{id:'deltamod.dasm', name:"Delta Modulation (ASM)"},
|
||||||
// {id:'zap.dasm', name:"ZAP! (ASM)"},
|
// {id:'zap.dasm', name:"ZAP! (ASM)"},
|
||||||
// {id:'tb_6502.s', name:'Tom Bombem (assembler game)'},
|
// {id:'tb_6502.s', name:'Tom Bombem (assembler game)'},
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user