Advertisement
vladikcomper

Mega PCM v.1.1 Source Code

Sep 10th, 2014
323
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. ; ===============================================================
  2. ; ---------------------------------------------------------------
  3. ; Mega PCM v.1.1
  4. ; (C) 2012, Vladikcomper
  5. ; ---------------------------------------------------------------
  6.  
  7.     cpu z80
  8.  
  9. ; ---------------------------------------------------------------
  10. ; Constants
  11. ; ---------------------------------------------------------------
  12.  
  13. ; Memory variables
  14.  
  15. Stack       equ 1FF0h
  16. Ptr_InitPlayback equ    Event_InitPlayback+1    ; Init Playback event pointer
  17. Ptr_SoundProc   equ Event_SoundProc+1   ; Sound process event pointer
  18. Ptr_Interrupt   equ Event_Interrupt+1   ; Sound interrupt event pointer
  19. Ptr_EndPlayback equ Event_EndPlayback+1 ; End playback event pointer
  20. DAC_Number  equ 1FFFh           ; Number of DAC sample to play ($81-based)
  21.                         ; There are special numbers to control playback:
  22.                         ;   $80 - Stop Playback
  23.                         ;   $7F - Pause Playback
  24.                         ;   $00 - Continue Playback
  25.  
  26. ; System ports
  27.  
  28. YM_Port0_Ctrl   equ 4000h
  29. YM_Port0_Data   equ 4001h
  30. YM_Port1_Ctrl   equ 4002h
  31. YM_Port1_Data   equ 4003h
  32. BankRegister    equ 6000h
  33.  
  34. ; Sample struct vars
  35.  
  36. flags   equ 0   ; playback flags
  37. pitch   equ 1   ; pitch value
  38. s_bank  equ 2   ; start bank
  39. e_bank  equ 3   ; end bank
  40. s_pos   equ 4   ; start offset (in first bank)
  41. e_pos   equ 6   ; end offset (in last bank)
  42.  
  43.  
  44. ; ===============================================================
  45. ; ---------------------------------------------------------------
  46. ; Driver initialization code
  47. ; ---------------------------------------------------------------
  48.  
  49.     di              ; disable interrupts
  50.     di
  51.     di
  52.  
  53.     ; Setup variables
  54.     ld  sp,Stack        ; init SP
  55.     xor a           ; a = 0
  56.     ld  (DAC_Number),a      ; reset DAC to play
  57.     ld  h,a
  58.     ld  l,a
  59.     ld  (Ptr_InitPlayback),hl   ; reset 'InitPlayback' event
  60.     ld  (Ptr_SoundProc),hl  ; reset 'SoundProc' event
  61.     ld  (Ptr_Interrupt),hl  ; reset 'Interrupt' event
  62.     ld  (Ptr_EndPlayback),hl    ; reset 'PlayOver' event
  63.     ld  iy,YM_Port0_Ctrl
  64.  
  65. ; ---------------------------------------------------------------
  66. ; Idle loop, waiting DAC number input
  67. ; ---------------------------------------------------------------
  68.  
  69. Idle_Loop:
  70.     ld  hl,DAC_Number
  71.  
  72. Idle_WaitDAC:
  73.     ld  a,(hl)          ; load DAC number
  74.     or  a           ; test it
  75.     jp  p,Idle_WaitDAC      ; if it's positive, branch
  76.  
  77. ; ---------------------------------------------------------------
  78. ; Load DAC sample according to its number and play it
  79. ; ---------------------------------------------------------------
  80.  
  81. LoadDAC:
  82.     sub 81h         ; subtract 81h from DAC number
  83.     jr  c,Idle_WaitDAC      ; if a = 80h, branch
  84.     ld  (hl),0h         ; reset DAC number in RAM
  85.  
  86.     ; Load DAC table entry
  87.     ld  ix,DAC_Table        ; ix = DAC Table
  88.     ld  h,0h
  89.     ld  l,a         ; hl = DAC
  90.     add hl,hl           ; hl = DAC*2
  91.     add hl,hl           ; hl = DAC*4
  92.     add hl,hl           ; hl = DAC*8
  93.     ex  de,hl
  94.     add ix,de           ; ix = DAC_Table + DAC*8
  95.  
  96.     ; Init events table according to playback mode
  97.     ld  a,(ix+flags)        ; a = Flags
  98.     and 7h          ; mask only Mode
  99.     add a,a         ; a = Mode*2
  100.     add a,a         ; a = Mode*4
  101.     add a,a         ; a = Mode*8
  102.     ld  b,0h
  103.     ld  c,a         ; bc = Mode*8
  104.     ld  hl,Events_List
  105.     add hl,bc           ; hl = Events_List + Mode*8
  106.     ld  de,Ptr_InitPlayback ; de = Events Pointers
  107.     ld  bc,4FFh         ; do 4 times, 'c' should never borrow 'b' on decrement
  108. -   ldi             ; transfer event pointer
  109.     ldi             ;
  110.     inc de          ; skip a byte in events table ('jp' opcode)
  111.     djnz    -
  112.  
  113.     jp  Event_InitPlayback  ; launch 'InitPlayback' event
  114.  
  115. ; ---------------------------------------------------------------
  116. ; Setup YM to playback DAC
  117. ; ---------------------------------------------------------------
  118.  
  119. SetupDAC:
  120.     ld  (iy+0),2Bh      ;
  121.     ld  (iy+1),80h      ; YM => Enable DAC
  122.     ld  a,(ix+flags)        ; load flags
  123.     and 0C0h            ; are pan bits set?
  124.     jr  z,+         ; if not, branch
  125.         ld  (iy+2),0B6h     ;
  126.     ld  (iy+3),a        ; YM => Set Pan
  127. +   ld  (iy+0),2Ah      ; setup YM to fetch DAC bytes
  128.     ret
  129.  
  130. ; ---------------------------------------------------------------
  131.  
  132. Events_List:
  133.     ;   Initplayback,   SoundProc,  Interrupt,  EndPlayback ;
  134.     dw  Init_PCM,   Process_PCM,    Int_Normal, StopDAC     ; Mode 0
  135.     dw  Init_PCM,   Process_PCM,    Int_NoOverride, StopDAC     ; Mode 1
  136.     dw  Init_PCM,   Process_PCM,    Int_Normal, Reload_PCM  ; Mode 2
  137.     dw  Init_PCM,   Process_PCM,    Int_NoOverride, Reload_PCM  ; Mode 3
  138.     dw  Init_DPCM,  Process_DPCM,   Int_Normal, StopDAC     ; Mode 4
  139.     dw  Init_DPCM,  Process_DPCM,   Int_NoOverride, StopDAC     ; Mode 5
  140.     dw  Init_DPCM,  Process_DPCM,   Int_Normal, Reload_DPCM ; Mode 6
  141.     dw  Init_DPCM,  Process_DPCM,   Int_NoOverride, Reload_DPCM ; Mode 7
  142.  
  143. ; ===============================================================
  144. ; ---------------------------------------------------------------
  145. ; Dynamic Events Table, filled from 'Events_List'
  146. ; ---------------------------------------------------------------
  147.  
  148. Event_InitPlayback:
  149.     jp  0h
  150.  
  151. Event_SoundProc:
  152.     jp  0h
  153.    
  154. Event_Interrupt:
  155.     jp  0h
  156.  
  157. Event_EndPlayback:
  158.     jp  0h
  159.  
  160.  
  161. ; ===============================================================
  162. ; ---------------------------------------------------------------
  163. ; Routines to control sound playback (stop/pause/interrupt)
  164. ; ---------------------------------------------------------------
  165. ; NOTICE:
  166. ;   The following routines are 'Interrupt' event handlers,
  167. ;   they must't use any registers except A. If they do,
  168. ;   it will break sample playback code.
  169. ;   You may do push/pop from stack though.
  170. ;   'StopDAC' is expection, as it breaks playback anyway.
  171. ; ---------------------------------------------------------------
  172.  
  173. ; ---------------------------------------------------------------
  174. ; DAC Interrupt: Normal Priority
  175. ; ---------------------------------------------------------------
  176. ; INPUT:
  177. ;   a   = Ctrl byte
  178. ; ---------------------------------------------------------------
  179.  
  180. Int_Normal:
  181.     cp  80h         ; stop flag?
  182.     jp  z,StopDAC       ; if yes, branch
  183.     jp  m,PauseDAC      ; if < 80h, branch
  184.     ld  hl,DAC_Number
  185.     jp  LoadDAC
  186.  
  187. ; ---------------------------------------------------------------
  188. ; DAC Interrupt: High Priority
  189. ; ---------------------------------------------------------------
  190. ; INPUT:
  191. ;   a   = Ctrl byte
  192. ; ---------------------------------------------------------------
  193.  
  194. Int_NoOverride:
  195.     cp  80h         ; stop flag?
  196.     jp  z,StopDAC       ; if yes, branch
  197.     jp  m,PauseDAC      ; if < 80h, branch
  198.     xor a           ; a = 0
  199.     ld  (DAC_Number),a      ; clear DAC number to prevent later ints
  200.     jp  Event_SoundProc
  201.  
  202. ; ---------------------------------------------------------------
  203. ; Code to wait while playback is paused
  204. ; ---------------------------------------------------------------
  205.  
  206. PauseDAC:
  207.     ld  (iy+1),80h      ; stop sound
  208.  
  209. -   ld  a,(DAC_Number)      ; load ctrl byte
  210.     or  a           ; is byte zero?
  211.     jr  nz,-            ; if not, branch
  212.  
  213.     call    SetupDAC        ; setup YM for playback
  214.     jp  Event_SoundProc     ; go on playing
  215.  
  216. ; ---------------------------------------------------------------
  217. ; Stop DAC playback and get back to idle loop
  218. ; ---------------------------------------------------------------
  219.  
  220. StopDAC:
  221.     ld  (iy+1),80h      ; stop sound
  222.     jp  Idle_Loop
  223.  
  224.  
  225. ; ===============================================================
  226. ; ---------------------------------------------------------------
  227. ; Routines to control bank-switching
  228. ; ---------------------------------------------------------------
  229. ; Bank-Switch Registers Set:
  230. ;   b'  = Current Bank Number
  231. ;   c'  = Last Bank Number
  232. ;   de' = Bank Register
  233. ;   hl' = End offset (bytes to play in last bank)
  234. ; ---------------------------------------------------------------
  235.  
  236. ; ---------------------------------------------------------------
  237. ; Inits bank-switch system and loads first bank
  238. ; ---------------------------------------------------------------
  239.  
  240. InitBankSwitching:
  241.     exx
  242.     ld  d,(ix+s_pos+1)
  243.     ld  e,(ix+s_pos)    ; de' = start offset (in first bank)
  244.     ld  h,(ix+e_pos+1)
  245.     ld  l,(ix+e_pos)    ; hl' = end offset (in last bank)
  246.     ld  b,(ix+s_bank)   ; b'  = start bank number
  247.     ld  c,(ix+e_bank)   ; c'  = end bank number
  248.     ld  a,b     ; load start bank number
  249.     cp  c       ; does the sample end in the first bank?
  250.     jr  nz,+        ; if not, branch
  251.     sbc hl,de       ; hl' = end offset - start offset
  252.     set 7,h     ; make the number 8000h-based
  253. +   ld  de,BankRegister ; de' = bank register
  254.     jp  LoadBank
  255.  
  256. ; ---------------------------------------------------------------
  257. ; Subroutine to switch to the next bank
  258. ; ---------------------------------------------------------------
  259.  
  260. LoadNextBank:
  261.     exx
  262.     inc b       ; increase bank number
  263.     ld  a,b     ; load bank number
  264.  
  265. LoadBank:
  266.     ld  (de), a ; A15
  267.     rrca
  268.     ld  (de), a ; A16
  269.     rrca
  270.     ld  (de), a ; A17
  271.     rrca
  272.     ld  (de), a ; A18
  273.     rrca
  274.     ld  (de), a ; A19
  275.     rrca
  276.     ld  (de), a ; A20
  277.     rrca
  278.     ld  (de), a ; A21
  279.     rrca
  280.     ld  (de), a ; A22
  281.     xor a   ; a = 0
  282.     ld  (de), a ; A23
  283.     exx
  284.     ret
  285.  
  286. ; ===============================================================
  287. ; ---------------------------------------------------------------
  288. ; Routines to process PCM sound playback
  289. ; ---------------------------------------------------------------
  290. ; PCM Registers Set:
  291. ;   B   = Pitch Counter
  292. ;   C   = Pitch
  293. ;   DE  = <Unused>
  294. ;   HL  = PCM byte pointer
  295. ; ---------------------------------------------------------------
  296.  
  297. ; ---------------------------------------------------------------
  298. ; Init PCM playback or reload PCM file
  299. ; ---------------------------------------------------------------
  300.  
  301. Reload_PCM:
  302.  
  303. Init_PCM:    
  304.     call    SetupDAC      
  305.     call    InitBankSwitching
  306.     ld  c,(ix+pitch)        ; c  = pitch
  307.     ld  h,(ix+s_pos+1)      ;
  308.     ld  l,(ix+s_pos)        ; hl = Start offset
  309.     set 7,h         ; make it 8000h-based if it's not (perverts memory damage if playing corrupted slots)
  310.     ld  (iy+0),2Ah      ; YM => prepare to fetch DAC bytes
  311.  
  312. ; ---------------------------------------------------------------
  313. ; PCM Playback Loop
  314. ; ---------------------------------------------------------------
  315.  
  316. Process_PCM:
  317.  
  318.     ; Read sample's byte and send it to DAC with pitching
  319.     ld  a,(hl)          ; 7 ; get PCM byte
  320.     ld  b,c         ; 4 ; b = Pitch
  321.     djnz    $           ; 8/13+ ; wait until pitch zero
  322.     ld  (YM_Port0_Data),a   ; 13    ; write to DAC
  323.     ; Cycles: 32*
  324.  
  325.     ; Increment PCM byte pointer and switch the bank if necessary
  326.     inc hl          ; 6 ; next PCM byte
  327.     bit 7,h         ; 8 ; has the bank warped?
  328.     jr  z,++            ; 7/12  ; if yes, switch the bank
  329.     ; Cycles: 21
  330.  
  331.     ; Check if sample playback is finished
  332.     exx             ; 4 ;
  333.     ld  a,c         ; 4 ; load last bank no.
  334.     sub b           ; 4 ; compare to current bank no.
  335.     jr  nz,+            ; 7/12  ; if last bank isn't reached, branch
  336.     dec hl          ; 6 ; decrease number of bytes to play in last bank
  337.     or  h           ; 4 ; is hl positive?
  338.     jp  p,+++           ; 10    ; if yes, quit playback loop
  339.     exx             ; 4 ;
  340.     ; Cycles: 43
  341.  
  342.     ; Check if we should play a new sample
  343. -   ld  a,(DAC_Number)      ; 13    ; load DAC number
  344.     or  a           ; 4 ; test it
  345.     jp  z,Process_PCM       ; 10    ; if zero, go on playing
  346.     jp  Event_Interrupt     ;   ; otherwise, interrupt playback
  347.     ; Cycles: 27
  348.  
  349.     ; Synchronization loop (20 cycles)
  350. +   exx             ; 4
  351.     nop             ; 4
  352.     jr  -           ; 12
  353.  
  354.     ; Switch to next bank
  355. +   ld  h,80h           ; restore base addr
  356.     call    LoadNextBank
  357.     jp  -
  358.  
  359.     ; Quit playback loop
  360. +   exx
  361.     jp  Event_EndPlayback
  362.  
  363. ; ---------------------------------------------------------------
  364. ; Best cycles per loop: 123*
  365. ; Max Possible rate:    3,550 kHz / 123 = 29 kHz (PAL)
  366. ; ---------------------------------------------------------------
  367.  
  368. ; ===============================================================
  369. ; ---------------------------------------------------------------
  370. ; Routines to process DPCM sound playback
  371. ; ---------------------------------------------------------------
  372. ; DPCM Registers Set:
  373. ;   B   = Pitch Counter / also DAC Value
  374. ;   C   = Pitch
  375. ;   DE  = DPCM byte pointer
  376. ;   HL  = Delta Table
  377. ; ---------------------------------------------------------------
  378.  
  379. ; ---------------------------------------------------------------
  380. ; Init DPCM playback or reload DPCM file
  381. ; ---------------------------------------------------------------
  382.  
  383. Reload_DPCM:
  384.  
  385. Init_DPCM:
  386.     call    SetupDAC
  387.     call    InitBankSwitching
  388.     ld  c,(ix+pitch)        ; c  = pitch
  389.     ld  d,(ix+s_pos+1)      ;
  390.     ld  e,(ix+s_pos)        ; de = start offset
  391.     set 7,d         ; make it 8000h-based if it's not (perverts memory damage if playing corrupted slots)
  392.     ld  h,DPCM_DeltaArray>>8    ; load delta table base
  393.     ld  (iy+0),2Ah      ; YM => prepare to fetch DAC bytes
  394.     ld  b,80h           ; init DAC value
  395.  
  396. Process_DPCM:
  397.  
  398.     ; Calculate and send 2 values to DAC
  399.     ld  a,(de)          ; 7 ; get a byte from DPCM stream
  400.     rrca                ; 4 ; get first nibble
  401.     rrca                ; 4 ;
  402.     rrca                ; 4 ;
  403.     rrca                ; 4 ;
  404.     and 0Fh         ; 7 ; mask nibble
  405.     ld  l,a         ; 4 ; setup delta table index
  406.     ld  a,b         ; 4 ; load DAC Value
  407.     add a,(hl)          ; 7 ; add delta to it
  408.     ld  b,c         ; 4 ; b = Pitch
  409.     djnz    $           ; 7/13+ ; wait until pitch zero
  410.     ld  (YM_Port0_Data),a   ; 13    ; write to DAC
  411.     ld  b,a         ; 4 ; b = DAC Value
  412.     ; Cycles: 73
  413.  
  414.     ld  a,(de)          ; 7 ; reload DPCM stream byte
  415.     and 0Fh         ; 7 ; get second nibble
  416.     ld  l,a         ; 4 ; setup delta table index
  417.     ld  a,b         ; 4 ; load DAC Value
  418.     add a,(hl)          ; 7 ; add delta to it
  419.     ld  b,c         ; 4 ; b = Pitch
  420.     djnz    $           ; 7/13+ ; wait until pitch zero
  421.     ld  (YM_Port0_Data),a   ; 13    ; write to DAC
  422.     ld  b,a         ; 4 ; b = DAC Value
  423.     ; Cycles: 57
  424.  
  425.     ; Increment DPCM byte pointer and switch the bank if necessary
  426.     inc de          ; 6 ; next DPCM byte
  427.     bit 7,d         ; 8 ; has the bank warped?
  428.     jr  z,++            ; 7/12  ; if no, switch the bank
  429.     ; Cycles: 21
  430.  
  431.     ; Check if sample playback is finished
  432.     exx             ; 4 ;
  433.     ld  a,c         ; 4 ; load last bank no.
  434.     sub b           ; 4 ; compare to current bank no.
  435.     jr  nz,+            ; 7/12  ; if last bank isn't reached, branch
  436.     dec hl          ; 6 ; decrease number of bytes to play in last bank
  437.     or  h           ; 4 ; is hl positive?
  438.     jp  p,+++           ; 10    ; if yes, quit playback loop
  439.     exx             ; 4 ;
  440.     ; Cycles: 43
  441.  
  442.     ; Check if we should play a new sample
  443. -   ld  a,(DAC_Number)      ; 13    ; load DAC number
  444.     or  a           ; 4 ; test it
  445.     jp  z,Process_DPCM      ; 10    ; if zero, go on playing
  446.     jp  Event_Interrupt     ;   ; otherwise, interrupt playback
  447.     ; Cycles: 27
  448.  
  449.     ; Synchronization loop (20 cycles)
  450. +   exx             ; 4
  451.     nop             ; 4
  452.     jr  -           ; 12
  453.  
  454.     ; Switch to next bank
  455. +   ld  d,80h           ; restore base address
  456.     call    LoadNextBank
  457.     jp  -
  458.  
  459.     ; Quit playback loop
  460. +   exx
  461.     jp  Event_EndPlayback
  462.  
  463. ; ---------------------------------------------------------------
  464. ; Best cycles per loop: 221/2
  465. ; Max possible rate:    3,550 kHz / 111 = 32 kHz (PAL)
  466. ; ---------------------------------------------------------------
  467.                                        
  468.     align   100h    ; it's important to align this way, or the code above won't work properly
  469.  
  470. DPCM_DeltaArray:
  471.     db  0, 1, 2, 4, 8, 10h, 20h, 40h
  472.     db  -80h, -1, -2, -4, -8, -10h, -20h, -40h
  473.  
  474. ; ---------------------------------------------------------------
  475. ; NOTICE ABOUT PLAYBACK RATES:
  476. ;   YM is only capable of producing DAC sound @ ~26 kHz
  477. ;   frequency, overpassing it leads to missed writes!
  478. ;   The fact playback code can play faster than that
  479. ;   means there is a good amount of room for more features,
  480. ;   i.e. to waste even more processor cycles! ;)
  481. ; ---------------------------------------------------------------
  482.  
  483. ; ===============================================================
  484.  
  485. ; Table of DAC samples goes right after the code.
  486. ; It remains empty here, you are meant to fill it in your hack's
  487. ; disassembly right after including compiled driver.
  488.  
  489. DAC_Table:
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement