z88dk forums

z80 Development Kit

You are not logged in.

#1 2016-10-25 21:11:23

Stefan123
Member
Registered: 2016-10-21
Posts: 85

How to play AY music with z88dk?

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

 

#2 2016-10-26 01:54:56

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

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:

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:

"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 smile  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

 

#3 2016-10-26 19:43:10

Stefan123
Member
Registered: 2016-10-21
Posts: 85

Re: How to play AY music with z88dk?

Thanks Alvin for your detailed answers and explanations! I will have to absorb all your info before coming back with any follow-up questions smile

Offline

 

#4 2016-11-18 21:29:23

Stefan123
Member
Registered: 2016-10-21
Posts: 85

Re: How to play AY music with z88dk?

Hi Alvin,

Now I'm able to play Vortex Tracker II modules in the background smile 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:

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

"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

 

#5 2016-11-20 16:44:46

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

Stefan123 wrote:

Now I'm able to play Vortex Tracker II modules in the background smile 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:

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

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

"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

 

#6 2016-11-20 17:01:55

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

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

 

#7 2016-11-22 19:37:23

Stefan123
Member
Registered: 2016-10-21
Posts: 85

Re: How to play AY music with z88dk?

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

 

#8 2016-11-24 02:52:14

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

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 smile

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

 

#9 2016-11-24 02:57:33

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

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 smile

Offline

 

#10 2016-11-29 19:16:36

Stefan123
Member
Registered: 2016-10-21
Posts: 85

Re: How to play AY music with z88dk?

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

 

#11 2016-12-07 21:37:34

Stefan123
Member
Registered: 2016-10-21
Posts: 85

Re: How to play AY music with z88dk?

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:

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

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

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

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

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

SECTION rodata_user

PUBLIC _music_module

_music_module:
BINARY "music.pt3"

Regards,
Stefan

Offline

 

#12 2016-12-10 16:55:12

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

Nice work Stefan.  I'm snowed under at work but I will certainly look more closely when the AY stuff is done.

Offline

 

#13 2017-06-19 20:47:40

Stefan123
Member
Registered: 2016-10-21
Posts: 85

Re: How to play AY music with z88dk?

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

 

#14 2017-06-19 20:51:29

Stefan123
Member
Registered: 2016-10-21
Posts: 85

Re: How to play AY music with z88dk?

The correct link in my last post should be: https://www.z88dk.org/forum/viewtopic.p … 75#p14675

Offline

 

#15 2017-06-19 20:54:00

Stefan123
Member
Registered: 2016-10-21
Posts: 85

Re: How to play AY music with z88dk?

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

 

#16 2017-06-20 02:19:21

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

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

 

#17 2017-06-20 21:25:16

Stefan123
Member
Registered: 2016-10-21
Posts: 85

Re: How to play AY music with z88dk?

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

 

#18 2017-06-21 00:27:14

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

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

 

#19 2018-10-28 15:53:22

jordi
Member
Registered: 2018-10-28
Posts: 19

Re: How to play AY music with z88dk?

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 sad

Any idea on why? I understood that I just need the pt3 file, so no .tap file concats or so.

Offline

 

#20 2018-10-28 16:21:58

jordi
Member
Registered: 2018-10-28
Posts: 19

Re: How to play AY music with z88dk?

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

 

#21 2018-10-29 08:51:24

Stefan123
Member
Registered: 2016-10-21
Posts: 85

Re: How to play AY music with z88dk?

Hi Jordi,

Good that you got the vt player working smile

I haven't used the SP1 sprite library so much. Alvin is the SP1 expert if you have questions.

Offline

 

#22 2018-10-29 16:56:21

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

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 smile

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:

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

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:

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

 

#23 2018-10-30 22:36:19

jordi
Member
Registered: 2018-10-28
Posts: 19

Re: How to play AY music with z88dk?

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

 

#24 2018-10-31 03:14:54

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

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

 

#25 2018-10-31 03:33:04

alvin
Administrator
Registered: 2007-07-16
Posts: 1932

Re: How to play AY music with z88dk?

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

 

Board footer

Powered by PunBB
© Copyright 2002–2005 Rickard Andersson