Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Using Beepola with ZX BASIC
#1
Using Beepola to add tunes to your ZX BASIC projects is actually pretty easy. Beepola is able to export its players and songs as .TAP files, which you can then merge in with your own code and call from ZX BASIC using standard RANDOMIZE USR calls.

Here's a quick how-to:

1. Make your tune. I can't help much with this bit, but I've successfully managed to integrate the Music Box, Phaser1 and Special FX engines into ZX BASIC, so I surmise they will all work. You can download Beepola from here: http://freestuff.grok.co.uk/beepola/

2. Now you need to compile your tune so that it's useful from ZX BASIC. You do this by selecting 'Compile' from Beepola's 'Tools' menu, which pops up a window with some options. The most important thing here is to make sure you set the player and song to use an area of memory which won't interfere with your own program. I tend to set this high in memory (64000 and up), but bear in mind that each player uses a different amount of memory and you'll also need to leave room for your song data. Each player has a different set of options, but basically it's all up to you here - choose whatever's best for your tune and situation.

3. The easiest way to integrate the song and player is to choose to export from Beepola as a .TAP file. I usually choose the 'code block only' option because you're not going to need the BASIC loader. Once that's saved, you need to load your game AND the player into memory at the same time. It's actually pretty easy to automate all of this using batch files (or their equivalent if you're non-Windows), because .TAP files are remarkably easy to join together. During Dex development, I created a BASIC loader which loaded my game code and the music code and saved that as a .TAP file. Then I used a batch 'make' file like this to build and assemble the game into a single .TAP.

e.g. BASIC LOADER (saved as loader.tap)
Code:
10 CLEAR 29999: LOAD "" CODE: LOAD "" CODE: RANDOMIZE USR 30000

e.g. BATCH FILE (saved as make.bat)
Code:
"\program files\zxbasic\zxb.py" dex.bas --output=dexgame.tap --org=30000 -t -Z
copy /b loader.tap + dexgame.tap + DexJingle.tap Dex.tap

The first line of the batch file compiles the game. Once done, the 'copy' command takes the loader, game code and tune and merges them all together into one large .TAP file (called Dex.tap) that I can just double-click and run. Don't forget the /b switch on the copy operation, otherwise the files will be copied as text rather than binary and probably won't work.

4. If your game is to have multiple tunes, you only need to have one copy of the player - you don't need a separate copy of the player for each tune. Each Beepola engine has a couple of POKEable memory addresses near the start of the player code which points to the tune to be played. If you make more than one tune, export those from Beepola as 'song data only' .TAP files. Make sure that you choose the same settings as for your main tune and that you place the song data at a memory location that is away from both your game code AND the first tune's code.

5. During all of these steps, remember to keep track of the memory addresses that you have compiled the music data to. This is rather important as without the addresses you won't know what to call from your game. Including the address in your filenames as a reminder might be a good idea! Smile

6. Once you have everything ready to go, all that's left to do is to call the player from within your own ZX BASIC code. This is actually very simple - just a standard RANDOMIZE USR xxxxx call (where xxxxx is the address you compiled the player to) will do the trick. However, if you're making this call from within a ZX BASIC sub or function block, be aware that you need to preserve the IX register or the Spectrum is likely to crash - more details on this below. If you wish to play one of the 'extra' tunes described in point 4, you'll first need to POKE the relevant memory addresses with the location of the new song data - Beepola's documentation will tell you how to calculate what these addresses are for each individual engine.

7. This all sounds a bit complicated, but it really isn't. It's probably easier to show in an example, so I'll show you how I created my music playing subroutine for Dex. See the code fragment below:

Code:
'some constants
const TUNESON as UBYTE = 1
const TUNEPLAYERADDR as UINTEGER = 64000
const TUNE1 as UINTEGER = 64798
const TUNE2 as UINTEGER = 64850
const TUNE3 as UINTEGER = 64900

const TUNEMAIN as UBYTE = 1
const TUNELEVELSTART as UBYTE = 2
const TUNELEVELEND as UBYTE = 3
const TUNEGAMEOVER as UBYTE = 4

'plays a piece of music
SUB playMusic(tune as UBYTE)
    DIM SongAddr as UINTEGER = 0
    
    'select the correct tune
    if tune = TUNEMAIN then
        SongAddr = TUNE3
    elseif tune = TUNELEVELSTART then
        SongAddr = TUNE1
    elseif tune = TUNELEVELEND then
        SongAddr = TUNE3
    elseif tune = TUNEGAMEOVER then
        SongAddr = TUNE2
    end if
    
    'call the music player
    if TUNESON = 1 and SongAddr > 0 then
        
        'poke the values for the tune start
        poke TUNEPLAYERADDR + 1, SongAddr - 256 * INT(SongAddr / 256)
        poke TUNEPLAYERADDR + 2, INT(SongAddr / 256)
        
        'and play the tune (remember to preserve IX register or it'll crash)
        asm
            push ix
        end asm
        randomize usr TUNEPLAYERADDR
        asm
            pop ix
        end asm
        
    end if
    
