Sorry for sort of duplicating thread, now I see this board is probably better place to discuss my problem.
https://www.cpcwiki.eu/forum/software-related/issue-with-re-enabling-amsdos-when-arkos-player-used/ (https://www.cpcwiki.eu/forum/software-related/issue-with-re-enabling-amsdos-when-arkos-player-used/)
In short - I have problems with using amsdos firmware routines after using Arkos Tracker player (AKG) in my code (cpctelera + assembly).
If I disable playback in interrupt handler, I can load next game level without problems but with music playing loading does not work. Any hints/suggestions how to fix it? Maybe I need somehow to force firmware/amsdos reload?
my loader:
.area _LOADER (ABS)
;;_loadlevel::
.org 0xA640
_loadlevel::
;;call #0xbca7 ;; reset sound manager
ld c,#0xff ;; disable all roms
ld hl, #_dummy ;; execution address for program
call #0xbd16 ;;mc_start_program ;; start it
_dummy:: nop
call #0xbccb ;;kl_rom_walk ;; enable all roms
;; ld c,#0x07
ld hl,#_loader
call #0xbd13 ;;mc_boot_program
_loader:: ;; this is a loader which loads file to specific address and returns start addr in (HL)
;;call #0xbccb
ld b, #10 ;;filename length
ld hl,#filename ;;pointer to filename string
;;ld l,#0x24
;;ld h,#0x89
ld de, #0x0 ;; 2k buffer - anything in this case
call #0xbc77 ;;cas_in_open
;
LD L, #0x00
LD H, #0x01
;; read file
call #0xbc83 ;;cas_in_direct
;; firmware function to close a file opened for reading
call #0xbc7a ;;cas_in_close
LD L, #0x62
LD H, #0xa1
;;scf
ret
filename: .asciz "LEVEL3.BIN"
invoking load:
void LoadGame(void) {
//cpct_setVideoMode(0);
//cpct_removeInterruptHandler();
//PLAYER_ARKOS_STOP();
textX=32;textY=11;
Print(" LOADING \2");
cpct_removeInterruptHandler();
PLAYER_ARKOS_STOP();
delay(4);
//loadlevel();
interrupt handler (commenting out PlaySound() makes the program working correctly but no music plays)
void sInterruptHandler(void)
{
static u8 sInterrupt = 0;
// Play sound at 1/50th
if (++sInterrupt == 6)
{
if (graj) PlaySound();
// cpct_scanKeyboard_if();
sInterrupt = 0;
}
cpct_scanKeyboard();
if (intro)
{
if (cpct_isAnyKeyPressed()) {graj=1; Gra();}
}
}
any help / hints appreciated
I'm not a CPCtelera specialist, but I'm not sure it's a good idea to call cpct_removeInterruptHandler() before loading the level. This function will turn off interrupts entirely by writing EI:RET at 38h.
And firmware functions would need the firmware interrupt to be present at 38h (jp 0B939h on a 464).
The way I see it, I would follow those steps :
1) call cpct_disableFirmware. It returns a Word containing a pointer to the current firmware ROM code. Save it!
2) Set a new interrupt handler to run your Interrupt Service Routine, the one calling the Music player (don't forget to use DI before and EI eventually).
3) Start the Music player. And the game.
Then, when the time has come to load a new level :
4) Stop the Music player (PLAYER_ARKOS_STOP())
5) call cpct_reenableFirmware (https://lronaldo.github.io/cpctelera/files/firmware/cpct_reenableFirmware-s.html#cpct_reenableFirmware) with the previously saved Word as input parameter.
6) Open, Read, Close your level file.
7) Repeat steps 1 & 2
Thanks a lot !!
Will try it out this evening.
Strange thing it is all working fine in current setup if I only comment out PlaySound(); in interrupt handler.
I tried yesterday something like that:
void main(void) {
firmwareptr=cpct_disableFirmware();
...
...
...
PLAYER_ARKOS_STOP();
cpct_reenableFirmware(firmwareptr);
loadlevel();
but did not help much
but what did a bit of change was
cpct_enableLowerROM();
this gave me "press PLAY then any key" (with corrupted font)
no AMSDOS still but at least soemthing
Summarizing:
Hope your hints will work, thanks once one :-)
Also, Arkos player seems to modify the shadow registers, especially AF' which is used by AMSDOS.
So you'd need to save & restore it when calling PlaySound.
It seems mc_start_program will do a lot of the work you need including restoring interrupts for firmware and setting up AF' and BC'.
I have not tested this but I think all you need is:
PLAYER_ARKOS_STOP();
loadlevel();
cpct_disableFirmware();
I *think* cpct_enableFirmware will enable the interrupt and because Arkos player is using BC' and AF' and it's not how the firmware expects this will cause problems.
But, because loadlevel calls mc_start_program this will init everything so you don't need to use cpct_enableFirmware.
Try that but if there are still problems I will be happy to debug it and tell you how to fix it and you can either share a minimal version of the code here as a dsk or you can private message me and tell me where I can download it and I will debug (and not share).
Hi
@bart71 , sorry I didn't see your PMs and this thread earlier (I'm on vacation). I don't know CPCTelera so I can't really help you with your specific problem, but:
- Before calling the "play song" every frame,
if you're under interruption, make sure to save
ALL the registers, including AF', BC', DE', HL', and IX/IY.
- The system requires AF' and BC' only, so you should also store these and put them back before loading a file.
- The PLY_AKG_PlaySoundEffect method modifies AF', save it first if needed.
Tell me if it you manage to make this work!
Trg.
Thank you all!
I see that I am in a very good company :) :) :)
These are my first attempts with assembler programming so please forgive me ... I am still not aware of many things happening there ..
I will try to preserve registers and see if it helps, sorry for silly question - normally you push values to the stack and get them later - is that what I need to do? Can save those values to unused memory locations and get them later and load back to registers?
Br
Bartek
PUSH/POP are your friends indeed. Make sure you POP in the reverse order you PUSHed. If wanting to save values and restoring them much later, you can either save into memory:
ld (SaveHL),hl
...
ld hl,(SaveHL)
...
SaveHL dw 0
... or by automodifying code:
ld (SaveHL + 1),hl
...
SaveHL ld hl,0
But this is more dangerous, though faster. Watch out, IX and IY operands are encoded on two bytes (so +2 instead of +1).
Hello again
Something silly:
Tried 2 methods of saving AF' and BC' but SDCC complains on those special registers with apostrophes
_store::
push af'
push bc'
ret
_restore::
pop bc'
pop af'
ret
filename: .asciz "LEVEL3.BIN"
SaveAF': .dw 0
src/loader.s:51: Error: <q> missing or improper operators, terminators, or delimiters
src/loader.s:55: Error: <q> missing or improper operators, terminators, or delimiters
src/loader.s:61: Error: <q> missing or improper operators, terminators, or delimiters
However when I look at player code it contains lines like that:
PLY_AKG_CHANNEL3_GLIDEDIRECTION: ld a,#0
or a
jr z,PLY_AKG_CHANNEL3_GLIDE_END
ld (PLY_AKG_CHANNEL3_AFTERARPEGGIOPITCHVARIABLES+1),hl
ld c,l
ld b,h
ex af,af'
ld a,(PLY_AKG_CHANNEL3_TRACKNOTE+1)
add a,a
ld l,a
ex af,af'
and it compiles OK !!!
What is wrong here?
OK I see this in Z80 manual:
Allowed Instructions
push af
push bc
push de
push hl
push ix
push iy
OK !!!!!!
have found I need to do ex af,af'
and for BC' I need to do EXX I assume
so pushing AF' would look like:
ex af,af' ; to exchange registers
push af ; push
ex af,af' ; get back proper value of AF
is above correct?
This is what I do :
;;Save general registers
push af:push bc:push de:push hl:push ix:push iy
;;Save shadow registers
exx:ex af,af':push af:push bc:push de:push hl
...
Your code here
...
;;Restore shadow registers
pop hl:pop de:pop bc:pop af:exx:ex af,af'
;;Restore general registers
pop iy:pop ix:pop hl:pop de:pop bc:pop af
Now it is more clear, however questions:
1. Why after pushing you are not reverting to original values using exx and ex ?
2. Do I need to preserve/restore those registered with every PlaySound()? My idea is to save it in the beginning of main() and then restore it before calling loadlevel()
alternative way of storing it to memory it would be something like below, correct?
ex af,af'
ld (storeaf),af
...
ld af,(storeaf)
ex af,af'
storeaf: .dw 0
Quote from: bart71 on 23:22, 26 July 25Why after pushing you are not reverting to original values using exx and ex ?
I forgot to mention this is a snippet of code I use for an Interrupt handler.
The first thing to do, obviously is to save the registers. After that, you're free to use all the registers the way you want, so you can indeed carry out another
exx:ex af,af' to switch to general registers, or keep using the shadow registers. It doesn't really matter since eventually, both set of registers will be restored.
I'm not sure how CPCTelera works with Interrupt handlers, but I assume it inserts some code to save/restore the general registers, otherwise you would have seen a crash already. The way I see it you should save the shadow registers just before calling PlaySound, and restore them when the function returns :
;;Save shadow registers
exx:ex af,af':push af:push bc:push de:push hl
;;Switch back to general registers
exx:ex af,af'
call PlaySound
;;Switch to shadow registers
exx:ex af,af'
;;Restore shadow registers
pop hl:pop de:pop bc:pop af
;;Switch back to general registers
exx:ex af,af'
...
ld (storeaf),af is not a valid instruction : you can't manipulate the flag register directly with a ld instruction. The only way to save/restore the flag register is through push & pop af.
Quote from: bart71 on 23:22, 26 July 25Now it is more clear, however questions:
1. Why after pushing you are not reverting to original values using exx and ex ?
The Z80 doesn't really care about which set of registers is which. When you switch with the with EXX the registers which were previously the alternate set are now the main ones, so you don't need to "switch them back"
Quote from: bart71 on 23:22, 26 July 25alternative way of storing it to memory it would be something like below, correct?
ex af, af'
ld (storeaf),af
Alas no, the Z80 doesn't have instructions for storing a 16-bit value to a memory address, so you either do it an 8-bit register at a time or do it indirectly with a pointer in HL, IX or IY. And the problem then becomes not being able to access F directly...
Pushing to the stack is absolutely the best way to preserve registers during an interrupt.
OK, I hope I will make it working later today.
However no comments on my question #2 ;-)
can't I copy registers while program initializes and restore them just before loading next level?
Quote from: bart71 on 09:46, 27 July 25OK, I hope I will make it working later today.
However no comments on my question #2 ;-)
can't I copy registers while program initializes and restore them just before loading next level?
PlaySound will corrupt the values in every register. Which one's you need to preserve depend on when you call it.
In an nterrupt routine you won't know which register values are "important" at the precise moment the interrupt occurs , so your interrupt code needs to restore any it uses to what they were when it was called. Hence, if calling PlaySound under interrupt, you're going to need to preserve them all.
If you're calling it outside of an interrupt handler, for example after a frame flyback check, then you can more easily consider which registers hold important information (which might not be any of them) and thus only preserve what you care about.
2) I suggest you should save AF' each time you call PlaySound, though once again, only the system should need to have it restored correctly. If calling PlaySound under interruption (But I don't think that's the case), you MUST save AF', but also all the registers that PlaySound will modify.
PLaySound only modifies AF, BC, DE, HL and AF'.
Checked in the debugger and some of those registers change only once. will experiment and share results
Finally I made it to work!!!!!
Thanks everyone.
At the moment it is a temporary dirty fix loading static register values taken from 'silent' working version - it seems ony AF' and BC' need to be restored.
__asm
ld hl,#0x0044
push hl
ex af,af'
pop af
ex af,af'
ld hl,#0x7f8d
push hl
exx
pop bc
exx
__endasm;