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 000000000..bdf618d33 Binary files /dev/null and b/examples/cx16/zsound/COLONY.ZSM differ diff --git a/examples/cx16/zsound/demoplayer.p8 b/examples/cx16/zsound/demoplayer.p8 new file mode 100644 index 000000000..a020bfd2f --- /dev/null +++ b/examples/cx16/zsound/demoplayer.p8 @@ -0,0 +1,66 @@ +%import textio +%import cx16diskio +%zeropage basicsafe +%zpreserved $22,$28 ; zsound lib uses this region + + +;; Proof Of Concept ZSM player using a binary blob version of zsound library by ZeroByte relocated to something usable here. + + +main $0830 { +; TODO: don't like to have *main* at a set start address, but otherwise the compiler puts it at the front, overwriting another module that we wanted there + +zsound_lib: + ; this has to be the first statement to make sure it loads at the specified module address $0830 + %asmbinary "zsmplayer-0830.bin" + + ; note: jump table is offset by 2 from the load address (because of prg header) + romsub $0832 = zsm_init() clobbers(A) + romsub $0835 = zsm_play() clobbers(A, X, Y) + romsub $0838 = zsm_playIRQ() clobbers(A, X, Y) + romsub $083b = zsm_start(ubyte bank @A, uword song_address @XY) clobbers(A, X, Y) -> 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 ; }