From f537793b0b0b9af1cbeb07c2472eb3fd4511d40b Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 8 Jun 2022 23:57:01 +0200 Subject: [PATCH] added zsound demo player example (cx16) --- docs/source/todo.rst | 1 + examples/cx16/zsound/COLONY.ZSM | Bin 0 -> 32173 bytes examples/cx16/zsound/demoplayer.p8 | 66 ++++++++++ examples/cx16/zsound/zsmplayer.txt | 195 +++++++++++++++++++++++++++++ examples/test.p8 | 4 +- 5 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 examples/cx16/zsound/COLONY.ZSM create mode 100644 examples/cx16/zsound/demoplayer.p8 create mode 100644 examples/cx16/zsound/zsmplayer.txt diff --git a/docs/source/todo.rst b/docs/source/todo.rst index f34b03d7a..42a7c5b8d 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,6 +3,7 @@ TODO For next release ^^^^^^^^^^^^^^^^ +- don't put main and main.start() at the top, allow other modules to sit in front (zsound) - pipe operator: (targets other than 'Virtual'): allow non-unary function calls in the pipe that specify the other argument(s) in the calls. Already working for VM target. - add McCarthy evaluation to shortcircuit and/or expressions. First do ifs by splitting them up? Then do expressions that compute a value? ... diff --git a/examples/cx16/zsound/COLONY.ZSM b/examples/cx16/zsound/COLONY.ZSM new file mode 100644 index 0000000000000000000000000000000000000000..bdf618d33ebab3bf997f63d01cb03742ce9bc262 GIT binary patch literal 32173 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 ubyte @Pc + romsub $083e = zsm_stop() + romsub $0841 = zsm_setspeed(uword hz @XY) clobbers(A, X, Y) + romsub $0844 = zsm_setloop(ubyte count @A) + romsub $0847 = zsm_forceloop(ubyte count @A) + romsub $084a = zsm_noloop() + romsub $084d = zsm_setcallback(uword address @XY) + romsub $0850 = zsm_clearcallback() clobbers(A) + romsub $0853 = zsm_get_music_speed() clobbers(A) -> uword @XY + + const ubyte song_bank = 1 + const uword song_address = $a000 + + sub start() { + txt.print("zsound demo program!\n") + + if not cx16diskio.load_raw(8, "colony.zsm", song_bank, song_address) { + txt.print("?can't load song\n") + return + } + cx16.rambank(1) ; ram bank to default + + zsm_init() + zsm_setcallback(&end_of_song_cb) + if zsm_start(song_bank, song_address)==0 { + txt.print("music speed: ") + txt.print_uw(zsm_get_music_speed()) + txt.print(" hz\nplaying song! hit enter to stop.\n") + + ; for IRQ based playback instead: cx16.set_irq(&zsm_playIRQ, true) + while cx16.joystick_get2(0)==$ffff { + sys.waitvsync() + zsm_play() + } + zsm_stop() + } else { + txt.print("?song start error\n") + } + + ; TODO set Vera back to sane state? + } + + sub end_of_song_cb() { + txt.print("end of song!\n") + } +} diff --git a/examples/cx16/zsound/zsmplayer.txt b/examples/cx16/zsound/zsmplayer.txt new file mode 100644 index 000000000..ee529ef23 --- /dev/null +++ b/examples/cx16/zsound/zsmplayer.txt @@ -0,0 +1,195 @@ +;.............. +; init_player : +; =========================================================================== +; Arguments: (none) +; Returns: (none) +; Affects: A +; --------------------------------------------------------------------------- +; Call this before using any of the other routines. +; +; Initializes the memory locations used by ZSM player to a stopped playback +; state, with the data pointer pointing at a dummy "end-of-data" command frame. + + +;............ +; playmusic : +; =========================================================================== +; Arguments: none +; Returns: none +; Affects: See stepmusic +; --------------------------------------------------------------------------- +; 60hz frontend for stepmusic. +; Call it once per frame to play ZSM music. +; +; Playmusic calls stepmusic however many times is determined from the ZSM tick +; rate header information. This function does not "smooth" time - i.e. it +; just calls stepmusic N times in a row. For more accurate timing +; you should generate your own timing source and call stepmusic directly. +; +; NOTE: playmusic calls stepmusic, WHICH IS NOT IRQ SAFE! Use playmusic_IRQ instead +; if you wish to run the music update during the IRQ handler, or else your +; handler should save and restore the VERA ctrl register and the address +; registers for the data0 data port. + + +;................ +; playmusic_IRQ : +; =========================================================================== +; IRQ-safe version of playmusic, which restores VERA registers and the active +; HiRAM bank. +; +; It does NOT save or restore the CPU registers. Your IRQ handler should +; pull those from the stack prior to RTI as usual if not jumping into the +; Kernal's once per frame routine. +; +; Alternatively, you can use this interface as a VERA-safe call from outside +; of IRQ handlers. A,X,and Y are still clobbered by stepmusic. + + +;............ +; stepmusic : +; =========================================================================== +; Arguments: (none) +; Returns: Carry flag: (currently broken) 0=playing, 1=stopped or looped +; Affects: A,X,Y, VERA CTRL and data port 0 address registers +; --------------------------------------------------------------------------- +; Advances the music by one tick. +; Music must be initialized by startmusic before this will have any effect. +; +; This routine may be removed from the API in future revisions! +; +; Call as many times per frame as required by the ZSM's playback rate. +; (usually 60Hz - once per frame) +; THIS ROUTINE IS NOT SAFE to call directly during IRQ, as it clobbers VERA +; registers w/o fixing them (for speed reasons). If your program +; is designed to run the music player during an IRQ, use one of the IRQ-safe +; wrapper functions that save and restore VERA before calling this +; core routine, or else be sure to save the states of the affected VERA registers + + +;-----------------------------------------------------[Music Control]-------- + + +;............. +; startmusic : +; =========================================================================== +; Arguments: +; A : HIRAM bank of tune +; XY : Memory address of beginning of ZSM header +; Returns: Carry flag: 0=success, 1=fail +; Affects: A,X,Y +; --------------------------------------------------------------------------- +; Uses the ZSM header to determine the default playback parameters. +; Copies them into the active data structures, and adjusts header pointers +; such as loop and PCM (future) sample bank offsets relative to where the +; ZSM was actually loaded into memory. +; +; Calls setmusicspeed with the song's default play speed from header. +; Defaults the looping behavior based on the ZSM being played. If the tune +; contains a loop, the playback is set for infinite loop mode. If it does +; not, the loop pointer is set to the beginning of the tune, but looping +; remains disabled. +; You may call set_loop, force_loop, or disable_loop to modify these defaults +; afterwards. + + +;............ +; stopmusic : +; =========================================================================== +; Arguments: (none) +; Returns: (none) +; Affects: (none) +; --------------------------------------------------------------------------- +; Halts music playback, silences all voices used by the current tune, +; and clears music channel mask + + +; ------------------------------------------------[On-The-Fly Controls]------ + + +;................ +; setmusicspeed : +; =========================================================================== +; Arguments: +; X/Y : Playback speed in Hz. (x=lo, y=hi) +; Returns: (none) +; Affects: A,X,Y +; --------------------------------------------------------------------------- +; Converts Hz into ticks/frame, and adjusts playback speed to the new rate. +; Setting a 60hz song to play back at 120hz will play at double speed and +; setting it to 30hz will play at 1/2 speed. +; (I suppose I should make an accessor function to return the native speed +; of a ZSM so that you don't have to guess in order to make calculated speed +; changes) + + +;............. +; force_loop : +; =========================================================================== +; Arguments: .A = number of times to loop the music. 0 = infinite +; Returns: none +; Affects: none +; --------------------------------------------------------------------------- +; Note that if the music was intended to be a one-shot playback (i.e. contains +; no loop), this will force looping play of the entire song. Use set_loop +; to modify the looper's behavior without forcing looping play of non-looped +; songs. + + +;........... +; set_loop : +; =========================================================================== +; Arguments: .A = number of times to loop the music. 0 = infinite +; Returns: none +; Affects: none +; --------------------------------------------------------------------------- +; Sets the number of repeats that loopback mode will perform after the current +; pass through the song is finished. If the playback mode is one-shot (no looping) +; then this will have no effect. + + +;............... +; disable_loop : +; =========================================================================== +; Arguments: none +; Returns: none +; Affects: none +; --------------------------------------------------------------------------- +; When music reaches EOF, playback will stop, regardless of loops in the tune. +; Has no effect if looping was not already enabled. + + +;-----------------------------------------------------[Information]---------- + + +;............... +; set_callback : +; =========================================================================== +; Arguments: +; .XY = address of callback function +; Returns: none +; Affects: none +; --------------------------------------------------------------------------- +; Sets a notification callback whenever a tune finishes or loops. +; The callback passes the following parameters: +; Z=0 if music is stopped, Z=1 if music is playing +; .A = number of remaining loops + +;................. +; clear_callback : +; =========================================================================== +; Arguments: none +; Returns: none +; Affects: A +; --------------------------------------------------------------------------- +; Disables the EOF notification callback + +;.................. +; get_music_speed : +; =========================================================================== +; Arguments: none +; Returns: .XY = song's play rate in Hz +; Affects: none +; --------------------------------------------------------------------------- +; This function returns the default playback rate in Hz of the active song. +; This is NOT the current playback speed, but the speed in the song's header. diff --git a/examples/test.p8 b/examples/test.p8 index 4f90aa8ec..b33af750e 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -25,10 +25,8 @@ other { } -main $2000 { +main { - ubyte x=10 - ubyte y=20 ; sub ands(ubyte arg, ubyte b1, ubyte b2, ubyte b3, ubyte b4) -> ubyte { ; return arg>b1 and arg>b2 and arg>b3 and arg>b4 ; }