base:dtv_dma_sid_digi_player
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
— | base:dtv_dma_sid_digi_player [2015-10-07 22:06] (current) – Moved page ftc | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== DTV DMA SID digi player experiment ====== | ||
+ | This is my little experiment (and my first try to do anything with the DTV) to play digi on the DTV without using the CPU. | ||
+ | |||
+ | ===== The idea itself ===== | ||
+ | |||
+ | This text was originally published [[http:// | ||
+ | . | ||
+ | |||
+ | DTV makes it easy to play digital sample back, since DtvSID has the " | ||
+ | |||
+ | Let's say I store sample in flash. I have to program DMA to read bytes from the flash and write them into register $D41E. Since it's possible to give zero as the destination step, it's not so complicated to find the solution to write samples one by one to the very same destination byte all the time. Cool. However one problem remains: DMA is "too fast", it would be quite extreme sampling rate if you try this. Fortunately, | ||
+ | |||
+ | As you can see, audio playback is done by the DMA, and IRQ is involved the eliminate the maximal DMA transfer size, where IRQ is triggered by the end of DMA transfer. This way, you are free to do anything with the CPU, while you have audio playback in the background. I think one issue can remain though: issuing the IRQ and reprogramming the DMA (actually it's just two LDA/STA opcodes, but still, accepting the IRQ needs some cycles) causes to have audible problems during the playback. | ||
+ | |||
+ | One thing: "speed of the DMA" have to be stable, which is not the case if other system components steal some cycles. So you should not use blitter meanwhile, also you should avoid VIC's color- and sprite data fetches (it sounds quite odd, otherwise). I have experienced this, simply switch off VIC (or disabling color fetches) worked the problem around. It's not so fun, to have switched of VIC, since the main advantage would be having "free to do anything" | ||
+ | |||
+ | ===== Source code ===== | ||
+ | |||
+ | Here is some working code, hopefully it will be useful: | ||
+ | |||
+ | < | ||
+ | ; (C)2011 Gábor Lénárt lgb-at-lgb-dot-hu | ||
+ | ; | ||
+ | ; Technical demonstration: | ||
+ | ; playing audio digi on DTV without using CPU | ||
+ | ; tested only in VICE 2.2 ... | ||
+ | ; | ||
+ | ; Note: it's my first try to do anything with DTV, so please | ||
+ | ; be patient :) Also, this is more like an " | ||
+ | ; ready-to-use solution. | ||
+ | ; Can be assembled with ca65 (assembler of the cc65 suite: www.cc65.org) | ||
+ | ; You can find some useful (?) comments throughout the source. | ||
+ | ; | ||
+ | ; Do whatever you want with this source (other than claiming you are | ||
+ | ; the author), but if you find it (the source or the idea) useful | ||
+ | ; and/or interesting, | ||
+ | ; credit in your work ... | ||
+ | |||
+ | .ORG $7FF | ||
+ | .WORD basic_stub_start | ||
+ | basic_stub_start: | ||
+ | .WORD @next_line | ||
+ | .WORD 2010 | ||
+ | .BYTE $9E | ||
+ | .BYTE .LOBYTE(main/ | ||
+ | .BYTE .LOBYTE(main/ | ||
+ | .BYTE .LOBYTE(main/ | ||
+ | .BYTE .LOBYTE(main .MOD 10+' | ||
+ | .BYTE $20,":", | ||
+ | .BYTE " | ||
+ | @next_line: | ||
+ | .WORD 0 | ||
+ | |||
+ | |||
+ | DMA_LENGTH = 0 ; 0 means 64K | ||
+ | .IF DMA_LENGTH = 0 | ||
+ | DMA_LENGTH_REAL = $10000 | ||
+ | .ELSE | ||
+ | DMA_LENGTH_REAL = DMA_LENGTH | ||
+ | .ENDIF | ||
+ | DMA_MODULO = 1 | ||
+ | ; DMA_LINE_LENGTH is for " | ||
+ | ; rate: bigger value causes lower playback frequency | ||
+ | DMA_LINE_LENGTH = 20 | ||
+ | ; Number of bytes to play from flash | ||
+ | ; (Note: this is not very accurate, since NUM_OF_DMA_RUNS is integer: | ||
+ | ; this is also a point where you can see, this is more like an experiment | ||
+ | ; than a more serious solution) | ||
+ | AUDIO_CLIP_BYTES = 1048576 | ||
+ | NUM_OF_DMA_RUNS = (AUDIO_CLIP_BYTES*DMA_LINE_LENGTH)/ | ||
+ | ; Starting address of sample data inside flash | ||
+ | FLASH_AUDIO_START_LO_WORD = $100+64000 | ||
+ | FLASH_AUDIO_START_HI_BYTE = $1 | ||
+ | DMA_START_COMMAND_W_IRQ = %10001101 | ||
+ | DMA_START_COMMAND_WO_IRQ = %00001101 | ||
+ | TARGET_REG = $D41E | ||
+ | ; Some picture to show: address in flash | ||
+ | FLASH_PICTURE_START_LO_WORD = $100 | ||
+ | FLASH_PICTURE_START_HI_BYTE = 1 | ||
+ | |||
+ | ; Using tables made easy for me to play with the DMA | ||
+ | |||
+ | dma_regs_for_playback: | ||
+ | .WORD FLASH_AUDIO_START_LO_WORD ; source lo& | ||
+ | .BYTE FLASH_AUDIO_START_HI_BYTE | ||
+ | .WORD TARGET_REG ; destination lo& | ||
+ | .BYTE 128 ; destination high address+target memory(RAM+I/ | ||
+ | .WORD 0 ; source step (zero, with line length/ | ||
+ | .WORD 0 ; destination step (zero: we want to write DtvSID' | ||
+ | .WORD DMA_LENGTH | ||
+ | .WORD DMA_MODULO | ||
+ | .WORD 0 ; destination modulo | ||
+ | .WORD DMA_LINE_LENGTH | ||
+ | .WORD 0 ; destination line length | ||
+ | .BYTE 0, | ||
+ | .BYTE 1 ; clear IRQ | ||
+ | .BYTE 1 ; enable source modulo | ||
+ | .BYTE DMA_START_COMMAND_W_IRQ ; start DMA! | ||
+ | |||
+ | dma_regs_for_screen_copy: | ||
+ | .WORD FLASH_PICTURE_START_LO_WORD | ||
+ | .BYTE FLASH_PICTURE_START_HI_BYTE | ||
+ | ; destination will be the second 64K of the RAM | ||
+ | .WORD 0 | ||
+ | .BYTE 64+1 ; destination high byte + target is RAM | ||
+ | .WORD 1 ; source step | ||
+ | .WORD 1 ; target step | ||
+ | .WORD 64000 ; DMA length | ||
+ | .WORD 0 ; source module | ||
+ | .WORD 0 ; dest modulo | ||
+ | .WORD 0 ; source line length | ||
+ | .WORD 0 ; dest line length | ||
+ | .BYTE 0, | ||
+ | .BYTE 1 ; clear IRQ | ||
+ | .BYTE 0 ; no modulo is used | ||
+ | .BYTE DMA_START_COMMAND_WO_IRQ | ||
+ | |||
+ | ; Program DMA with register data stored in memory | ||
+ | ; A/Y=address of DMA register table ($20 bytes) | ||
+ | dma_start: | ||
+ | STA 2 | ||
+ | STY 3 | ||
+ | LDA #1 ; wait for the end of possible already issued DMA | ||
+ | @wait_dma: | ||
+ | BIT $D31F | ||
+ | BNE @wait_dma | ||
+ | TAY | ||
+ | DEY | ||
+ | @dma_init_loop: | ||
+ | LDA (2),Y | ||
+ | STA $D300,Y | ||
+ | INY | ||
+ | CPY #$20 | ||
+ | BNE @dma_init_loop | ||
+ | RTS | ||
+ | |||
+ | |||
+ | |||
+ | main: | ||
+ | ; disable interrupts | ||
+ | SEI | ||
+ | ; extended DTV I/O registers to be enabled & disable interrupt sources | ||
+ | LDX #1 | ||
+ | STX $D03F ; we want access for extended DTV regs | ||
+ | DEX | ||
+ | STX $D01A ; VIC interrupt control | ||
+ | LDA #%01111111 | ||
+ | STA $DC0D ; CIA-1 interrupt control (IRQ) | ||
+ | STA $DD0D ; CIA-2 interrupt control (NMI) | ||
+ | |||
+ | ;; some DTV power ... (not so much needed here, but still) | ||
+ | ;.BYTE $32,$99 | ||
+ | ;ORA #%11 | ||
+ | ;.BYTE $32,$0 | ||
+ | |||
+ | ; switch to all RAM+I/O | ||
+ | LDA #%00110101 | ||
+ | STA 1 | ||
+ | |||
+ | ; install our IRQ handler | ||
+ | LDA #< | ||
+ | STA $FFFE | ||
+ | LDA #> | ||
+ | STA $FFFF | ||
+ | |||
+ | ; clear SID registers to be safe ... | ||
+ | LDA #0 | ||
+ | LDX #$1F | ||
+ | @sid_clear_loop: | ||
+ | STA $D400,X | ||
+ | DEX | ||
+ | BPL @sid_clear_loop | ||
+ | |||
+ | ; Initialize some SID registers for the player | ||
+ | LDA #$F0 | ||
+ | STA $D406 | ||
+ | LDA #$21 | ||
+ | STA $D404 | ||
+ | LDA #$0F | ||
+ | STA $D418 | ||
+ | |||
+ | ; DTV-VIC 320*200/ | ||
+ | ; please *DO* read my comment at the loop (color fetch, etc) | ||
+ | LDA #%01110101 | ||
+ | STA $D03C | ||
+ | LDA #%01011011 | ||
+ | STA $D011 | ||
+ | LDA #%00011000 | ||
+ | STA $D016 | ||
+ | LDY #1 | ||
+ | STY $D04B | ||
+ | DEY | ||
+ | STY $D020 | ||
+ | STY $D021 | ||
+ | STY $D047 | ||
+ | STY $D048 | ||
+ | STY $D049 | ||
+ | STY $D04A | ||
+ | LDA #8 | ||
+ | STA $D04C | ||
+ | @pal_init_loop: | ||
+ | TYA | ||
+ | STA $D200,Y | ||
+ | INY | ||
+ | CPY #$10 | ||
+ | BNE @pal_init_loop | ||
+ | |||
+ | ; DMA to copy the screen | ||
+ | LDA #< | ||
+ | LDY #> | ||
+ | JSR dma_start | ||
+ | |||
+ | start_playback: | ||
+ | |||
+ | ; initialize DMA for playback, and enable interrupts as well | ||
+ | LDA # | ||
+ | STA 4 | ||
+ | LDA # | ||
+ | STA 5 | ||
+ | LDA #0 | ||
+ | STA 6 | ||
+ | LDA #< | ||
+ | LDY #> | ||
+ | JSR dma_start | ||
+ | CLI | ||
+ | |||
+ | ; The loop (we just do " | ||
+ | ; because hardware does the playback without the CPU's help) | ||
+ | ; (since playing is done by DMA and some IRQ is used | ||
+ | ; it's totally OK to whatever you want during the playback) | ||
+ | ; In my case, I don't do too much with the CPU though :) | ||
+ | ; | ||
+ | ; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
+ | ; NOTE: using Blitter, using VIC mode which involves color-fetch | ||
+ | ; or sprite data fetch cause a very " | ||
+ | ; the speed of DMA is not constant, if other cycles need to be | ||
+ | ; " | ||
+ | ; not use color fetch, so it should be OK ... Other possibility: | ||
+ | ; try to disable "bad line emulation" | ||
+ | @wait_for_end_of_the_show: | ||
+ | ; just copy waveform accu's content to the screen border color reg | ||
+ | ; of course, much more interesting thing can be done here ... | ||
+ | LDA $D41E | ||
+ | STA $D020 | ||
+ | LDA 6 ; IRQ handler sets this ZP loc to non-zero when playing is done | ||
+ | BEQ @wait_for_end_of_the_show | ||
+ | |||
+ | SEI | ||
+ | JMP start_playback | ||
+ | |||
+ | |||
+ | |||
+ | irq_handler: | ||
+ | PHA | ||
+ | ; clear IRQ+source continue mode | ||
+ | LDA #%11 | ||
+ | STA $D31D | ||
+ | ; counting down | ||
+ | DEC 4 | ||
+ | LDA 4 | ||
+ | CMP #$FF | ||
+ | BNE @no_hib_dec | ||
+ | DEC 5 | ||
+ | BMI @counter_expired | ||
+ | @no_hib_dec: | ||
+ | ; start another DMA | ||
+ | LDA # | ||
+ | STA $D31F | ||
+ | ; return from IRQ handler | ||
+ | PLA | ||
+ | RTI | ||
+ | ; counter expired, no more playback, set the " | ||
+ | @counter_expired: | ||
+ | ; ZP location 6 is $FF when end of playing | ||
+ | LDA #$FF | ||
+ | STA 6 | ||
+ | ; return from IRQ handler | ||
+ | PLA | ||
+ | RTI | ||
+ | </ |
base/dtv_dma_sid_digi_player.txt · Last modified: 2015-10-07 22:06 by ftc