TECHNICAL INFO ABOUT THE PLAYERS -------------------------------- GENERALITY ---------- The 3 players have the same main code. The ASM is the most optimised. The BASIC and INTERRUPTION player uses the system vectors to trigger the 300hz interruption, and are a bit slower, because it must saves some registers the system needs. The INTERRUPTION takes more memory, because it must interpret the frequency written in the song, and play the song according to it. RESSOURCE CONSUMPTION --------------------- I coded the players as best as I could. The ASM player can take a maximum of 34 lines of machine-time, but will generally raise up to 30, for a size of more than 2kb in memory ; some of it can be saved, read below. The player uses no buffer, there's no more memory than the Player plus the song to be used. The 2 others players takes a bit more, especially the INTERRUPTION player. Players size : ASM = &98B BASIC = &9A6 INTER = &9FC MEMORY MANAGMENT ---------------- If you use the ASM player, you can choose to put the player and the music everywhere you want. The only condition is that the player can access the whole music data when being called. If the music and/or the player is/are in bank (completely or partially), the bank must be opened before calling the player. The players don't modify the Stack, the interruption flag/mode, or any chipset registers (except of the Z80 and PSG, of course). No barbarian tricks (out (0),a) or anything like that. Every Z80 registers are corrupt. Using the INTERRUPT Player gives more constraints : you can't close a bank if the music/player is inside, or open a bank if the music/player is in the central RAM. On top of that, the player must be assembled from #4000, and of course must not reach the HIMEM of the Basic (=#A67B. HIMEM decreases when ROMs are loaded. In order to stay compatible with most configuraion, try not to load your player too high in memory). However, the generator won't prevent you from doing this. Don't forget that these limitations concern the player, but the song data as well. TWEAKING THE PLAYER or THE SONG ------------------------------- Unlike the AMC and the Sountrakker, you're given some addresses you can play with, so that you can modify a bit the player to make it fit your needs, or to get some interesting values. ALL addresses given are in fact OFFSETS from the beginning of the song or the player. They're all in hexadecimal. ----- In the header of a song are... values ! I won't give you all of them, it'd be useless to you, but some are really valuable. OFFSETS 0-3 DB "SK10" Just a little ASCII tag for rippers to detect STK songs easier :). 4-5 DW Base Address of the Song. #00,#04 if the song has to be loaded in #4000, for example. 6 DB No of the Channel used by Special Tracks Digidrums. 1,2 or 3. Shouldn't be really useful, but it's here. 7-8 DW Song Frequency in Hertz (13 (=#D), 25 (=#19), 50 (=#32), 100 (=#64), 150 (=#96) or 300 (=#12C)). Only used by the INTERRUPTION player when initialising. That's all. You're not allowed to search elsewhere :). ----- You can also find the base address of the player. If it's a ASM or BASIC player, it is coded at the offsets +#9/+#A (low byte/high). For an INTERRUPTION player, look at the offsets +6/+7 (low/high). ----- If you want to bypass the sending of values to the PSG, poke a &C9 here... ASM BASIC INTER &3AA &3B3 &406 This is useful if you want, for example, to make digidrums (look at the DIGIDRUM section below). Also could be used if you want to generate a YM-list in order to have the fastest music code ever (but also a great amount of memory used by the list !). In this case, 'hearing' the song is useless, you would just have to run the player, get the values, code them and restart until the song is over, then read the YM values with your own player. The normal value in this address is a &C3 (JUMP). WARNING, there's a special case about the Register 7, its value is given through A before the jump ! So if you bypass the JUMP, but jump to that code later, don't forget to set A with the Register 7 value you took care to save... !!! WARNING !!! IF you set a RET with the BASIC or INTER players, it will crash ! Why ? Because these players saves some registers (by PUSHing them) at the beginning, and recover them (by POPing) after having sent PSG values. So by bypassing it, the registers are never recovered, the RET will jump at a bad address. So instead of setting a RET, we'll jump directly to the code that will recover the registers. You must poke this : BASIC : poke #C3 (JUMP) in #3b3, and (Address of the player + #85D) next. (low byte then high byte). INTER : poke #C3 (JUMP) in #406, and (Address of the player + #8B0) next. (low byte then high byte). BUT WARNING AGAIN !! By doing this we will be unable to get the Reg7 value, as it's given through A to the code that send PSG values (see below). We must save it first. You can do it where you want. The address #1F is used in this example, because no one use it. But we must do it outside the player, because this code addition will overflow on some other parts of the player. SO, we will our code in #3C, no one goes here. Here, we save A in #1F, and then JUMP to the code that recovers players : Poke in ADPLAYER+#3B3 (BASIC) ou ADPLAYER+#406 the following bytes : #C3 #3C #00 (Jump #003c) In #3c, poke this : #32,#1F,#00, the two last bytes being where you want to save Reg7. Then poke the 3 bytes discussed above (the 1st WARNING). Now you can recover the Reg7 from #1f at any time. I know it's really bad trick, I will perhaps improve that in futur player, however it's not very useful except in the YM case. And most BASIC coders won't ever need it, so... If you use the ASM player : don't bother with this crap, poke a RET as said at the beginning, and save A ! ---- Now comes the address of the code that send values to the PSG... ASM BASIC INTER &6CB &6D4 &727 WARNING : The value of the REGISTER 7 is GIVEN through A ! ---- Then, here are the addresses where you can get the values sent to the PSG. So if you need to code an equalizer, you can get all PSG values here. Of course, the addresses are various according to the players. They are quite useless if you use the INTERRUPT player, but hey, here are they anyway : ASM BASIC INTERRUPT R0 &6D6 &6DF &732 Low Byte Frequency, Channel 1 R1 &6F1 &6FA &74D High Byte Frequency, Channel 1 R2 &727 &730 &783 Low Byte Frequency, Channel 2 R3 &742 &74B &79E High Byte Frequency, Channel 2 R4 &778 &781 &7D4 Low Byte Frequency, Channel 3 R5 &793 &79C &7EF High Byte Frequency, Channel 3 R6 &7C9 &7D2 &825 Noise R7 NOT CODED Mixer (R7OLD &7E5 &7EE &841) R8 &70C &715 &768 Volume, Channel 1 R9 &75D &766 &7B9 Volume, Channel 2 R10 &7AE &7B7 &80A Volume, Channel 3 R11 &7FE &807 &85A Low Byte Hard Enveloppe Frequency R12 &819 &822 &875 High Byte Hard Enveloppe Frequency R13 &834 &83D &890 Hardware Enveloppe Shape The Register 7 is NOT automodified ! It is GIVEN in A to the code that send values to the PSG (see above). NOTE : If you add 2 to each address, you get the PREVIOUS value of the register. BEFORE sending a value to a register, the new value is compared to the previous one. If they are equal, there's no need to send it again, hence a saving of machine time. The exception is the Register 13, which is managed in any case if RETRIG is >0. (Note that I gave you the address of the old value of Reg7, since the new one is given through A). RETRIG : ASM BASIC INTER &83B &844 &897 To force the sending of a velur, you can put a 255 in the previous value, since this value est not used by most registers (and I never noticed anything strange from the frequency registers). ---- SAVE A BIT OF MACHINE TIME ? Errr... That's not possible, sorry :). I hope to release some more optimised versions later, coz I know where the machine time leaks are. However, you may notice that the first frame takes a lot of machine time, that's because all PSG registers are refreshed, thus sent to the PSG (Their 'previous value' is set to 255). To avoid this, AFTER initialisation, you can read the current PSG values (through the PPI, not through the player !), and write them in their respective 'previous values' address in the player. Thus, only the changing values will be played in the first frame. ---- FADE IN/OUT It's possible to create a fade in or out in a Starkos song. However, you must do it yourself... Indeed, in order to save memory and machine time, I didn't explicitly write a function to allow that. But with a bit of trick, it's possible. You simply have to bypass the values sending to the PSG. Play the song once, read the values of the Registers 8,9 and 10, and decrease them of 1,2,3... to 15 (for a fade out), then jump to the code that send the values (Don't forget to save/restore A which contains the Register 7 value). Take care of overflows. If the volume is below 0, set it to 0. If it gets higher than 15 (during a fade in), set it to 15. Hardbasses are more problematic, that's why you will choose by yourself how you want to handle them. Their volume is ALWAYS 16 with Starkos. You can decrease them too, so they will lost their 'hard' status, which will probably sounds strange and flat. You can also ignore them, but only the 'normal' sounds will fade. You can also cut them, simply. The choice is yours... ---- DETECTING THE END OF THE SONG You want to detect the end of the song ? For example, to convert SKS file into YM (for example !) ???? Ok, this is possible. Once again, no hardcoded functions. This one is really crap tricking. All apologize for this, but I found no other way to do it. Yet it remains very simple to code, just follow this algorithm : (xxxx)16 means '16 bits number found at the offset xxxx'. (byte at xxxx=low byte byte at xxxx+1=high byte) (xxxx)8 means '8 bits number found at the offset xxxx'. ASM BASIC --- --- STATE=0 LOOP: Play your song If STATE=0 : if (009D)16 <> (283)16 then goto LOOP (00A6)16 <> (028C)16 STATE=1 : goto LOOP If STATE=1 : if (0270)8 <> 0 then goto LOOP (0279)8 <> 0 if (0015)8 <> (0017)8 then goto LOOP (001E)8 <> (0020)8 STATE=2 : goto LOOP If STATE=2 : if (0015)8 <> (0017)8 then goto LOOP (001E)8 <> (0020)8 STATE=0 Song End is now ! ---- KILLING USELESS PARTS OF THE PLAYER You're short in memory and you want to save it as much as possible ? STarKos is made for you, really :). In the BASIC and ASM players, the INITIALIZE code is at the end of the player. So you can simply destroy it. Just before is the STOP MUSIC code, but it's really tiny so you won't get much. These addresses show you from where these codes begin : ASM BASIC STOPMUS &854 &866 INITMUS &870 &88B Don't forget that you can also completely forget about the INITIALIZE code, by loading the player, your song, calling the INITIALIZE code, and saving your player (without the INITIALIZE code). This way, the player already got all the inits he needs. Of course, you won't be able to re-init the song, or use the player for another music, but demomakers will surely find this very useful. ---- DIGIDRUMS SO. STarKos players manage digidrums in a very flexible way. It reads the song, and if a digidrum is detected in the song, returns the number of the sample (from 1-#3f, 0=No digidrum) and the channel where to play it (1/2/3). Basic can't play digidrums, but I guess you can use digidrums to trigger some events (it works in ASM too). Digidrum detection shouldn't be used in any case under the INTERRUPT player, because you might miss some events, but hey, you do what you want... The values must be read here : Sample Number : ASM BASIC INTER &B &B &8 Channel Number: ASM BASIC INTER &C &C &9 How to play digidrums ? ----------------------- I won't tell technically how to play digidrums, this is not the purpose this text (read Demoniak 5, or Quasar). However, I'd like to talk about how you should manage the digidrums to play them as cleanly as possible. The most common, easiest, yet less effective way to play digidrum would be to run the player one time, test if a digidrum must be played, and play it (cutting the noise and sound of the channel). You can improve this. Check DIGIDRUM.DAM / DIGIDRUM.MXM. The method I use is a bit more complex, but provides a better sound quality. Of course, you're free to code it the way you want ! The main point is that we play the music but don't send any value to the PSG. If a digidrum is detected, we prevent the player from modifying the PSG registers linked to the channel where we're going to play the sample (in fact, only the VOLUME is concerned). We also cut the noise and sound of the digichannel, in the Register 7. Now we're ready to send the modified values to the PSG. Then, we just have to play the sample, and that's all for this frame. In the source example, the samples addresses and size are given in a table, but we test if the sample is over only at the end of the frame, so if you play 160 samples per frame (160*50 = 8000Khz, which is good for a digidrum), and your sample is 350 bytes long, the CPC will actually play 480 bytes (160* 3). This low accuracy doesn't matter when playing digidrums ; Testing the end after each byte would not provide any better result, and would take much more machine time. ---- Ok, enough tweaking for today, it drives the player mad.