END SUB

This routine is basically all of the stuff I described above squashed into a single sub. The main music player was compiled and loaded to 64000, which I've put into a constant. The three tunes I use also have their memory locations described by constants. To play a tune, I call 'playMusic(TUNEMAIN)' (or whatever) and the SUB does all the rest.

You'll see that the address of the tune is determined by the constant I pass - this address is then POKEd into the player engine as described in Beepola's docs. Because we're calling from a SUB, the IX register needs to be preserved - this is easily done with a simple 'push ix' in an ASM block. Once we've done that, it's safe to call the player with 'RANDOMIZE USR'. Once the player returns control to us, we 'pop' the value of IX to allow us to return safely from the subroutine.

Job done!


I hope this is clear, it seems a lot more complicated to write down than it is in my head. If anyone has further questions, please feel free to ask! Smile
Reply
#2
It`s very interesting ¡

I will try to use

Thanks
Reply
#3
Hey, great how-to, thanks! Big Grin I've managed to put one song with this method but now that I know that the player can be reused, I can put more that one in a game without loss too much memory.

Only one question, with Beeopola, is not possible export the song to a BEEP Basic list, isn't?

Cheers
Reply
#4
No, you can't export to a list of BEEPs - Beepola doesn't support making tunes just for the beeper, you can only use one of the provided multi-channel beep engines.
Reply
#5
There´s a program that transforms "MIDI" to "BEEP":

http://matt.west.co.tt/music/midibeep/


I've tried it and it works well
Reply
#6
Heh heh - that's quite impressive! I might play around with that a bit myself!
Reply
#7
JBGV Wrote:There´s a program that transforms "MIDI" to "BEEP":

http://matt.west.co.tt/music/midibeep/


I've tried it and it works well

Wow, thanks a lot! I've tried it and works impressibly well Smile

Cheers
Reply
#8
Hello ¡

Works perfectly (THANKS !!).


I have a few questions about Beepola:

1) How works the option "Exit after every note (for in-game music)" ?


I want to put the call to sub in the main loop of the game. I only get sound the first note again and again.


2) To make sure set the player and song to use an area of memory which won't interfere with the program, I try to put the asm code beepola generated in the program itself:

(I remove previously the colons at the end of the label before the EQUs http://www.boriel.com/forum/post2183.htm...pola#p2183 )



Code:
(...)

    ASM
    PUSH IX
    END ASM

    RANDOMIZE USR @OLE

    ASM
    POP IX
    END ASM

END SUB






OLE:

ASM

; *****************************************************************************
; * The Music Box Player Engine
; *
; * Based on code written by Mark Alexander for the utility, The Music Box.
; * Modified by Chris Cowley
; *
; * Produced by Beepola v1.06.01
; ******************************************************************************

START:
                          LD    HL,MUSICDATA         ;  <- Pointer to Music Data. Change
                                                     ;     this to play a different song
                          LD   A,(HL)                         ; Get the loop start pointer
                          LD   (PATTERN_LOOP_BEGIN),A
                          INC  HL
                          LD   A,(HL)                         ; Get the song end pointer
                          LD   (PATTERN_LOOP_END),A
                          INC  HL

(... etc ...)

This also works well, but can you put more than one song in this way??


THANKS AND REGARDS
Reply
#9
1. I've not used that option, so I'm not really sure. I figured you'd just keep calling the routine over and over and it would automatically play the 'next' note, but maybe there's more to it than that?

2. I don't see any reason why not providing you can also find a safe area of memory to load the extra song data. When you export 'extra' songs it doesn't produce any ASM (because there's no player), just a bunch of notes to feed the existing player. You could load those files separately or maybe find a way to turn them into lines of .defb to include them in your source.
Reply
#10
Hello, first thank you, Ltee. Big Grin


Quote:1. I've not used that option, so I'm not really sure. I figured you'd just keep calling the routine over and over and it would automatically play the 'next' note, but maybe there's more to it than that?
1. I thought that but it always sounds the same note, I'm doing something wrong.

Quote:2. I don't see any reason why not providing you can also find a safe area of memory to load the extra song data. When you export 'extra' songs it doesn't produce any ASM (because there's no player), just a bunch of notes to feed the existing player. You could load those files separately or maybe find a way to turn them into lines of .defb to include them in your source.
2. You're right, i can find a safe area of memory to load the extra song data, anyway I will try to include the DEFB in the code, I'm learning ASM and will be a good exercise. :roll:

I'm experimenting with a game for the CSSCGC 2011 :oops:
Reply
#11
While reading again this thread, I've realized how much talent is here!! 8)
With the arrival of ZX Next and ZX Uno the retroscene is back and stronger than ever. I wonder were all you are guys! :|
Reply


Forum Jump:


Users browsing this thread: 3 Guest(s)