How to play AY music with z88dk?

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

How to play AY music with z88dk?

Post by Stefan123 »

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
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

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):

Code: Select all

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):

Code: Select all

"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).
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

Thanks Alvin for your detailed answers and explanations! I will have to absorb all your info before coming back with any follow-up questions :)
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

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.

Code: Select all

"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

Code: Select all

"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
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

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:

Code: Select all

"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

Code: Select all

"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

Code: Select all

"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)
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

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.
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

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.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

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.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

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 :)
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

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.
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

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

Code: Select all

/*******************************************************************************
 * 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

Code: Select all

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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

Code: Select all

;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

Code: Select all

/*******************************************************************************
 * 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

Code: Select all

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PT3 demo module
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

SECTION rodata_user

PUBLIC _music_module

_music_module:
BINARY "music.pt3"
Regards,
Stefan
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Nice work Stefan. I'm snowed under at work but I will certainly look more closely when the AY stuff is done.
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

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
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

The correct link in my last post should be: https://www.z88dk.org/forum/viewtopic.p ? 75#p14675
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

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?
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

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
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

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
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

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.
jordi
Member
Posts: 57
Joined: Sun Oct 28, 2018 3:35 pm

Post by jordi »

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.
jordi
Member
Posts: 57
Joined: Sun Oct 28, 2018 3:35 pm

Post by jordi »

Forget it, I made it work using
https://github.com/stefanbylund/vt_sound

But, sometimes adding new sprites just makes the game to hang
Stefan123
Member
Posts: 85
Joined: Fri Oct 21, 2016 7:57 am

Post by Stefan123 »

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.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

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 )

Code: Select all

# 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:

Code: Select all

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:

Code: Select all

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.
jordi
Member
Posts: 57
Joined: Sun Oct 28, 2018 3:35 pm

Post by jordi »

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.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

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.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

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.
Post Reply