z80 Development Kit
You are not logged in.
Hi,
I'm new to Spectrum programming and z88dk. I'm trying to make a graphics demo with SP1 sprites and I want to add some background AY music. My goal is to have an IM2 isr playing the AY music in the background.
I've googled a bit on this and several people recommend using Vortex Tracker II and export the music file as an "Hobeta with player". There are also options for exporting as "Hobeta without player", ".AY-file", ".SCL-file (player and module separately)", and ".TAP-file (player and module separately)".
I have tried to use Vortex Tracker II and export a music file as an "Hobeta with player" with 0xA000 as compilation address and tried to use the assembly snippet provided in the Vortex Tracker export dialog. I first tried to do this in my C program and call it from an IM2 isr but it just crashed horribly. I then tried to write a simple assembly program for playing the exported Hobeta with player file by using BINARY/INCBIN for including it and then play it using the code snippet. That didn't work either...
Can an Hobeta file just be included using BINARY/INCBIN in an assembly file? I read somewhere that the Hobeta file format contains a 17 bytes header but removing 17 bytes from the Hobeta file didn't improve anything.
Does any one have any tips (or even code snippets) on how to play some format of AY files using z88dk? This was more difficult than I first thought. I had some naive idea that there would be some kind of standard AY data format that could just feed to an AY player routine but it seems more complicated than that...
Regards,
Stefan
Offline
Stefan123 wrote:
I've googled a bit on this and several people recommend using Vortex Tracker II and export the music file as an "Hobeta with player". There are also options for exporting as "Hobeta without player", ".AY-file", ".SCL-file (player and module separately)", and ".TAP-file (player and module separately)".
Another option is wyz tracker. There are also tools that produce ay sound effects (ayfx), automatically convert midis to AY and a new tool that tries to replicate wavs at 50Hz -- it works quite well for sound effects although its initial purpose was speech synthesis.
Can an Hobeta file just be included using BINARY/INCBIN in an assembly file? I read somewhere that the Hobeta file format contains a 17 bytes header but removing 17 bytes from the Hobeta file didn't improve anything.
No, not quite. The best output option from VT is tap file that will have the player followed by song saved as CODE.
Here's a basic player that will play music output in this form using the default assemble option (to address 49152):
10 LOAD ""CODE : REM player 20 LOAD ""CODE : REM music 30 RANDOMIZE USR 49152 : REM init 40 RANDOMIZE USR 49157 : REM play one step @ init + 5 50 PAUSE 1 : REM wait 1/50s 60 GOTO 40
The problem with VT and many other tools is that they're not very helpful in producing something useful for programmers. In this case the only output option from VT is a player and music data that has already been assembled to a fixed address. This makes it more difficult to use; ideally you want player and data in assembly language so that you can assemble it as part of your program and have it made part of the output binary. In the already assembled form you have to make sure your program does not overwrite the area reserved for the player and song and you have to load it separately into memory.
The source code for VT's pt player is available out there and can be modified to be used with z80asm so this option is open but it requires a little more work.
z80asm's BINARY keyword will include a binary at the point of assembly (you don't know what the address will be). This is not compatible with what VT's done - it requires the binary to be loaded at a specific address. In z80asm you can place things in memory by creating sections at a specific address and then, eg, including a binary there but this doesn't get you any further ahead as z80asm's output for that section will be a binary, something you already have. It's your responsibility to get each output binary loaded into the spectrum's memory at the required address. So what you really want to do is just load those extra bits of code that VT provided in its tap from basic before starting the C program.
From C you should be able to play a VT song like this (new c library ; I am a little rusty on the classic c library):
"main.c" #include <intrinsic.h> #include <string.h> #include <z80.h> // vortex tracker subroutines implemented in vt.asm file extern void vt_init(void); extern void vt_play(void); void main(void) { // create im2 interrupt routine intrinsic_di(); // disable interrupts, unnecessary for recent z88dk builds because the new crts disable interrupts at startup memset(0xfd00, 0xfe, 257); // create 257-byte im2 table z80_bpoke(0xfefe, 195); // jp z80_wpoke(0xfeff, (unsigned int)vt_play); // _vt_play im2_init(0xfd00); // point I at table intrinsic_ei(); // enable interrupts vt_init(); while (1) ; // forever } "vt.asm" SECTION code_user PUBLIC _vt_init PUBLIC _vt_play _vt_init: ;; initialize song ;; I don't believe vt's code worries about race conditions so ;; we should disable interrupts around the initialization di call 0xc000 ;; vortex tracker assembles to this address ei ret _vt_play: ;; vt interrupt service routine ;; must save all registers as we're not sure what the vt player uses push af push bc push de push hl ex af,af' exx push af push bc push de push hl push ix push iy call 0xc005 ;; vortex tracker play address pop iy pop ix pop hl pop de pop bc pop af exx ex af,af' pop hl pop de pop bc pop af ei reti
You would compile with (eg):
sccz80: zcc +zx -vn -startup=31 -O3 -clib=new main.c vt.asm -o main -create-app
sdcc: zcc +zx -vn -startup=31 -clib=sdcc_iy -SO3 --max-allocs-per-node200000 main.c vt.asm -o main -create-app
(-startup=31 for the new c library selects a crt without stdin,stdout,stderr instantiated which will make your program smaller)
But you also need to load the vt player and data into memory. You can just concatenate taps together:
copy /b main.tap + vt.tap final.tap
Load up final.tap and MERGE it in: MERGE "". Add LOAD""CODE : LOAD""CODE just before the rand usr - this will get the vt stuff loaded into memory before the program is executed. Then run.
We did that because the tap file z88dk is producing knows nothing about the extra stuff you need to load from tape. A better solution is you write the basic loader in a text editor and then convert that to tap with a program called "bas2tap". Then you get zcc to make its output tap without a loader (in this case it will just be CODE bytes), I think adding -Cz--noloader to the compile line will work. Then you can produce the entire final tap with:
copy /b loader.tap + main.tap + vt.tap final.tap
That will load and run on an emulator or the real thing without intervention.
In this program you have to worry about the memory map because z88dk is not in complete control of that. The program is compiled to address 32768 by default. The vt tracker is at 49152 by default and we put a 257-byte im2 table at 0xfd00 = 64768 and a three-byte jump at 0xfefe = 65278. We have to make sure none of that overlaps. Don't forget about the stack. For the new c lib the default is 65368, which is leaving us a little less than 90 bytes which should be ok. You may want to change that to -1 (meaning don't modify the stack location) if you do a CLEAR from basic (which moves the stack underneath the CLEAR) before running the program. You can do that by adding "#pragma output REGISTER_SP = -1" at the top of main.c. You may also need to add "#pragma output CLIB_MALLOC_HEAP_SIZE = 0" if the program won't start (by default the heap is created in the space between the end of the program and the bottom of the stack - if that space is negative the program won't start and also, in this case, that heap would occupy the same space as the vt code and im2 table).
All of these complications come up because z88dk is not in control of the memory map. Player and sound data in asm form would allow z88dk to control placement and then we would only need to worry about the stack and im2 table. Sorry if it scares a bit but if you read through, it should make sense Putting together players for these ay editors is on the list and after that is done, it will be much simpler.
Does any one have any tips (or even code snippets) on how to play some format of AY files using z88dk? This was more difficult than I first thought. I had some naive idea that there would be some kind of standard AY data format that could just feed to an AY player routine but it seems more complicated than that...
There should be but there isn't (yet).
Offline
Thanks Alvin for your detailed answers and explanations! I will have to absorb all your info before coming back with any follow-up questions
Offline
Hi Alvin,
Now I'm able to play Vortex Tracker II modules in the background Thanks for all your help.
I have created a C wrapper for all functions in the Vortex Tracker II (VT) player for ZX Spectrum, see the files vt_sound.h and vt_sound.asm below. If you want to, feel free to include them with z88dk and make any changes as you see fit. I can send you the proper files if you're interested.
"vt_sound.h" /******************************************************************************* * C API for calling the Vortex Tracker II (VT) player. * * The module to be played is created in the following way: * * 1. Load a module into Vortex Tracker II. * 2. Export the module for ZX Spectrum. * 3. Configure the compilation address of the VT player. Make sure that the * memory for the VT player and the VT module doesn't overlap with any memory * used by your program. * 4. Select the format ".TAP-file (player and module separately)". * 5. This will create a TAP file that contains two blocks: the first block * contains the VT player and the second block contains the VT module. * * How to use: * * 1. Concatenate the exported VT TAP file with your program TAP file and add * a custom BASIC loader that, in addition to loading and starting your * program, also loads the blocks for the VT player and VT module. * 2. Update the VT_START definition in vt_sound.asm to the compilation address * of the VT player in your VT TAP file. * 3. Call vt_init() to initialize the VT player with the default module * (i.e. the module exported together with the player) or call * vt_init_module(<module_address>) to initialize the VT player with the * specified module. * 4. Call vt_play() each 1/50 second or install the vt_play_isr() function as * an IM2 interrupt service routine. The module will now start playing in the * background. * * How to create additional separate modules without the VT player: * * 1. Load a module into Vortex Tracker II. * 2. Export the module for ZX Spectrum. * 3. Configure the compilation address of the VT module. Make sure that the * memory for the module doesn't overlap with any memory used by your * program. * 4. Select the format "Hobeta without player". * 5. This will create an Hobeta file that contains only the VT module and no * VT player. * 6. Convert the Hobeta file to a TAP file using the tools from * https://sourceforge.net/projects/zxspectrumutils/. First convert the * Hobeta file to an 000 file using hobto0 and then convert the 000 file * to a TAP file using 0totap. * 7. Now you will have a TAP file that contains the VT module. * 8. Concatenate this VT module TAP file with the rest of your program as * described in step 1 in the "How to use" description above and load it. * 9. Call vt_init_module(<module_address>) to initialize the VT player with * this module. ******************************************************************************/ #ifndef _VT_SOUND_H #define _VT_SOUND_H /* * Returns a pointer to the VT player's setup byte. * Set bit 0 to disable looping; bit 7 is set after each loop. */ extern uint8_t *vt_get_setup(void); /* * Initializes the VT player with the default module (i.e. the module exported * together with the player). * * If this function is called during playing of the module, it mutes the sound * and reinitializes the VT player with the default module to be played from * its beginning. */ extern void vt_init(void); /* * Initializes the VT player with the specified module. * * If this function is called during playing of the module, it mutes the sound * and reinitializes the VT player with the specified module to be played from * its beginning. */ extern void vt_init_module(void *module_address); /* * Plays one snippet of the module. Call this function each 1/50 second to play * the module continuously. If you want to call this function from an interrupt * service routine, you can use the vt_play_isr() function which is tailored to * be used as an ISR. */ extern void vt_play(void); /* * This function is tailored for installation as an IM2 interrupt service * routine to play the module in the background. */ extern void vt_play_isr(void); /* * Enables (enabled = 1) or disables (enabled = 0) the vt_play_isr() interrupt * service routine. The vt_play_isr() interrupt service routine is initially * enabled. */ extern void vt_set_play_isr_enabled(int enabled); /* * Mutes the sound. Call vt_play() to continue playing again. * * If the vt_play_isr() interrupt service routine is used, call * vt_set_play_isr_enabled(0) before calling vt_mute() to mute the sound. * Call vt_set_play_isr_enabled(1) to continue playing again. */ extern void vt_mute(void); /* * Returns a pointer to the VT player's current position pointer word. */ extern uint16_t *vt_get_cur_pos(void); #endif
"vt_sound.asm" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Routines for calling the Vortex Tracker II (VT) player. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SECTION code_user DEFC VT_START = $A000 DEFC VT_INIT = VT_START DEFC VT_INIT_MODULE = VT_START + 3 DEFC VT_PLAY = VT_START + 5 DEFC VT_MUTE = VT_START + 8 DEFC VT_SETUP_BYTE = VT_START + 10 DEFC VT_CUR_POS_WORD = VT_START + 11 PUBLIC _vt_get_setup PUBLIC _vt_init PUBLIC _vt_init_module PUBLIC _vt_play PUBLIC _vt_play_isr PUBLIC _vt_set_play_isr_enabled PUBLIC _vt_mute PUBLIC _vt_get_cur_pos _vt_play_isr_enabled: DEFW 1 _vt_get_setup: ld hl,VT_SETUP_BYTE ret _vt_init: di call VT_INIT ei ret _vt_init_module: ; hl contains module address di call VT_INIT_MODULE ei ret _vt_play: di call VT_PLAY ei ret _vt_play_isr: push af push bc push de push hl ex af,af' exx push af push bc push de push hl push ix push iy ld hl,(_vt_play_isr_enabled) ld a,h or l call nz,VT_PLAY pop iy pop ix pop hl pop de pop bc pop af exx ex af,af' pop hl pop de pop bc pop af ei reti _vt_set_play_isr_enabled: ; hl contains enablement/disablement parameter ld (_vt_play_isr_enabled),hl ret _vt_mute: di call VT_MUTE ei ret _vt_get_cur_pos: ld hl,VT_CUR_POS_WORD ret
I'm not 100% sure about the Z80 assembly details and the C to assembly calling conventions in vt_sound.asm but it works for me using both sccz80 and sdcc. Another thing that I would like to improve is to be able to set the VT_START definition in vt_sound.asm using a pragma instead of editing it directly in the file. However, I haven't yet figured out exactly how pragmas work in z88dk.
I would also like to adapt the VT player assembly source file to be compatible with the assembler in z88dk so that it can be assembled and linked together with the rest of your program without a fixed start address as is needed when using the pre-assembled version exported by the Vortex Tracker II program.
Another potential improvement would be to support pausing a module, e.g. the background music, and temporarily switch to another module to play a sound effect and then switch back to resume playing the background music module. That feature is not supported out of the box by the VT player but I have received some information from its author Sergey Bulba how it could be done.
It would also be great if Vortex Tracker II (and other trackers) would be able to export modules to assembly source files so we could assemble and link them in a more easy and flexible way without any manual address planning.
Regards,
Stefan
Offline
Stefan123 wrote:
Now I'm able to play Vortex Tracker II modules in the background Thanks for all your help.
Good to hear!
I'm not 100% sure about the Z80 assembly details and the C to assembly calling conventions in vt_sound.asm but it works for me using both sccz80 and sdcc.
If the function doesn't take any parameters (as is the case for the functions you posted) then the compiler generates a plain "call" as you would expect and nothing special has to be done. It's only if parameters are passed to the function that the prototype must define how the parameters are passed and the asm function must gather the parameters according to the method specified by the prototype. Return values usually don't require special attention either -- the compiler just expects values returned in a subset of DEHL for the most part. Exceptions to that would be float values and 64-bit values.
Another thing that I would like to improve is to be able to set the VT_START definition in vt_sound.asm using a pragma instead of editing it directly in the file. However, I haven't yet figured out exactly how pragmas work in z88dk.
pragmas are written to the file "zcc_opt.def" while the C source file(s) are compiled. You may have noticed it after compilation was completed. This zcc_opt.def file is included into the crt (the startup code that runs before main is called) during the linking step where definitions are used to select options inside the crt.
The pragma-define pragma that defines a label to hold a constant value does not make the label public which means it is only visible inside the crt. What you would want is for such a label to be exported so that your separate asm file could see it during linking. I can see this as a general need so I've added a new pragma-export pragma that does this. This will be available starting with the Nov 21 build.
So after tonight's build:
"vt_sound.asm" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Routines for calling the Vortex Tracker II (VT) player. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SECTION code_user EXTERN VT_START ;; look for it during linking step DEFC VT_INIT = VT_START DEFC VT_INIT_MODULE = VT_START + 3 ....
Method 1: Define VT_START on compile line
zcc +zx -vn .... -pragma-export:VT_START=0xa000
Method 2: Define VT_START in main.c
"main.c" #pragma export VT_START = 0xa000 #include <stdio.h> void main(void) { ... }
Method 3: Define VT_START in the project's pragma include file
zcc +zx -vn .... -pragma-include:pragma.inc
"pragma.inc" #pragma define CRT_ORG_CODE = 30000 ;; set org to 30000 #pragma define CLIB_OPT_PRINTF = 0x200 ;; printf() supports %s only #pragma export VT_START = 0xa000 ;; VTII music module start (made public)
Offline
Stefan123 wrote:
I would also like to adapt the VT player assembly source file to be compatible with the assembler in z88dk so that it can be assembled and linked together with the rest of your program without a fixed start address as is needed when using the pre-assembled version exported by the Vortex Tracker II program.
Another potential improvement would be to support pausing a module, e.g. the background music, and temporarily switch to another module to play a sound effect and then switch back to resume playing the background music module. That feature is not supported out of the box by the VT player but I have received some information from its author Sergey Bulba how it could be done.
It would also be great if Vortex Tracker II (and other trackers) would be able to export modules to assembly source files so we could assemble and link them in a more easy and flexible way without any manual address planning.
The main problem with these players is that they tend to contain self-modifying code and write to the AY device directly. The smc means the code occupies twice as much space in rom compiles (one copy in ROM, one copy in RAM during execution). The zx has a seldom used if2-cartridge target but we're thinking more in terms of other machines whose primary program storage format is ROM. The fact that the players write directly to the AY device means we can't port the player to other machines without making changes and we can't support more than one AY device (there are machines with more than one AY chip out there).
What we'd like to do is change the players to get rid of the smc and to get them to write the AY state to a small ram buffer. This way the output to the AY device step is just copying the ram buffer to the device where we have control over which AY device is written to and this would also open the opportunity to mix AY output generated from several AY players or sound effects generators.
There are a number of AY generators that we'd like to support:
* VTII's pt player
* Wyz Tracker with sound effect engine
* AYFX for sound effects
* wav2ay - this is a new tool that does a best fit sample to ay at 50 Hz. Can be used for speech synthesis or sound effects
* midi2ay
I've helped someone use the last two in their project with really good results.
Offline
Really nice that you have added the new pragma-export feature. I will definitely try that.
If the function doesn't take any parameters (as is the case for the functions you posted) then the compiler generates a plain "call" as you would expect and nothing special has to be done. It's only if parameters are passed to the function that the prototype must define how the parameters are passed and the asm function must gather the parameters according to the method specified by the prototype. Return values usually don't require special attention either -- the compiler just expects values returned in a subset of DEHL for the most part. Exceptions to that would be float values and 64-bit values.
Actually, two of the functions take a parameter: vt_init_module(void *module_address) and vt_set_play_isr_enabled(int enabled). In both cases I have assumed that the parameter is passed in HL and that seems to work.
There is a version of the VT player on Sergey Bulba's site that doesn't use self-modifying code. When I get the time, I will try to get it to assemble with z88dk's assembler. However, your point that the VT player (and other players) write to the AY chip directly instead of via a memory buffer that can be copied in a platform-dependent way to the AY chip is probably out of my Z80 league.
Good to hear that you plan to support several AY players. Do you have a time frame for that? Let me know if I can help out in any way.
Offline
Stefan123 wrote:
If the function doesn't take any parameters (as is the case for the functions you posted) then the compiler generates a plain "call" as you would expect and nothing special has to be done. It's only if parameters are passed to the function that the prototype must define how the parameters are passed and the asm function must gather the parameters according to the method specified by the prototype. Return values usually don't require special attention either -- the compiler just expects values returned in a subset of DEHL for the most part. Exceptions to that would be float values and 64-bit values.
Actually, two of the functions take a parameter: vt_init_module(void *module_address) and vt_set_play_isr_enabled(int enabled). In both cases I have assumed that the parameter is passed in HL and that seems to work.
It may be that the compiler collects the value into HL shortly before the call but this is not guaranteed behaviour. Especially with sdcc you will probably see instances where HL does not always hold the single parameter since sdcc can generate much more variable code.
A plain single-parameter prototype like this:
void func(int a);
instructs the compiler to pass the parameter via the stack (this is always the case when there are no special attributes in the prototype).
Code generated for the call might be:
ld hl,(_a)
push hl
call _func
pop af
in which case the parameter happens to be in HL.
The correct way to get the compiler to pass a single parameter by register is to use fastcall linkage:
void __FASTCALL__ func(int a); // sccz80
void func(int a) __z88dk_fastcall; // sdcc
It's specified differently for the two compilers. The new c library actually generates different header files for each compiler because there are numerous differences in these sorts of attributes but if you want to accommodate both compilers you can also use pre-processor #if to select based on the active compiler:
#ifdef __SDCC
void func(int) __z88dk_fastcall;
#endif
#ifdef __SCCZ80
void __FASTCALL__ func(int a);
#endif
In fastcall linkage the single parameter will be passed via (DE)HL instead of the stack and the generated call code would look like:
ld hl,(_a)
call _func
(64-bit params cannot use fastcall linkage and how fastcall floats are passed depends on which float library is used. In the new c library the float is 48-bit and passed via BCDEHL' in the exx set)
Calls via function pointers are always done using standard linkage (parameters on the stack) so if the function must also be callable via function pointer you have to provide an entry point to the function using standard linkage (params via stack). In the library we define entry points for standard linkage, fastcall/callee and asm for every function and we use a little macro magic to get the pre-processor to automatically choose between the function pointer's standard linkage and the regular call's fastcall/callee.
extern void memcpy(void *dst, void *src, uint n); // standard linkage, params via stack
extern void memcpy_callee(void *dst, void *src, uint n); // callee linkage, params via stack but called function clears stack
#define memcpy(a,b,c) memcpy_callee(a,b,c)
f = mempy; // macro substitution does not happen because wrong number of params -- function pointer gets standard linkage entry
memcpy(16384, 32768, 6912); // macro changes this to "memcpy_callee(16384, 32768, 6912)" -- regular function calls choose better callee linkage
All this is probably information overload but the quick fix to guarantee that the single parameter is in HL is to use fastcall linkage.
Good to hear that you plan to support several AY players. Do you have a time frame for that? Let me know if I can help out in any way.
It's all about free time
It will probably be time for another z88dk release in late December so I'd like to finish a few things before then:
* finalize terminal & character i/o in the new c lib (introduce non-blocking i/o, add buffered i/o for interrupting devices, more driver base classes)
* finish disk i/o in the new c lib (this will make it easy to support any disk system)
* Ay might be nice and would help with last point which may not happen before next release
* Introduce a method to instantiate hardware devices such that the library can automatically generate drivers for them. So, eg, if you have two AY chips you can say you have one mapped to such and such ports and another to these other ports and then the drivers will automatically be available.
Offline
Stefan123 wrote:
Let me know if I can help out in any way.
If you can rewrite some of these things to get rid of the smc and write ay data into a memory buffer (pointer passed as param) this might speed things up. A while ago when I looked at some of these players, it wasn't a trivial job though. Lots of nasty code was seen
Offline
Alvin, thanks for your info on the different calling conventions.
I will take a look at trying to assemble the non-smc version of the VT player with z88dk's assembler.
Offline
Hi Alvin,
Now I have done some improvements on my C API for calling the Vortex Tracker II (VT) player.
I have adapted the VT player PT3PROM.asm to the z80asm assembler syntax in z88dk. It is based on the file PT3Play/ROM/PT3PROM.asm in http://bulba.untergrund.net/PTxTools.7z, which uses the SjASM assembler syntax. The code section can be executed from RAM or ROM, contains no self-modifying code, and can be assembled at any address. The data section contains the variables and self-modifying code and can be located at any address in RAM. The file generates a few "integer out of range" warnings when assembled that are harmless but I don't know how to get rid of them.
I have also updated the header file vt_sound.h to use fastcall linkage where appropriate.
When working on adapting the VT player, I realized that it plays PT3 modules directly, i.e. there is no need to export them to TAP or Hobeta format and they can be located at any address not only the compilation address specified when doing the export. To play a non-PT3 module, simply open it in Vortex Tracker II and save it as a PT3 module first.
This means that the VT player and the PT3 modules are under the control of the linker (no manual address planning needed nor any custom BASIC loader as in my first attempt). The easiest way to load the PT3 modules is to use a small assembler snippet where you define a read-only data section and load the PT3 modules using the BINARY directive with a public label.
I haven't refactored PT3PROM.asm to introduce an AY device abstraction as you suggested since I, at this point, don't understand the code in PT3PROM.asm well enough to do that. Adding such an abstraction would also mean that the z80asm-adapted PT3PROM.asm may deviate too much from the original version in http://bulba.untergrund.net/PTxTools.7z too make it feasible to add any future bug fixes and improvements done on the original.
Below are the files for the C API wrapper for calling the VT player, the z80asm-adapted VT player, and a simple example program demonstrating how to use all this. If you want to, feel free to include these files with z88dk and make any changes as you see fit. I can send you the proper files if you're interested. We may also need a blessing from Sergey Bulba (svbulba@gmail.com) at http://bulba.untergrund.net/ for including the z80asm-adapted PT3PROM.asm file.
vt_sound.h
/******************************************************************************* * Stefan Bylund 2016 * * C API for calling the Vortex Tracker II (VT) player in PT3PROM.asm. * The VT player supports playing of Pro Tracker 3.x (PT3) modules. * * The module(s) to be played is created in the following way: * * 1. If the module is a PT3 module, it can be played directly by the VT player. * 2. If the module is not a PT3 module but a module in another format supported * by Vortex Tracker II, open the module in Vortex Tracker II and save it as * a PT3 module. * 3. Create an assembler source file for loading the PT3 module(s). * Define a read-only data section and load the PT3 module(s) using the * BINARY directive. Associate each PT3 module with a public label starting * with an underscore, e.g. _music_module. * * How to use: * * 1. Make the PT3 module(s) available to your C program by declaring external * references to their public labels (but without the leading underscore) as * created in step 3 above, e.g. "extern uint8_t music_module[];". * 2. Call vt_init(<module_address>) to initialize the VT player with the * specified module. * 3. Call vt_play() each 1/50 second or install the vt_play_isr() function as * an IM2 interrupt service routine. The module will now start playing in the * background. ******************************************************************************/ #ifndef _VT_SOUND_H #define _VT_SOUND_H #if defined(__SCCZ80) #define __FASTCALL__ __FASTCALL__ #define __SMALLCFASTCALL #elif defined(__SDCC) #define __FASTCALL__ #define __SMALLCFASTCALL __z88dk_fastcall #else #define __FASTCALL__ #define __SMALLCFASTCALL #endif /* * Returns a pointer to the VT player's setup byte. * Set bit 0 to disable looping; bit 7 is set after each loop. */ extern uint8_t *vt_get_setup(void); /* * Initializes the VT player with the specified module. * * If this function is called during playing of the module, it mutes the sound * and reinitializes the VT player with the specified module to be played from * its beginning. */ extern void __FASTCALL__ vt_init(void *module_address) __SMALLCFASTCALL; /* * Plays one snippet of the module. Call this function each 1/50 second to play * the module continuously. If you want to call this function from an interrupt * service routine, you can use the vt_play_isr() function which is tailored to * be used as an ISR. */ extern void vt_play(void); /* * This function is tailored for installation as an IM2 interrupt service * routine to play the module in the background. */ extern void vt_play_isr(void); /* * Enables (enabled = 1) or disables (enabled = 0) the vt_play_isr() interrupt * service routine. The vt_play_isr() interrupt service routine is initially * enabled. */ extern void __FASTCALL__ vt_set_play_isr_enabled(int enabled) __SMALLCFASTCALL; /* * Mutes the sound. Call vt_play() to continue playing again. * * If the vt_play_isr() interrupt service routine is used, call * vt_set_play_isr_enabled(0) before calling vt_mute() to mute the sound. * Call vt_set_play_isr_enabled(1) to continue playing again. */ extern void vt_mute(void); /* * Returns a pointer to the VT player's current position pointer word. */ extern uint16_t *vt_get_cur_pos(void); #endif
vt_sound.asm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Stefan Bylund 2016 ;; ;; Routines for calling the Vortex Tracker II (VT) player in PT3PROM.asm. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SECTION code EXTERN VT_START DEFC VT_INIT = VT_START + 3 DEFC VT_PLAY = VT_START + 5 DEFC VT_MUTE = VT_START + 8 DEFC VT_SETUP_BYTE = VT_START + 10 DEFC VT_CUR_POS_WORD = VT_START + 11 PUBLIC _vt_get_setup PUBLIC _vt_init PUBLIC _vt_play PUBLIC _vt_play_isr PUBLIC _vt_set_play_isr_enabled PUBLIC _vt_mute PUBLIC _vt_get_cur_pos _vt_get_setup: ld hl,VT_SETUP_BYTE ret _vt_init: ; hl contains module address di call VT_INIT ei ret _vt_play: di call VT_PLAY ei ret _vt_play_isr: push af push bc push de push hl ex af,af' exx push af push bc push de push hl push ix push iy ld hl,(_vt_play_isr_enabled) ld a,h or l call nz,VT_PLAY pop iy pop ix pop hl pop de pop bc pop af exx ex af,af' pop hl pop de pop bc pop af ei reti _vt_set_play_isr_enabled: ; hl contains enablement/disablement parameter ld (_vt_play_isr_enabled),hl ret _vt_mute: di call VT_MUTE ei ret _vt_get_cur_pos: ld hl,VT_CUR_POS_WORD ret SECTION data _vt_play_isr_enabled: DEFW 1
PT3PROM.asm
;Vortex Tracker II v1.0 PT3 player for ZX Spectrum ;ROM version (specially for Axor) ;(c)2004,2007 S.V.Bulba <vorobey@mail.khstu.ru> ;http://bulba.untergrund.net (http://bulba.at.kz) ;Release number ;DEFM Release = "MOR7" ;This file has been adapted to the z80asm assembler in z88dk and is based on the ;file PT3Play/ROM/PT3PROM.asm in http://bulba.untergrund.net/PTxTools.7z, which ;uses the SjASM assembler syntax. The code section can be executed from RAM or ;ROM, contains no self-modifying code, and can be assembled at any address. ;The data section contains the variables and self-modifying code and can be ;located at any address in RAM. ;Features ;-------- ;-Can run in ROM (self-modified code is not used). ;-Can be compiled at any address (i.e. no need rounding ORG ; address). ;-Variables (VARS) can be located at any address (not only after ;code block). ;-INIT subroutine detects module version and rightly generates ; both note and volume tables outside of code block (in VARS). ;-Two portamento (spc. command 3xxx) algorithms (depending of ; module version). ;-New 1.XX and 2.XX special command behaviour (only for PT v3.7 ; and higher). ;-Any Tempo value are accepted (including Tempo=1 and Tempo=2). ;-Fully compatible with Ay_Emul PT3 player codes. ;-See also notes at the end of this source code. ;Warning!!! PLAY subroutine can crash if no module are loaded ;into RAM or INIT subroutine was not called before. ;Call MUTE or INIT one more time to mute sound after stopping ;playing SECTION code ; ORG $C000 ;Test codes (commented) ; CALL START ; EI ;_LP: ; HALT ; CALL START+5 ; XOR A ; IN A,($FE) ; CPL ; AND 15 ; JR Z,_LP ; JR START+8 DEFC TonA = 0 DEFC TonB = 2 DEFC TonC = 4 DEFC Noise = 6 DEFC Mixer = 7 DEFC AmplA = 8 DEFC AmplB = 9 DEFC AmplC = 10 DEFC Env = 11 DEFC EnvTp = 13 ;ChannelsVars ; STRUCT CHP ;reset group DEFC CHP_PsInOr = 0 DEFC CHP_PsInSm = 1 DEFC CHP_CrAmSl = 2 DEFC CHP_CrNsSl = 3 DEFC CHP_CrEnSl = 4 DEFC CHP_TSlCnt = 5 DEFC CHP_CrTnSl = 6 DEFC CHP_TnAcc = 8 DEFC CHP_COnOff = 10 ;reset group DEFC CHP_OnOffD = 11 ;IX for PTDECOD here (+12) DEFC CHP_OffOnD = 12 DEFC CHP_OrnPtr = 13 DEFC CHP_SamPtr = 15 DEFC CHP_NNtSkp = 17 DEFC CHP_Note = 18 DEFC CHP_SlToNt = 19 DEFC CHP_Env_En = 20 DEFC CHP_Flags = 21 ;Enabled - 0,SimpleGliss - 2 DEFC CHP_TnSlDl = 22 DEFC CHP_TSlStp = 23 DEFC CHP_TnDelt = 25 DEFC CHP_NtSkCn = 27 DEFC CHP_Volume = 28 ; ENDS DEFC CHP = 29 ;Entry and other points ;START initialization ;START+3 initialization with module address in HL ;START+5 play one quark ;START+8 mute ;START+10 setup and status flags ;START+11 pointer to current position value in PT3 module; ;After INIT (START+11) points to Postion0-1 (optimization) PUBLIC VT_START VT_START: START: LD HL,MDLADDR JR INIT JP PLAY JR MUTE ;Identifier DEFM "=VTII PT3 Player r.7ROM=" CHECKLP: LD HL,SETUP SET 7,(HL) BIT 0,(HL) RET Z POP HL LD HL,DelyCnt INC (HL) LD HL,ChanA+CHP_NtSkCn INC (HL) MUTE: XOR A LD H,A LD L,A LD (AYREGS+AmplA),A LD (AYREGS+AmplB),HL JP ROUT_A0 INIT: ;HL - AddressOfModule LD (MODADDR),HL PUSH HL LD DE,100 ADD HL,DE LD A,(HL) LD (Delay),A PUSH HL POP IX ADD HL,DE LD (CrPsPtr),HL LD E,(IX+102-100) ADD HL,DE INC HL LD (LPosPtr),HL POP DE LD L,(IX+103-100) LD H,(IX+104-100) ADD HL,DE LD (PatsPtr),HL LD HL,169 ADD HL,DE LD (OrnPtrs),HL LD HL,105 ADD HL,DE LD (SamPtrs),HL LD HL,SETUP RES 7,(HL) ;note table data depacker LD DE,T_PACK LD BC,T1_+(2*49)-1 TP_0: LD A,(DE) INC DE CP 15*2 JR NC,TP_1 LD H,A LD A,(DE) LD L,A INC DE JR TP_2 TP_1: PUSH DE LD D,0 LD E,A ADD HL,DE ADD HL,DE POP DE TP_2: LD A,H LD (BC),A DEC BC LD A,L LD (BC),A DEC BC SUB $F8*2 JR NZ,TP_0 LD HL,VAR0START LD (HL),A LD DE,VAR0START+1 LD BC,VAR0END-VAR0START-1 LDIR INC A LD (DelyCnt),A LD HL,$F001 ;H - CHP_Volume, L - CHP_NtSkCn LD (ChanA+CHP_NtSkCn),HL LD (ChanB+CHP_NtSkCn),HL LD (ChanC+CHP_NtSkCn),HL LD HL,EMPTYSAMORN LD (AdInPtA),HL ;ptr to zero LD (ChanA+CHP_OrnPtr),HL ;ornament 0 is "0,1,0" LD (ChanB+CHP_OrnPtr),HL ;in all versions from LD (ChanC+CHP_OrnPtr),HL ;3.xx to 3.6x and VTII LD (ChanA+CHP_SamPtr),HL ;S1 There is no default LD (ChanB+CHP_SamPtr),HL ;S2 sample in PT3, so, you LD (ChanC+CHP_SamPtr),HL ;S3 can comment S1,2,3; see ;also EMPTYSAMORN comment LD A,(IX+13-100) ;EXTRACT VERSION NUMBER SUB $30 JR C,L20 CP 10 JR C,L21 L20: LD A,6 L21: LD (Version),A PUSH AF CP 4 LD A,(IX+99-100) ;TONE TABLE NUMBER RLA AND 7 ;NoteTableCreator (c) Ivan Roshin ;A - NoteTableNumber*2+VersionForNoteTable ;(xx1b - 3.xx..3.4r, xx0b - 3.4x..3.6x..VTII1.0) LD HL,NT_DATA PUSH DE LD D,B ADD A,A LD E,A ADD HL,DE LD E,(HL) INC HL SRL E SBC A,A AND $A7 ;$00 (NOP) or $A7 (AND A) LD (L3),A LD A,201 ;RET temporary LD (L3+1),A ;temporary EX DE,HL POP BC ;BC=T1_ ADD HL,BC LD A,(DE) ADD A,T_ LD C,A ADC A,T_/256 SUB C LD B,A PUSH BC LD DE,NT_ PUSH DE LD B,12 L1: PUSH BC LD C,(HL) INC HL PUSH HL LD B,(HL) PUSH DE EX DE,HL LD DE,23 LD IXH,8 L2: SRL B RR C CALL L3 ;temporary ;L3: ; DB $19 ;AND A or NOP LD A,C ADC A,D ;=ADC 0 LD (HL),A INC HL LD A,B ADC A,D LD (HL),A ADD HL,DE DEC IXH JR NZ,L2 POP DE INC DE INC DE POP HL INC HL POP BC DJNZ L1 POP HL POP DE LD A,E CP TCOLD_1 JR NZ,CORR_1 LD A,$FD LD (NT_+$2E),A CORR_1: LD A,(DE) AND A JR Z,TC_EXIT RRA PUSH AF ADD A,A LD C,A ADD HL,BC POP AF JR NC,CORR_2 DEC (HL) DEC (HL) CORR_2: INC (HL) AND A SBC HL,BC INC DE JR CORR_1 TC_EXIT: POP AF ;VolTableCreator (c) Ivan Roshin ;A - VersionForVolumeTable (0..4 - 3.xx..3.4x; ;5.. - 3.5x..3.6x..VTII1.0) CP 5 LD HL,$11 LD D,H LD E,H LD A,$17 JR NC,M1 DEC L LD E,L XOR A M1: LD (M2),A LD IX,VT_+16 LD C,$10 INITV2: PUSH HL ADD HL,DE EX DE,HL SBC HL,HL INITV1: LD A,L ;M2: ; DB $7D CALL M2 ;temporary LD A,H ADC A,0 LD (IX),A INC IX ADD HL,DE INC C LD A,C AND 15 JR NZ,INITV1 POP HL LD A,E CP $77 JR NZ,M3 INC E M3: LD A,C AND A JR NZ,INITV2 JP ROUT_A0 ;pattern decoder PD_OrSm: LD (IX-12+CHP_Env_En),0 CALL SETORN LD A,(BC) INC BC RRCA PD_SAM: ADD A,A PD_SAM_: LD E,A LD D,0 ;DEFC SamPtrs = ASMPC+1 ; LD HL,$2121 LD HL,(SamPtrs) ADD HL,DE LD E,(HL) INC HL LD D,(HL) ;DEFC MODADDR = ASMPC+1 ; LD HL,$2121 LD HL,(MODADDR) ADD HL,DE LD (IX-12+CHP_SamPtr),L LD (IX-12+CHP_SamPtr+1),H JR PD_LOOP PD_VOL: RLCA RLCA RLCA RLCA LD (IX-12+CHP_Volume),A JR PD_LP2 PD_EOff: LD (IX-12+CHP_Env_En),A LD (IX-12+CHP_PsInOr),A JR PD_LP2 PD_SorE: DEC A JR NZ,PD_ENV LD A,(BC) INC BC LD (IX-12+CHP_NNtSkp),A JR PD_LP2 PD_ENV: CALL SETENV JR PD_LP2 PD_ORN: CALL SETORN JR PD_LOOP PD_ESAM: LD (IX-12+CHP_Env_En),A LD (IX-12+CHP_PsInOr),A CALL NZ,SETENV LD A,(BC) INC BC JR PD_SAM_ PTDECOD: LD A,(IX-12+CHP_Note) ; LD (PrNote+1),A LD (PrNote),A LD L,(IX-12+CHP_CrTnSl) LD H,(IX-12+CHP_CrTnSl+1) ; LD (PrSlide+1),HL LD (PrSlide),HL PD_LOOP: LD DE,$2010 PD_LP2: LD A,(BC) INC BC ADD A,E JR C,PD_OrSm ADD A,D JR Z,PD_FIN JR C,PD_SAM ADD A,E JR Z,PD_REL JR C,PD_VOL ADD A,E JR Z,PD_EOff JR C,PD_SorE ADD A,96 JR C,PD_NOTE ADD A,E JR C,PD_ORN ADD A,D JR C,PD_NOIS ADD A,E JR C,PD_ESAM ADD A,A LD E,A LD HL,SPCCOMS+$FF20-$2000 ADD HL,DE LD E,(HL) INC HL LD D,(HL) PUSH DE JR PD_LOOP PD_NOIS: LD (Ns_Base),A JR PD_LP2 PD_REL: RES 0,(IX-12+CHP_Flags) JR PD_RES PD_NOTE: LD (IX-12+CHP_Note),A SET 0,(IX-12+CHP_Flags) XOR A PD_RES: ; LD (PDSP_+1),SP LD (PDSP_),SP LD SP,IX LD H,A LD L,A PUSH HL PUSH HL PUSH HL PUSH HL PUSH HL PUSH HL ;PDSP_: ; LD SP,$3131 LD SP,(PDSP_) PD_FIN: LD A,(IX-12+CHP_NNtSkp) LD (IX-12+CHP_NtSkCn),A RET C_PORTM: RES 2,(IX-12+CHP_Flags) LD A,(BC) INC BC ;SKIP PRECALCULATED TONE DELTA (BECAUSE ;CANNOT BE RIGHT AFTER PT3 COMPILATION) INC BC INC BC LD (IX-12+CHP_TnSlDl),A LD (IX-12+CHP_TSlCnt),A LD DE,NT_ LD A,(IX-12+CHP_Note) LD (IX-12+CHP_SlToNt),A ADD A,A LD L,A LD H,0 ADD HL,DE LD A,(HL) INC HL LD H,(HL) LD L,A PUSH HL ;PrNote: ; LD A,$3E LD A,(PrNote) LD (IX-12+CHP_Note),A ADD A,A LD L,A LD H,0 ADD HL,DE LD E,(HL) INC HL LD D,(HL) POP HL SBC HL,DE LD (IX-12+CHP_TnDelt),L LD (IX-12+CHP_TnDelt+1),H LD E,(IX-12+CHP_CrTnSl) LD D,(IX-12+CHP_CrTnSl+1) ;DEFC Version = ASMPC+1 ; LD A,$3E LD A,(Version) CP 6 JR C,OLDPRTM ;Old 3xxx for PT v3.5- ;PrSlide: ; LD DE,$1111 LD DE,(PrSlide) LD (IX-12+CHP_CrTnSl),E LD (IX-12+CHP_CrTnSl+1),D OLDPRTM: LD A,(BC) ;SIGNED TONE STEP INC BC EX AF,AF' LD A,(BC) INC BC AND A JR Z,NOSIG EX DE,HL NOSIG: SBC HL,DE JP P,SET_STP CPL EX AF,AF' NEG EX AF,AF' SET_STP: LD (IX-12+CHP_TSlStp+1),A EX AF,AF' LD (IX-12+CHP_TSlStp),A LD (IX-12+CHP_COnOff),0 RET C_GLISS: SET 2,(IX-12+CHP_Flags) LD A,(BC) INC BC LD (IX-12+CHP_TnSlDl),A AND A JR NZ,GL36 LD A,(Version) ;AlCo PT3.7+ CP 7 SBC A,A INC A GL36: LD (IX-12+CHP_TSlCnt),A LD A,(BC) INC BC EX AF,AF' LD A,(BC) INC BC JR SET_STP C_SMPOS: LD A,(BC) INC BC LD (IX-12+CHP_PsInSm),A RET C_ORPOS: LD A,(BC) INC BC LD (IX-12+CHP_PsInOr),A RET C_VIBRT: LD A,(BC) INC BC LD (IX-12+CHP_OnOffD),A LD (IX-12+CHP_COnOff),A LD A,(BC) INC BC LD (IX-12+CHP_OffOnD),A XOR A LD (IX-12+CHP_TSlCnt),A LD (IX-12+CHP_CrTnSl),A LD (IX-12+CHP_CrTnSl+1),A RET C_ENGLS: LD A,(BC) INC BC LD (Env_Del),A LD (CurEDel),A LD A,(BC) INC BC LD L,A LD A,(BC) INC BC LD H,A LD (ESldAdd),HL RET C_DELAY: LD A,(BC) INC BC LD (Delay),A RET SETENV: LD (IX-12+CHP_Env_En),E LD (AYREGS+EnvTp),A LD A,(BC) INC BC LD H,A LD A,(BC) INC BC LD L,A LD (EnvBase),HL XOR A LD (IX-12+CHP_PsInOr),A LD (CurEDel),A LD H,A LD L,A LD (CurESld),HL C_NOP: RET SETORN: ADD A,A LD E,A LD D,0 LD (IX-12+CHP_PsInOr),D ;DEFC OrnPtrs = ASMPC+1 ; LD HL,$2121 LD HL,(OrnPtrs) ADD HL,DE LD E,(HL) INC HL LD D,(HL) ;DEFC MDADDR2 = ASMPC+1 ; LD HL,$2121 LD HL,(MODADDR) ADD HL,DE LD (IX-12+CHP_OrnPtr),L LD (IX-12+CHP_OrnPtr+1),H RET ;ALL 16 ADDRESSES TO PROTECT FROM BROKEN PT3 MODULES SPCCOMS: DEFW C_NOP DEFW C_GLISS DEFW C_PORTM DEFW C_SMPOS DEFW C_ORPOS DEFW C_VIBRT DEFW C_NOP DEFW C_NOP DEFW C_ENGLS DEFW C_DELAY DEFW C_NOP DEFW C_NOP DEFW C_NOP DEFW C_NOP DEFW C_NOP DEFW C_NOP CHREGS: XOR A LD (Ampl),A BIT 0,(IX+CHP_Flags) PUSH HL JP Z,CH_EXIT ; LD (CSP_+1),SP LD (CSP_),SP LD L,(IX+CHP_OrnPtr) LD H,(IX+CHP_OrnPtr+1) LD SP,HL POP DE LD H,A LD A,(IX+CHP_PsInOr) LD L,A ADD HL,SP INC A CP D JR C,CH_ORPS LD A,E CH_ORPS: LD (IX+CHP_PsInOr),A LD A,(IX+CHP_Note) ADD A,(HL) JP P,CH_NTP XOR A CH_NTP: CP 96 JR C,CH_NOK LD A,95 CH_NOK: ADD A,A EX AF,AF' LD L,(IX+CHP_SamPtr) LD H,(IX+CHP_SamPtr+1) LD SP,HL POP DE LD H,0 LD A,(IX+CHP_PsInSm) LD B,A ADD A,A ADD A,A LD L,A ADD HL,SP LD SP,HL LD A,B INC A CP D JR C,CH_SMPS LD A,E CH_SMPS: LD (IX+CHP_PsInSm),A POP BC POP HL LD E,(IX+CHP_TnAcc) LD D,(IX+CHP_TnAcc+1) ADD HL,DE BIT 6,B JR Z,CH_NOAC LD (IX+CHP_TnAcc),L LD (IX+CHP_TnAcc+1),H CH_NOAC: EX DE,HL EX AF,AF' LD L,A LD H,0 LD SP,NT_ ADD HL,SP LD SP,HL POP HL ADD HL,DE LD E,(IX+CHP_CrTnSl) LD D,(IX+CHP_CrTnSl+1) ADD HL,DE ;CSP_: ; LD SP,$3131 LD SP,(CSP_) EX (SP),HL XOR A OR (IX+CHP_TSlCnt) JR Z,CH_AMP DEC (IX+CHP_TSlCnt) JR NZ,CH_AMP LD A,(IX+CHP_TnSlDl) LD (IX+CHP_TSlCnt),A LD L,(IX+CHP_TSlStp) LD H,(IX+CHP_TSlStp+1) LD A,H ADD HL,DE LD (IX+CHP_CrTnSl),L LD (IX+CHP_CrTnSl+1),H BIT 2,(IX+CHP_Flags) JR NZ,CH_AMP LD E,(IX+CHP_TnDelt) LD D,(IX+CHP_TnDelt+1) AND A JR Z,CH_STPP EX DE,HL CH_STPP: SBC HL,DE JP M,CH_AMP LD A,(IX+CHP_SlToNt) LD (IX+CHP_Note),A XOR A LD (IX+CHP_TSlCnt),A LD (IX+CHP_CrTnSl),A LD (IX+CHP_CrTnSl+1),A CH_AMP: LD A,(IX+CHP_CrAmSl) BIT 7,C JR Z,CH_NOAM BIT 6,C JR Z,CH_AMIN CP 15 JR Z,CH_NOAM INC A JR CH_SVAM CH_AMIN: CP -15 JR Z,CH_NOAM DEC A CH_SVAM: LD (IX+CHP_CrAmSl),A CH_NOAM: LD L,A LD A,B AND 15 ADD A,L JP P,CH_APOS XOR A CH_APOS: CP 16 JR C,CH_VOL LD A,15 CH_VOL: OR (IX+CHP_Volume) LD L,A LD H,0 LD DE,VT_ ADD HL,DE LD A,(HL) CH_ENV: BIT 0,C JR NZ,CH_NOEN OR (IX+CHP_Env_En) CH_NOEN: LD (Ampl),A BIT 7,B LD A,C JR Z,NO_ENSL RLA RLA SRA A SRA A SRA A ADD A,(IX+CHP_CrEnSl) ;SEE COMMENT BELOW BIT 5,B JR Z,NO_ENAC LD (IX+CHP_CrEnSl),A NO_ENAC: LD HL,AddToEn ADD A,(HL) ;BUG IN PT3 - NEED WORD HERE. ;FIX IT IN NEXT VERSION? LD (HL),A JR CH_MIX NO_ENSL: RRA ADD A,(IX+CHP_CrNsSl) LD (AddToNs),A BIT 5,B JR Z,CH_MIX LD (IX+CHP_CrNsSl),A CH_MIX: LD A,B RRA AND $48 CH_EXIT: LD HL,AYREGS+Mixer OR (HL) RRCA LD (HL),A POP HL XOR A OR (IX+CHP_COnOff) RET Z DEC (IX+CHP_COnOff) RET NZ XOR (IX+CHP_Flags) LD (IX+CHP_Flags),A RRA LD A,(IX+CHP_OnOffD) JR C,CH_ONDL LD A,(IX+CHP_OffOnD) CH_ONDL: LD (IX+CHP_COnOff),A RET PLAY: XOR A LD (AddToEn),A LD (AYREGS+Mixer),A DEC A LD (AYREGS+EnvTp),A LD HL,DelyCnt DEC (HL) JP NZ,PL2 LD HL,ChanA+CHP_NtSkCn DEC (HL) JR NZ,PL1B ;DEFC AdInPtA = ASMPC+1 ; LD BC,$0101 LD BC,(AdInPtA) LD A,(BC) AND A JR NZ,PL1A LD D,A LD (Ns_Base),A LD HL,(CrPsPtr) INC HL LD A,(HL) INC A JR NZ,PLNLP CALL CHECKLP ;DEFC LPosPtr = ASMPC+1 ; LD HL,$2121 LD HL,(LPosPtr) LD A,(HL) INC A PLNLP: LD (CrPsPtr),HL DEC A ADD A,A LD E,A RL D ;DEFC PatsPtr = ASMPC+1 ; LD HL,$2121 LD HL,(PatsPtr) ADD HL,DE LD DE,(MODADDR) ; LD (PSP_+1),SP LD (PSP_),SP LD SP,HL POP HL ADD HL,DE LD B,H LD C,L POP HL ADD HL,DE LD (AdInPtB),HL POP HL ADD HL,DE LD (AdInPtC),HL ;PSP_: ; LD SP,$3131 LD SP,(PSP_) PL1A: LD IX,ChanA+12 CALL PTDECOD LD (AdInPtA),BC PL1B: LD HL,ChanB+CHP_NtSkCn DEC (HL) JR NZ,PL1C LD IX,ChanB+12 ;DEFC AdInPtB = ASMPC+1 ; LD BC,$0101 LD BC,(AdInPtB) CALL PTDECOD LD (AdInPtB),BC PL1C: LD HL,ChanC+CHP_NtSkCn DEC (HL) JR NZ,PL1D LD IX,ChanC+12 ;DEFC AdInPtC = ASMPC+1 ; LD BC,$0101 LD BC,(AdInPtC) CALL PTDECOD LD (AdInPtC),BC ;DEFC Delay = ASMPC+1 PL1D: ; LD A,$3E LD A,(Delay) LD (DelyCnt),A PL2: LD IX,ChanA LD HL,(AYREGS+TonA) CALL CHREGS LD (AYREGS+TonA),HL LD A,(Ampl) LD (AYREGS+AmplA),A LD IX,ChanB LD HL,(AYREGS+TonB) CALL CHREGS LD (AYREGS+TonB),HL LD A,(Ampl) LD (AYREGS+AmplB),A LD IX,ChanC LD HL,(AYREGS+TonC) CALL CHREGS ; LD A,(Ampl) ;Ampl = AYREGS+AmplC ; LD (AYREGS+AmplC),A LD (AYREGS+TonC),HL LD HL,(Ns_Base_AddToNs) LD A,H ADD A,L LD (AYREGS+Noise),A ;DEFC AddToEn = ASMPC+1 ; LD A,$3E LD A,(AddToEn) LD E,A ADD A,A SBC A,A LD D,A LD HL,(EnvBase) ADD HL,DE LD DE,(CurESld) ADD HL,DE LD (AYREGS+Env),HL XOR A LD HL,CurEDel OR (HL) JR Z,ROUT_A0 DEC (HL) JR NZ,ROUT ;DEFC Env_Del = ASMPC+1 ; LD A,$3E LD A,(Env_Del) LD (HL),A ;DEFC ESldAdd = ASMPC+1 ; LD HL,$2121 LD HL,(ESldAdd) ADD HL,DE LD (CurESld),HL ROUT: XOR A ROUT_A0: LD DE,$FFBF LD BC,$FFFD LD HL,AYREGS LOUT: OUT (C),A LD B,E OUTI LD B,D INC A CP 13 JR NZ,LOUT OUT (C),A LD A,(HL) AND A RET M LD B,E OUT (C),A RET NT_DATA: DEFB (T_NEW_0-T1_)*2 DEFB TCNEW_0-T_ DEFB (T_OLD_0-T1_)*2+1 DEFB TCOLD_0-T_ DEFB (T_NEW_1-T1_)*2+1 DEFB TCNEW_1-T_ DEFB (T_OLD_1-T1_)*2+1 DEFB TCOLD_1-T_ DEFB (T_NEW_2-T1_)*2 DEFB TCNEW_2-T_ DEFB (T_OLD_2-T1_)*2 DEFB TCOLD_2-T_ DEFB (T_NEW_3-T1_)*2 DEFB TCNEW_3-T_ DEFB (T_OLD_3-T1_)*2 DEFB TCOLD_3-T_ T_: TCOLD_0: DEFB $00+1,$04+1,$08+1,$0A+1,$0C+1,$0E+1,$12+1,$14+1 DEFB $18+1,$24+1,$3C+1,0 TCOLD_1: DEFB $5C+1,0 TCOLD_2: DEFB $30+1,$36+1,$4C+1,$52+1,$5E+1,$70+1,$82,$8C,$9C DEFB $9E,$A0,$A6,$A8,$AA,$AC,$AE,$AE,0 TCNEW_3: DEFB $56+1 TCOLD_3: DEFB $1E+1,$22+1,$24+1,$28+1,$2C+1,$2E+1,$32+1,$BE+1,0 TCNEW_0: DEFB $1C+1,$20+1,$22+1,$26+1,$2A+1,$2C+1,$30+1,$54+1 DEFB $BC+1,$BE+1,0 DEFC TCNEW_1 = TCOLD_1 TCNEW_2: DEFB $1A+1,$20+1,$24+1,$28+1,$2A+1,$3A+1,$4C+1,$5E+1 DEFB $BA+1,$BC+1,$BE+1,0 DEFC EMPTYSAMORN = ASMPC-1 DEFB 1,0,$90 ;delete $90 if you don't need default sample ;first 12 values of tone tables (packed) T_PACK: DEFB $06EC*2/256,$06EC*2 DEFB $0755-$06EC DEFB $07C5-$0755 DEFB $083B-$07C5 DEFB $08B8-$083B DEFB $093D-$08B8 DEFB $09CA-$093D DEFB $0A5F-$09CA DEFB $0AFC-$0A5F DEFB $0BA4-$0AFC DEFB $0C55-$0BA4 DEFB $0D10-$0C55 DEFB $066D*2/256,$066D*2 DEFB $06CF-$066D DEFB $0737-$06CF DEFB $07A4-$0737 DEFB $0819-$07A4 DEFB $0894-$0819 DEFB $0917-$0894 DEFB $09A1-$0917 DEFB $0A33-$09A1 DEFB $0ACF-$0A33 DEFB $0B73-$0ACF DEFB $0C22-$0B73 DEFB $0CDA-$0C22 DEFB $0704*2/256,$0704*2 DEFB $076E-$0704 DEFB $07E0-$076E DEFB $0858-$07E0 DEFB $08D6-$0858 DEFB $095C-$08D6 DEFB $09EC-$095C DEFB $0A82-$09EC DEFB $0B22-$0A82 DEFB $0BCC-$0B22 DEFB $0C80-$0BCC DEFB $0D3E-$0C80 DEFB $07E0*2/256,$07E0*2 DEFB $0858-$07E0 DEFB $08E0-$0858 DEFB $0960-$08E0 DEFB $09F0-$0960 DEFB $0A88-$09F0 DEFB $0B28-$0A88 DEFB $0BD8-$0B28 DEFB $0C80-$0BD8 DEFB $0D60-$0C80 DEFB $0E10-$0D60 DEFB $0EF8-$0E10 SECTION data ;vars from here can be stripped ;you can move VARS to any other address VARS: ;vars in code and other self-modified code moved here ;(for ROM and RAM separation) SETUP: DEFB 0 ;set bit0 to 1, if you want to play without looping ;bit7 is set each time, when loop point is passed CrPsPtr: DEFW 0 AddToEn: DEFB 0 AdInPtA: DEFW 0 AdInPtB: DEFW 0 AdInPtC: DEFW 0 Env_Del: DEFB 0 MODADDR: DEFW 0 ESldAdd: DEFW 0 Delay: DEFB 0 PDSP_: CSP_: PSP_: DEFW 0 SamPtrs: DEFW 0 OrnPtrs: DEFW 0 PatsPtr: DEFW 0 LPosPtr: DEFW 0 L3: M2: PrSlide: DEFW 0 PrNote: DEFB 0 Version: DEFB 0 ;end of moved vars and self-modified code VAR0START: ;START of INITZERO area ChanA: DEFS CHP ChanB: DEFS CHP ChanC: DEFS CHP ;GlobalVars DelyCnt: DEFB 0 CurESld: DEFW 0 CurEDel: DEFB 0 Ns_Base_AddToNs: Ns_Base: DEFB 0 AddToNs: DEFB 0 AYREGS: VT_: DEFS 256 ;CreatedVolumeTableAddress DEFC EnvBase = VT_+14 DEFC T1_ = VT_+16 ;Tone tables data depacked here DEFC T_OLD_1 = T1_ DEFC T_OLD_2 = T_OLD_1+24 DEFC T_OLD_3 = T_OLD_2+24 DEFC T_OLD_0 = T_OLD_3+2 DEFC T_NEW_0 = T_OLD_0 DEFC T_NEW_1 = T_OLD_1 DEFC T_NEW_2 = T_NEW_0+24 DEFC T_NEW_3 = T_OLD_3 NT_: DEFS 192 ;CreatedNoteTableAddress ;local var DEFC Ampl = AYREGS+AmplC DEFC VAR0END = VT_+16 ;INIT zeroes from VARS to VAR0END-1 DEFC VARSEND = ASMPC DEFC MDLADDR = ASMPC ;Release 0 steps: ;11.Sep.2004 - Note tables creator ;12.Sep.2004 - Volume tables creator; INIT subroutine ;13.Sep.2004 - Play counters, position counters ;14.Sep.2004 - Patterns decoder subroutine ;15.Sep.2004 - Resting (no code) ;16.Sep.2004 - CHREGS subroutine; global debugging; 1st stable ;version was born ;17.Sep.2004 - Debugging and optimization. First release! ;Release 1 steps: ;20.Sep.2004 - local vars moved to code (selfmodified code ;smaller and faster) ;22.Sep.2004 - added mute sound entry at START+8; position ;pointer moved to START+11; added setup and status byte at ;START+10 noloop mode and loop passed flags added ;Release 2 steps: ;28.Sep.2004 - Optimization: code around CHREGS's volume and ;vibrato faster now; zeroing PD_RES through stack; Ton and Ampl ;moved from channel vars to global ones; first position selector ;removed from INIT; optimization for packers(Ivan Roshin method) ;Release 3 steps: ;2.Oct.2004 - optimization in INIT and PD_LOOP (thanks to Ivan ;Roshin) ;4.Oct.2004 - load delay from (hl) in INIT (2 bytes shorter) ;5.Oct.2004 - optimization in PD_LOOP (->PD_LP2) ;7.Oct.2004 - swaping some commands for better packing ;Release 4 steps: ;9.Oct.2004 - optimization around LD HL,SPCCOMS (thanks to Ivan ;Roshin); in PTDECOD swapped BC and DE to optimize C_PORTM; ;removed sam and orn len and loop channel vars; CHREGS totally ;optimized ;Release 5 steps: ;11.Oct.2004 - PD_OrSm and C_PORTM optimized; Ivan Roshin's ;volume tables creator algorithm (51 bytes shorter than mine) ;12.Oct.2004 - Ivan Roshin's note tables creator algorithm (74 ;bytes shorter than mine) ;Release 6 steps: ;14.Oct.2004 - loop and next position calculations moved to INIT ;15.Oct.2004 - AdInPt moved to code ;19.Oct.2004 - Env_Del moved to code ;20.Oct.2004 - Version PUSH and POP (1 byte shorter, thanks to ;Ivan Roshin) ;22.Oct.2004 - Env_En moved from Flags' bit to byte (shorter and ;faster code) ;25.Oct.2004 - SETENV optimized ;29.Oct.2004 - Optimized around AddToEn (SBC A,A, thanks to Ivan ;Roshin) ;3.Nov.2004 - Note tables data was compressed; with depacker it ;is 9 bytes shorter than uncompressed (thanks to Ivan Roshin) ;4.Nov.2004 - default sample and ornament both are fixed now ;and placed into code block (6 bytes shorter) ;7.Nov.2004 - LD A,(Ns_Base):LD L,A changed to LD HL,(Ns_Base) ;(thanks to Dima Bystrov) ;9.Nov.2004 - Ns_Base and AddToNs are merged to Ns_Base_AddToNs; ;LD A,255 changed to DEC A (at start of PLAY); added ROUT_A0 ;12.Nov.2004 - NtSkCn&Volume are merged (8 bytes smaller init); ;LD BC,T1_ changed to PUSH DE...POP BC in note table creator ;19.Dec.2004 - NT_DATA reorganized (6 bytes shorter, thanks to ;Ivan Roshin); C_PORTM and C_GLISS are merged via SET_STP (48 ;tacts slower, but 8 bytes smaller, thanks to Ivan Roshin) ;15.Apr.2007 - all in-code variables and self-modified code ;moved to VARS (specially for Axor), code can run in ROM now. ;29.Apr.2007 - new 1.xx and 2.xx interpretation for PT 3.7+. ;Size: ;Code block $664 bytes ;Variables $23B bytes (can be stripped) ;Size in RAM $664+$23B=$89F (2207) bytes ;Notes: ;Pro Tracker 3.4r can not be detected by header, so PT3.4r tone ;tables really used only for modules of 3.3 and older versions.
vt_demo.c
/******************************************************************************* * A simple example program for Sinclair ZX Spectrum for demonstrating how * to use the Vortex Tracker II player from C to play a PT3 module in the * background. See vt_sound.h for more information. * * Compile with sccz80: * zcc +zx -vn -O3 -startup=1 -clib=new vt_demo.c vt_demo_module.asm \ * vt_sound.asm PT3PROM.asm -o vt_demo -create-app * or sdcc: * zcc +zx -vn -SO3 -startup=1 -clib=sdcc_iy --max-allocs-per-node200000 \ * vt_demo.c vt_demo_module.asm vt_sound.asm PT3PROM.asm -o vt_demo -create-app ******************************************************************************/ #include <input.h> #include <z80.h> #include <intrinsic.h> #include <stdint.h> #include <stdbool.h> #include <stdio.h> #include <string.h> #include "vt_sound.h" #pragma output CLIB_MALLOC_HEAP_SIZE = 0 #define printCls() printf("%c", 12) #define printAt(row, col, str) printf("\x16%c%c%s", (col), (row), (str)) extern uint8_t music_module[]; static void init_isr(void) { // Put Z80 in IM2 mode with a 257-byte interrupt vector table located at // address 0xD000 filled with 0xD1 bytes. Install vt_play_isr() as an IM2 // interrupt service routine. intrinsic_di(); im2_init((void *) 0xD000); memset((void *) 0xD000, 0xD1, 257); z80_bpoke(0xD1D1, 0xC3); z80_wpoke(0xD1D2, (uint16_t) vt_play_isr); intrinsic_ei(); } int main(void) { vt_init(music_module); init_isr(); printCls(); printAt(10, 8, "Enjoy the music!\n"); printAt(12, 5, "Press any key to exit\n"); while (true) { if (in_inkey() != 0) { break; } } vt_set_play_isr_enabled(false); vt_mute(); return 0; }
vt_demo_module.asm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PT3 demo module ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SECTION rodata_user PUBLIC _music_module _music_module: BINARY "music.pt3"
Regards,
Stefan
Offline
Nice work Stefan. I'm snowed under at work but I will certainly look more closely when the AY stuff is done.
Offline
The example I provided in my last comment in this thread, https://www.z88dk.org/forum/viewtopic.p … 75#p14675, compiles fine with the z88dk snaphsot from 2016-10-16. However, when I try to compile it with the latest z88dk snaphsot from 2017-06-19, I get the following error:
WARNING: some code or data may not be assigned to sections.
The compilation also generates a new file called vt_demo_BANK_07.bin which was not generated with the older z88dk snapshot.
Any idea what I'm doing wrong?
Regards,
Stefan
Offline
The correct link in my last post should be: https://www.z88dk.org/forum/viewtopic.p … 75#p14675
Offline
Arrgh, one last try: the link I referred to is: https://www.z88dk.org/forum/viewtopic.p … 675#p14675
By the way, why is it not possible to edit your own posts?
Offline
Stefan123 wrote:
Arrgh, one last try: the link I referred to is: https://www.z88dk.org/forum/viewtopic.p … 675#p14675
By the way, why is it not possible to edit your own posts?
The forum software frequently crashes on post edits so editing was disabled. I fall victim to this all the time too.
I get the following error:
WARNING: some code or data may not be assigned to sections.
The compilation also generates a new file called vt_demo_BANK_07.bin which was not generated with the older z88dk snapshot.
You have some code or data that was assigned to an unknown section (to the memory map) or not assigned to a section at all so it was not made part of the binary. This is an error.
Is vt_demo_BANK_07.bin empty? Until a few days ago, z88dk was emitting a bunch of empty files. With a newlib compile, you'd normally see unassigned stuff put into "vt_demo.bin" but I won't say it won't happen that a unknown named section would be assigned to the last defined section in the memory map (BANK_07 is bank#7 on a 128k spectrum).
This is what I found:
PT3PRM.asm
change section "code" to "code_user"
change section "data" to "data_user"
Use of ASMPC I think is ok. (The assemble-time value of ASMPC is different from the link-time value)
vt_demo.c
printAt macro -> change to col+1 and row+1. Coordinates are now 1-based to avoid putting 0s into strings which would act as string terminator.
https://github.com/z88dk/z88dk/blob/mas … trol-codes
vt_sound.asm
change section "code" to "code_user"
change section "data" to "data_user"
vt_sound.h
The attributes for fastcall and callee linkage have been homogenized for both compilers.
You can now always use "__z88dk_fastcall" at the end of a function to indicate fastcall linkage.
After I made these changes, things were working for me. Sections "code" and "data" are not defined in the memory map so that's why you got that error message. They have never been defined (there is a "CODE" and "DATA") but I think z80asm may have been case insensitive for a while.
https://drive.google.com/file/d/0B6XhJJ … sp=sharing
Offline
Alvin, thanks a lot for your help. I reverted to the latest official release, 1.99B, and then everything worked fine. I have added your changes to stay in line with the latest snapshot versions.
However, the __z88dk_fastcall decoration didn't work with sccz80 using 1.99B. Maybe the harmonization of the fastcall decorations were made after 1.99B?
By the way, the vt_demo_BANK_07.bin file was not empty.
Regards,
Stefan
Offline
Stefan123 wrote:
However, the __z88dk_fastcall decoration didn't work with sccz80 using 1.99B. Maybe the harmonization of the fastcall decorations were made after 1.99B?
Yes that change is after 1.99B. There have been more than 1000 commits since 1.99B so it's good to keep up. We intend to do another release soon but that has lost steam lately because we all seem to be busy.
By the way, the vt_demo_BANK_07.bin file was not empty.
Ok it probably makes sense for your case. You assign things to an unknown section so the linker appends that section to the last defined which happens to be the 128's bank 7. I should check if we can make the unnamed section the last one so stuff like that doesn't disappear into a memory bank that is actually being used.
Offline
Stefan123 wrote:
The example I provided in my last comment in this thread, https://www.z88dk.org/forum/viewtopic.p … 75#p14675, compiles fine with the z88dk snaphsot from 2016-10-16. However, when I try to compile it with the latest z88dk snaphsot from 2017-06-19, I get the following error:
WARNING: some code or data may not be assigned to sections.
The compilation also generates a new file called vt_demo_BANK_07.bin which was not generated with the older z88dk snapshot.
Any idea what I'm doing wrong?
Regards,
Stefan
Hi Stefan,
I'm trying to use your code for playing AY music on my game
Code could be easily shown on this github pull request
https://github.com/jsmolina/speccy-misifu/pull/34
It causes a crash in the game, so computer reboots
Any idea on why? I understood that I just need the pt3 file, so no .tap file concats or so.
Offline
Forget it, I made it work using
https://github.com/stefanbylund/vt_sound
But, sometimes adding new sprites just makes the game to hang
Offline
Hi Jordi,
Good that you got the vt player working
I haven't used the SP1 sprite library so much. Alvin is the SP1 expert if you have questions.
Offline
Hi jordi,
I downloaded a zip and built the game - it's looking good! It's a little finicky with jumping onto the barrels in the first level; I don't know if that's how the original works or not but if not maybe a little smoothing over there would help
2018-10-29 10:06 AM 22,823 misifu_CODE.bin
The binary size is 22823 bytes and that has all code and data in it. ORG is at 25124 (zpragma.inc) so the program extends to ~ 0xbb4b.
I saw in your zpragma file that you created a single queue for the block memory allocator. I think this is a remnant from the Black Hole example as I can't see you using the block allocator anywhere. SP1 will call malloc implicitly to allocate memory when creating sprites but the Black Hole example actually overrides malloc to allocate using the block allocator instead. When using the heap, what happens is the crt will take memory from the end of your program (~ 0xbb4b) up to SP - 512 = 0xd000 - 512 = 0xce00 and add that to the heap. So in total there is about 0xce00-0xbb4b = 4789 bytes to create sprites. That sounds like it's quite a bit depending on what your are doing. If you exceed this amount, the create and addcol functions will fail with 0 and if you don't test for that you may see crashes when creating new sprites.
SP1 takes memory from the top of the memory space for its data structures and by default the memory map looks like this:
( https://github.com/z88dk/z88dk/blob/mas … sp1.m4#L35 )
# DEFAULT MEMORY MAP # # With these default settings the memory map is: # # ADDRESS (HEX) LIBRARY DESCRIPTION # # f200 - ffff SP1.LIB horizontal rotation tables # f000 - f1ff SP1.LIB tile array # d200 - efff SP1.LIB update array for full size screen 32x24 # d1ff - d1ff SP1.LIB attribute buffer # d1f7 - d1fe SP1.LIB pixel buffer # d1ed - d1f6 SP1.LIB update list head - a dummy struct sp1_update acting as first in invalidated list # * d1ef - d1f0 SP1.LIB update list tail pointer (inside dummy struct sp1_update) # d1d4 - d1ec --free- 25 bytes free # d1d1 - d1d3 ------- JP to im2 service routine (im2 table filled with 0xd1 bytes) # d101 - d1d0 --free- 208 bytes # d000 - d100 IM2.LIB im 2 vector table (257 bytes) # ce00 - cfff ------- z80 stack (512 bytes) set SP=d000
You can see the stack is at the bottom so it's perfectly fine for the heap to take memory from the end of your program up to the stack.
Stefan's vt_sound library is assigning all pt3 related code and data to sections "code_user", etc, and you've put the song in "rodata_user" so it will all be part of your main binary. This is ok as you still have 4.7k space. If you ever needed to move that to another memory bank, you'd change the section assignment.
Your interrupt routine is a tiny bit off:
IM2_DEFINE_ISR_8080(isr) { // update the clock ++tick; vt_play_isr(); // todo this call only makes noise! vt_play_isr(); if (row1_moving != NONE) { --row1_moving; } }
An 8080 isr means only registers AF,BC,DE,HL are saved by the isr. This is ok for most lightweight interrupt routines like you have there with the exeception of "vt_play_isr()". "vt_play_isr" comes from the vt_sound library:
https://github.com/stefanbylund/vt_soun … nd.asm#L50
where you can see it's a standalone isr in its own right that preserves and restores all registers. The small problem is that at the end it re-enables interrupts when it returns meaning your isr's tail code (if row_moving...) is running with interrupts enabled. If you intended that to be atomic then it's not now. It may be better to have your isr preserve all registers and call the vt_play routine yourself:
IM2_DEFINE_ISR(isr) { // update the clock ++tick; if (_vt_play_isr_enabled) vt_play_raw(); if (row1_moving != NONE) { --row1_moving; } }
The one problem with this is you want to call "VT_PLAY" as in ( https://github.com/stefanbylund/vt_soun … nd.asm#L67 ) but this symbol is not exported by the vtsound library. The one exported "_vt_play" ( https://github.com/stefanbylund/vt_soun … nd.asm#L67 ) disables and re-enables interrupts which is not what you want. So you would have to add an asm define to your project "defc _vt_play_raw = VT_START + 5" (see https://github.com/stefanbylund/vt_soun … nd.asm#L12 ) and make that public so that the c compiler can see "vt_play_raw" if you wanted to do this. Or you could just make the call to vt_play last in the isr so everything before it runs atomically. There's a little extra push/pop but that won't kill anyone.
I can't see anything that would cause a crash unless you are running out of memory for creating sprites. 4700 bytes is quite a lot but the amout of memory needed is also large for each sprite. If you allocate and forget to free when done or if you do a lot of allocating and freeing in a tight space you may get fragmentation problems in the heap, these may cause you to run out of sprite memory. Black Hole used the block memory allocator specifically to avoid fragmentation issues.
Offline
Wow that's really good feedback. LOT of thanks alvin!
It's my first ever game for the Spectrum so I'm learning over the job.
If you download the .zip of the show/crash branch:
https://github.com/jsmolina/speccy-misi … show/crash
I've commented the CLIB_BALLOC_TABLE_SIZE, but it still crashes.
Such branch crashes nevermind if I add a new sprite or if sprite becomes larger (I tested both solutions).
I though it was related to CRT_ORG_CODE value, but from your comments I understood that this is not the case.
I already miss adding 2-3 sprites so I should be able to create them, so how I could scale it further?
I'm avoding the destroy just for the 'fragmentation' you mentioned, so I keep 'hiding' the sprites when they do not appear (e.g. dog is not visible, clothes are not visible).
Thanks for the advice, I'll change the code to test for sp1_CreateSpr not returning zero. Anyway, it's ok if I use 128k as the game will have AY music.
Offline
The crash branch has about 3970 bytes available for sprites. I tried adding more memory by reducing the expected size of the stack to 256 bytes by adding this pragma to zpragma.inc:
#pragma output CRT_STACK_SIZE = 256
The default is 512 bytes reserved for the stack and that's very large. 256 bytes is even big and maybe could be made smaller. Anyway, the game seems to run fine with this increase of 256 bytes in the heap.
You can also try moving the org down lower in memory to create more space.
Offline
Thanks for the advice, I'll change the code to test for sp1_CreateSpr not returning zero.
Adding test code is ok for finding the problem, otherwise it just adds to the code size; you want to make sure the game always has sufficient memory otherwise it can't run anyway, test code or not.
3970 bytes seems to be too small. You can do a calculation to figure out how much space from the heap each sprite requires:
https://github.com/z88dk/z88dk/blob/mas … /sp1.h#L56
1. struct sp1_ss : 20 bytes + 4(?) malloc overhead
2. Per character square a struct sp1_cs: 24 + 4(?) malloc overhead
So a 3 rows x 5 cols sprite would use up about:
24 + 15*(24+4) = 444 bytes
With 3970 bytes free, that adds up quickly!
If you want to create all sprites at the beginning and have them the whole game, your only option is to get more memory. So reduce the amount allocated to the stack, drop crt org, and/or move the ay stuff to a 128k memory bank. The latter is probably the easiest especially if the game is going to grow.
If you want to change it so that you allocate and destroy sprites as needed, you can calculate how much the worst case memory demand is and then use the block memory allocator to avoid fragmentation issues connected to a heap. What you can do is create a single queue that dispenses 24-byte blocks and redefine malloc to allocate from that queue and free to call the block allocator free function. To initialize, you would add available memory to the block queue. The nice thing about it is these blocks can come from all over the memory space, including that 208-byte hole at 0xd001 shown in the sp1 memory map. Then you can create and destroy sprites between levels as needed.
Offline