jetsetdanny Posted January 12 Report Share Posted January 12 Overview The objective of this document is to allow, and make it easy for, authors of JSW games to code their own in-game music for games using the JSW128 and JSW64 game engines. I have prepared one document, but I will post it in several parts to make it easier to read and to reply to the various issues I intend to discuss. Credits The following information draws heavily on (and at times quotes directly) information contained in John Elliott’s document “Jet Set Willy 128: Music format” and in his JSWED v2.3.7 manual (a PDF file that can be found in JSWED’s installation folder). Richard Hallas’s document “A Miner Triad. Music in Jet-Set Willy and both versions of Manic Miner” was used for the JSW48 chart tone. Andrew Broad’s specialised program SPECSAISIE was used to obtain the basic tone chart for the 128K games as equivalent to the basic tone chart of the 48K games. Introduction People interested in creating new JSW games using JSWED will likely design games using either the classic 48K game engine (JSW48) or one of the 128K game engines, i.e., JSW128 or JSW64. How to code music for JSW48 games was described very clearly by Richard Hallas in his document mentioned above. More useful information can also be found in this thread. This document discusses the coding of title-screen and in-game music in games using the JSW128 and JSW64 game engines. Most of the information presented here has already been available for a long time in John Elliott’s documents credited above. My aim is to present it in a slightly different way — which I hope makes it easier to understand what should and what can be done. I also share my own experiences and practices from coding the 128K music. Music in JSW128 and JSW64 games is played by default via a built-in music player by Ian Collier. This music player can be replaced in JSWED with Soundtracker by BZYK (Piotr Baczkiewicz). I have had no experience with Soundtracker, so this document refers exclusively to the default music player. My own experiences with coding 128K music relate to two games by Hervé Ast released in 2025. Hervé kindly allowed me to contribute musical arrangements to them. For “Welcome to Willy’s Fun Park!”, I composed both the title-screen tune and the in-game tune (the same in all rooms). In “Willy in the Islands of Mystery. Part II: The Temple”, I expanded my work significantly: I composed multiple in-game tunes and modified the game engine to allow each room to have its own tune, resulting in different tunes in various rooms. SymbolShift, Jet Set Willie, Hervé AST and 1 other 4 Quote Link to comment Share on other sites More sharing options...
jetsetdanny Posted January 12 Author Report Share Posted January 12 Coding the 128K music From my perspective, the simplest way to understand 128K music in JSW games is to consider each tune as having a 6-byte header and three channels of music. The header contains the addresses for the start of each channel (three sets of two bytes). Each channel consists of music notes represented by two bytes each: the first byte indicates the note according to the music chart, and the second byte indicates the length of the note. Each channel must be terminated with a value of #FF, which signals the end of the channel. This is a simplified approach that does not fully represent reality, but for me it’s the easiest way to think about 128K music. I will present “the rest of the reality” a little later. The header consists of 6 bytes. The first two show where the start of the first channel of the tune is. The address is shown using the little-endian byte ordering system, where the least significant byte (the "little end") of a multi-byte data value is stored first, at the lowest memory address, making it appear "backwards" to humans. For example, if the first channel starts at #F500, the first two bytes of the tune’s header will be #00 F5. Without checking each one individually, I believe that in the released JSW128 and JSW64 games, the header and the three channels are usually placed together. The default in-game tune in Jet Set Willy 128 starts at #F700 in Memory Bank 0. The header occupies #F700–#F705; the first channel spans #F706–#F736; the second channel spans #F737–#F767; and the third channel spans #F768–#F7AA. In this case, the in-game tune (header plus three channels) forms a continuous block of data. Please note that it does not have to be like this. Since the header defines the start of each channel, the header and channels could be placed anywhere in memory, as long as they are, or end up being, in Memory Bank 0 at runtime. By “end up being”, I mean that the tune can reside in room data stored in another Memory Bank (1, 3, 4, or 6), but when the room is displayed, its data is copied to the current-room buffer in Memory Bank 0, so that the music player effectively uses the data from Memory Bank 0 during its operation. Each note has two bytes. The first byte is the note value according to the music chart. Here is the basic tone chart for 128K games, alongside the 48K equivalents. Generally speaking, you will not need the 48K values if you are coding 128K music, unless you want to use a tune already coded for a 48K game and convert it into a 128K tune — in that case, the double chart presented below can come in handy. Note Value in 128K game Value in 48K game X C 54 10 B 53 11 # 52 12 A 51 13 # 50 14 G 4F 16 # 4E 17 F 4D 18 E 4C 19 # 4B 1B D 4A 1D # 49 1F High C 48 20 B 47 22 # 46 24 A 45 26 # 44 28 G 43 2B # 42 2D F 41 30 E 40 33 # 3F 36 D 3E 39 # 3D 3C Top C 3C 40 B 3B 44 # 3A 48 A 39 4C # 38 51 G 37 56 # 36 5B F 35 60 E 34 66 # 33 6C D 32 73 # 31 79 Middle C 30 80 B 2F 88 # 2E 90 A 2D 99 # 2C A2 G 2B AB # 2A B4 F 29 C0 E 28 CC # 27 D8 D 26 E6 # 25 F2 Bottom C 24 FF B 23 0 # 22 A 21 # 20 G 1F As for note lengths (the second byte of each note), John Elliott explains that the unit used is the “jiffy” (1/50 of a second). In practice, I chose different values for what I call the basic unit depending on the intended speed of the tune. For example: The title-screen tune in Willy in the Islands of Mystery. Part II: The Temple is a slow-rolling piece of music. In this case, I used a value of #18 (24 in decimal) for what I call the “basic unit” of the tune — essentially, a single note. Logically, I then used the following values for notes of other lengths: Half-note: #0C Double-length: #30 Triple-length: #48 Quadruple-length: #60 Hextaple-length: #90 At the other end of the “tune-speed spectrum” is the lively tune playing, for example, in the room “The Blades” (012) in the same game. I used #0A (10 in decimal) as my “basic unit” or single-length note and, correspondingly, the following values for the other lengths: #14 for double-length notes (20 in decimal) #1E for triple-length notes (30 in decimal) #50 for eight-times-length notes (80 in decimal). My advice for anyone wanting to code a tune would be to establish what the best value is for a single note (depending on how fast the tune is supposed to play) and go from there. Once you get this right, establishing the code value of the half-length and multiple-length notes is pure mathematics. Jet Set Willie 1 Quote Link to comment Share on other sites More sharing options...
Coding the 128K music From my perspective, the simplest way to understand 128K music in JSW games is to consider each tune as having a 6-byte header and three channels of music. The header contains the addresses for the start of each channel (three sets of two bytes). Each channel consists of music notes represented by two bytes each: the first byte indicates the note according to the music chart, and the second byte indicates the length of the note. Each channel must be terminated with a value of #FF, which signals the end of the channel. This is a simplified approach that does not fully represent reality, but for me it’s the easiest way to think about 128K music. I will present “the rest of the reality” a little later. The header consists of 6 bytes. The first two show where the start of the first channel of the tune is. The address is shown using the little-endian byte ordering system, where the least significant byte (the "little end") of a multi-byte data value is stored first, at the lowest memory address, making it appear "backwards" to humans. For example, if the first channel starts at #F500, the first two bytes of the tune’s header will be #00 F5. Without checking each one individually, I believe that in the released JSW128 and JSW64 games, the header and the three channels are usually placed together. The default in-game tune in Jet Set Willy 128 starts at #F700 in Memory Bank 0. The header occupies #F700–#F705; the first channel spans #F706–#F736; the second channel spans #F737–#F767; and the third channel spans #F768–#F7AA. In this case, the in-game tune (header plus three channels) forms a continuous block of data. Please note that it does not have to be like this. Since the header defines the start of each channel, the header and channels could be placed anywhere in memory, as long as they are, or end up being, in Memory Bank 0 at runtime. By “end up being”, I mean that the tune can reside in room data stored in another Memory Bank (1, 3, 4, or 6), but when the room is displayed, its data is copied to the current-room buffer in Memory Bank 0, so that the music player effectively uses the data from Memory Bank 0 during its operation. Each note has two bytes. The first byte is the note value according to the music chart. Here is the basic tone chart for 128K games, alongside the 48K equivalents. Generally speaking, you will not need the 48K values if you are coding 128K music, unless you want to use a tune already coded for a 48K game and convert it into a 128K tune — in that case, the double chart presented below can come in handy. Note Value in 128K game Value in 48K game X C 54 10 B 53 11 # 52 12 A 51 13 # 50 14 G 4F 16 # 4E 17 F 4D 18 E 4C 19 # 4B 1B D 4A 1D # 49 1F High C 48 20 B 47 22 # 46 24 A 45 26 # 44 28 G 43 2B # 42 2D F 41 30 E 40 33 # 3F 36 D 3E 39 # 3D 3C Top C 3C 40 B 3B 44 # 3A 48 A 39 4C # 38 51 G 37 56 # 36 5B F 35 60 E 34 66 # 33 6C D 32 73 # 31 79 Middle C 30 80 B 2F 88 # 2E 90 A 2D 99 # 2C A2 G 2B AB # 2A B4 F 29 C0 E 28 CC # 27 D8 D 26 E6 # 25 F2 Bottom C 24 FF B 23 0 # 22 A 21 # 20 G 1F As for note lengths (the second byte of each note), John Elliott explains that the unit used is the “jiffy” (1/50 of a second). In practice, I chose different values for what I call the basic unit depending on the intended speed of the tune. For example: The title-screen tune in Willy in the Islands of Mystery. Part II: The Temple is a slow-rolling piece of music. In this case, I used a value of #18 (24 in decimal) for what I call the “basic unit” of the tune — essentially, a single note. Logically, I then used the following values for notes of other lengths: Half-note: #0C Double-length: #30 Triple-length: #48 Quadruple-length: #60 Hextaple-length: #90 At the other end of the “tune-speed spectrum” is the lively tune playing, for example, in the room “The Blades” (012) in the same game. I used #0A (10 in decimal) as my “basic unit” or single-length note and, correspondingly, the following values for the other lengths: #14 for double-length notes (20 in decimal) #1E for triple-length notes (30 in decimal) #50 for eight-times-length notes (80 in decimal). My advice for anyone wanting to code a tune would be to establish what the best value is for a single note (depending on how fast the tune is supposed to play) and go from there. Once you get this right, establishing the code value of the half-length and multiple-length notes is pure mathematics.
jetsetdanny Posted January 12 Author Report Share Posted January 12 Tune Channels There are three channels in a 128K tune, but you do not have to use all three. You can use just one or two if desired. For channels that are unused, the header still needs to point to a memory location containing #FF (end of tune). For example, the default in-game tune in Jet Set Willy 64 (JSW64) has only one channel. Its header at #F700 in Memory Bank 0 reads: #06 F7 51 F7 52 F7. This means: First channel data starts at #F706; Second channel data starts at #F751 (#FF - it's mute); Third channel data starts at #F752 (#FF - it's mute). In this case, both the addresses #F751 and #F752 hold the same value: #FF. Furthermore, the address #F751 seems to be the end of the first channel, in addition to also being “the entire” second channel (pointing to it as being muted). I believe this same address could also serve as the end of the third channel. If anyone knows of any reason why having a separate ending for the (mute) third channel in the form of another #FF value elsewhere would have any advantages over ending all three channels at the same address, please share this information with us. In most of the tunes I coded, I used the following configuration: First channel: main melody Second channel: supporting notes (chords) harmonising with the main melody Third channel: main melody repeated This approach allowed me to balance the prominence of the main melody against the accompaniment. In practice, I only needed two chunks of data in addition to the header: one chunk for the first and third channels (the main melody) and another for the second channel (the chords). The header pointed to the same address for both the first and third channels and to a different address for the second channel. This method was satisfactory for me, but it is by no means the only way to structure the channels. You may certainly find other arrangements that take full advantage of the three available channels or create different textures for the melody and accompaniment. The reason for my solution described above was also that I did not use the function which sets the volume of the tune. So, let me now describe “the full reality” of the music player in JSW128 and JSW64. Jet Set Willie 1 Quote Link to comment Share on other sites More sharing options...
jetsetdanny Posted January 12 Author Report Share Posted January 12 Additional instructions I stated before that the clearest picture of a tune in a JSW128 or JSW64 game is that each note is defined by two bytes: the first being the note on the music scale, and the second being its length. This is true, but the “more technical” reality is that, as John Elliott put it, “each channel is a string of commands, which may be followed by one or two parameter bytes.” So, going back to what I described, you could also say that the first byte of each note — if it is a value between #01 and #F8 — is an instruction which means “Play a note,” at the same time defining the tone of this note, and it has to be followed by a second byte (the parameter byte) defining the length of the note, as discussed before. If the first byte is a value outside the #01–#F8 range, then it is an instruction that has another meaning. The meanings of these instructions are as follows: #00 – rest, i.e., don’t play any note, remain silent. The second byte (the parameter byte) is the length of the rest (silence). So, if the “basic unit” of your tune is, for example, #10, and you want silence to last for three times the length of that basic unit, your values in the code will be: #00 #30. #F9 – set the waveform effect (like "W" in the PLAY command). The parameter byte is the waveform <-- I am quoting this from John Elliott’s description. I didn’t use this instruction in my coding and cannot say anything more about it. I believe the “PLAY command” refers to the PLAYTUNE app, but I didn't use it, so I cannot say anything about it. #FA – set the waveform period (like "X" in the PLAY command). The parameter byte is the duration; I don’t know the units. <-- Again, quoting John Elliott. I didn’t use this instruction, and I cannot say anything more about it. #FB – set the volume (like "V" in the PLAY command). The parameter byte is the volume. I didn’t use this instruction myself, but I can say the following: the “standard” tunes present in previously released JSW128 and JSW64 games did use this parameter. For example, the default in-game tune in JW128 games has three channels, and it sets the following values of loudness for each of them: #07 for the first channel (via an instruction at the very start of the channel, before the first note, reading #FB 07), #06 for the second channel (#FB 06), and #08 for the third channel (#FB 08). I inserted this tune in the room called “The Wings” (057) in Willy in the Islands of Mystery. Part II: The Temple (since this room is from Hervé’s game Jet Set Willy in Paris, using the JSW128 game engine and its default in-game tune). In comparison with the tunes I coded for The Temple — which do not have the volume defined via an instruction, so you could say that they play at a default volume — this tune, with channel volumes defined as #07, #06, and #08, sounded very quiet, much too quiet in comparison with my tunes. I therefore adjusted the volume of the three channels to #0B, #0A, and #0C, respectively, and with this, I felt the tune sounded similar in loudness to my tunes that do not have a custom-defined volume. I hope this gives you an idea of what values can be used to set the volume, but of course, experimenting yourself and a trial-and-error approach is probably the best way to establish what suits you most. John Elliott also mentions that: “If you want to use a waveform effect, you should first set the volume to 16, then set the effect, and then the period.” I did not use the waveform effect, so I cannot say any more about it. #FC – I’ll just quote what John says (he gives decimal values; I have converted them to Hex): "djnz" – used to repeat sections. The first and second parameters should both be set to the number of repeats; the third is the relative address of the start of the section: #FC: Section starts at the DJNZ #FB: Section starts 1 byte before the DJNZ #FA: Section starts 2 bytes before the DJNZ ... #80: Section starts 124 bytes before the DJNZ John warns: “I've only seen this used in title-screen tunes. I don't know how it would get on in an in-game tune.” I have tried using this feature for an in-game tune — it could be very helpful if the length of the data you can dedicate to the tune is limited — but I couldn’t get it to work well. I am not saying it doesn’t work, only that I haven’t been able to apply it successfully. I would certainly be interested in hearing the experience of those who manage to achieve it. #FD – return from a sub-tune; it doesn’t have a parameter byte, it’s a one-byte instruction. #FE – play a “sub-tune,” which must end with a “return” instruction (#FD). It has two parameter bytes which are the address of the sub-tune, in standard Z80 format (little-endian). I can see this is applied in the default in-game tune in Jet Set Willy 64 (JSW64). The header of the tune is at #F700–#F705 (in Memory Bank 0). The first channel starts at #F706, and it begins with the instruction #FE 32 F7. This instruction means that a sub-tune is to be played that starts at #F732. Strangely enough, this sub-tune starts at #F732–#F734 with another #FE instruction to play yet another sub-tune starting at #F742. That sub-tune ends with an #FD at #F741. I think — just from looking at the code, without investigating it in detail — that what happens later is that, after playing the sub-tune starting at #F742, the program goes back to #F735, where it starts to play the “original” sub-tune that was called at #F706–#F708. This one, in turn, ends with an #FD instruction at #F750. I assume that after this the player goes back to the initial part of the first channel, i.e., #F709. There, the sub-tune at #F742 is called again, and I assume the program returns from it to #F70C, where it “finally” plays the regular first channel (no more sub-tunes). This sounds quite complicated, but the main point is that it demonstrates that the #FE — play a “sub-tune” instruction — certainly works in in-game tunes. As mentioned before, this tune has only one channel (channels 2 and 3 are immediately terminated by their start addresses pointing to locations holding a value of #FF each), so calling sub-tunes is probably easier to code than if it had three channels and sub-tunes were called on each channel — although I don’t see any theoretical reason why that wouldn’t be possible. #FF – this is the instruction terminating the channel. The tune on each channel must end with this instruction. Jet Set Willie 1 Quote Link to comment Share on other sites More sharing options...
jetsetdanny Posted January 12 Author Report Share Posted January 12 The actual coding I coded all the tunes in Hervé’s “Welcome to Willy’s Fun Park!” and “Willy in the Islands of Mystery. Part II: The Temple” manually. I coded melodies that I knew — that sounded in my head, so to speak. I would play a note or a sequence of notes on a keyboard — either an actual piano keyboard or a virtual one — determine which note goes next, convert it to a Hex value according to the basic tone chart, determine (by ear) how long it should be, determine a Hex value for its length, and input it in JSWED’s Hex editor at the place in memory where I was coding that tune (more about this later). Then I would play the following note or notes and repeat the process. I usually coded the main melodic line first and then coded the accompaniment (the chords) in pretty much the same way, but this time determining a different length for the chords, because in “my” music each chord typically sounds over a few notes of the main melodic line. I used this virtual piano for this work. Please note that I’m not affiliated with them and have no related interests whatsoever; I’m only sharing my experience, and there are certainly lots of other websites out there that would let you do the same thing. This manual coding of the music was a very slow and time-consuming process. It was probably partly because of my ineptitude — from time to time, I made mistakes and had to correct them later. A lot of the time I didn’t get the length of the notes right and had to go back and correct them. So yes, it was a slow, protracted and error-prone process, but also a rewarding one at the end. It seemed a drudgery at times, but I’m very content with the result and believe it was worth it. It’s up to you to judge, of course. I would assume there are better ways of coding the music, but since I didn’t use them, I cannot share my experience about them. I will just mention that: John Elliott offers links to two apps: MAKETUNE and PLAYTUNE. The first one apparently allows you to “write” a tune. I didn’t try using either one, but if someone has, do let us know how it went. There is a user on this forum who has made an app of his own that facilitates the creation of 128K tunes. I cannot give any details right now, but perhaps he will be kind enough to share it with us. Jet Set Willie 1 Quote Link to comment Share on other sites More sharing options...
jetsetdanny Posted January 12 Author Report Share Posted January 12 Default tunes in JSW128 and JSW64 games Games using the JSW128 and JSW64 game engine have three tunes by default: The title-screen tune It can occupy up to 2774 bytes (the total length, including the 6 bytes of the header and the data of all three channels). By default, JSWED saves it as the last code block of the TAP file, called “title.tun”, which gets loaded from the address 29994 (#752A). If you want to edit the title-screen tune in JSWED’s Hex Editor, it starts at #F52A in Memory Bank 7. The in-game tune It can occupy up to 256 bytes (the total length, including the 6 bytes of the header and the data of all three channels). By default, JSWED saves it as a code block called “main.tun”, which gets loaded from the address 63232 (#F700). If you want to edit the in-game tune in JSWED’s Hex Editor, it starts at #F700 in Memory Bank 0. The cheat-mode tune (for info about how to activate the cheat mode, have a look here) It can occupy up to 254 bytes (the total length, including the 6 bytes of the header and the data of all three channels). By default, JSWED saves it as a code block called “cheat.tun”, which gets loaded from the address 63746 (#F902). If you want to edit the cheat-mode tune in JSWED’s Hex Editor, it starts at #F902 in Memory Bank 0. Here’s what it looks like in ZX-Blockeditor: JSWED has functionality in its “Music” section for importing and exporting these three tunes: This can be useful if you want to use tunes that already exist, either in another game file or saved as separate files in the .TUN format. When this subject was discussed back in October 2005 on the MM&JSW Yahoo! Group, John Elliott gave the following information about the .TUN format tunes that existed back then: “To my knowledge, the following tunes exist: Cannonball (SKY), programmed by Ian Collier. Never used in any JSW, and may be too big to fit. <-- My comment: It does fit. It was used as the title-screen tune in Jet Set Willy: Mind Control. Danza (SKY), programmed by Ian Collier. The original JSW128 title-screen tune. <-- My comment: It was used as the title-screen tune in Jet Set Willy: The 2010 Megamix. Grace (SKY), programmed by Ian Collier. The original JSW128 in-game tune, which I don't think anyone liked. The Moonlight Sonata (Beethoven), programmed by Ian Collier. The current JSW128/JSW64 title-screen tune. Blank (a tune that only plays silence) – see message 1331 and replies. In The Hall Of The Mountain King (Grieg), programmed by me (one channel only). The current JSW128/JSW64 in-game tune; see message 1348. Fifteen Men on the Dead Man's Chest (?), programmed by me – see message 3506 and replies. <-- My comment: It was used as the in-game tune in Jet Set Willy: The 2010 Megamix. The Blue Danube (Strauss), programmed by me. Used by JSW64:MM as the title-screen tune.” To this, Andrew Broad added: "A Wolf at the Door (Radiohead), programmed by me. Used by Party Willy 128 as the title-screen tune. <-- My comment: Party Willy (JSW128 version) can be found here if you want to check it out. Where Bluebirds Fly (Radiohead), programmed by me. Used by Party Willy 128 as the in-game tune. I encoded both these tunes as 48K JSW tunes, and used SPECSAISIE 1.3's JSWtoJSW128Tunes function to convert them to 128K tunes." I did try using SPECSAISIE to convert 48K tunes to 128K tunes, but I was not satisfied with the results. It is certainly able to convert the basic chart values, but it can’t cope with the length of the notes, in my opinion. If you try it and have a different experience, do let us know. For what it’s worth, attached to this message are some of the .TUN format tunes mentioned above. I did try to extract the tunes coded by Andrew Broad for Party Willy (JSW128 version) using JSWED's export/import functionality mentioned above, but continued to get an error message when trying to import the exported tunes. tunes.zip Jet Set Willie 1 Quote Link to comment Share on other sites More sharing options...
Default tunes in JSW128 and JSW64 games Games using the JSW128 and JSW64 game engine have three tunes by default: The title-screen tune It can occupy up to 2774 bytes (the total length, including the 6 bytes of the header and the data of all three channels). By default, JSWED saves it as the last code block of the TAP file, called “title.tun”, which gets loaded from the address 29994 (#752A). If you want to edit the title-screen tune in JSWED’s Hex Editor, it starts at #F52A in Memory Bank 7. The in-game tune It can occupy up to 256 bytes (the total length, including the 6 bytes of the header and the data of all three channels). By default, JSWED saves it as a code block called “main.tun”, which gets loaded from the address 63232 (#F700). If you want to edit the in-game tune in JSWED’s Hex Editor, it starts at #F700 in Memory Bank 0. The cheat-mode tune (for info about how to activate the cheat mode, have a look here) It can occupy up to 254 bytes (the total length, including the 6 bytes of the header and the data of all three channels). By default, JSWED saves it as a code block called “cheat.tun”, which gets loaded from the address 63746 (#F902). If you want to edit the cheat-mode tune in JSWED’s Hex Editor, it starts at #F902 in Memory Bank 0. Here’s what it looks like in ZX-Blockeditor: JSWED has functionality in its “Music” section for importing and exporting these three tunes: This can be useful if you want to use tunes that already exist, either in another game file or saved as separate files in the .TUN format. When this subject was discussed back in October 2005 on the MM&JSW Yahoo! Group, John Elliott gave the following information about the .TUN format tunes that existed back then: “To my knowledge, the following tunes exist: Cannonball (SKY), programmed by Ian Collier. Never used in any JSW, and may be too big to fit. <-- My comment: It does fit. It was used as the title-screen tune in Jet Set Willy: Mind Control. Danza (SKY), programmed by Ian Collier. The original JSW128 title-screen tune. <-- My comment: It was used as the title-screen tune in Jet Set Willy: The 2010 Megamix. Grace (SKY), programmed by Ian Collier. The original JSW128 in-game tune, which I don't think anyone liked. The Moonlight Sonata (Beethoven), programmed by Ian Collier. The current JSW128/JSW64 title-screen tune. Blank (a tune that only plays silence) – see message 1331 and replies. In The Hall Of The Mountain King (Grieg), programmed by me (one channel only). The current JSW128/JSW64 in-game tune; see message 1348. Fifteen Men on the Dead Man's Chest (?), programmed by me – see message 3506 and replies. <-- My comment: It was used as the in-game tune in Jet Set Willy: The 2010 Megamix. The Blue Danube (Strauss), programmed by me. Used by JSW64:MM as the title-screen tune.” To this, Andrew Broad added: "A Wolf at the Door (Radiohead), programmed by me. Used by Party Willy 128 as the title-screen tune. <-- My comment: Party Willy (JSW128 version) can be found here if you want to check it out. Where Bluebirds Fly (Radiohead), programmed by me. Used by Party Willy 128 as the in-game tune. I encoded both these tunes as 48K JSW tunes, and used SPECSAISIE 1.3's JSWtoJSW128Tunes function to convert them to 128K tunes." I did try using SPECSAISIE to convert 48K tunes to 128K tunes, but I was not satisfied with the results. It is certainly able to convert the basic chart values, but it can’t cope with the length of the notes, in my opinion. If you try it and have a different experience, do let us know. For what it’s worth, attached to this message are some of the .TUN format tunes mentioned above. I did try to extract the tunes coded by Andrew Broad for Party Willy (JSW128 version) using JSWED's export/import functionality mentioned above, but continued to get an error message when trying to import the exported tunes.
jetsetdanny Posted January 12 Author Report Share Posted January 12 Expanding the in-game music Now, something I really wanted to do in Hervé’s games (and only had time to work on it for Willy in the Islands of Mystery. Part II: The Temple) was to “install” different tunes in different groups of rooms, much like I did in a number of 48K JSW games that I collaborated on over the last few years. I managed to achieve it in The Temple, and I will now describe my solution, which could certainly be applied (as-is or with modifications) to other games if the authors so wish. The address of the in-game tune in The Temple is defined in offsets #B6 and #B7 of the first page of the room data. In other words, the address of the tune occupies the first two characters of the room name. You can see this if you open the game in JSWED. This obviously means that these first two characters cannot be used for the room name. The code that prints the room names is adjusted accordingly: it prints the room name starting at the third (not first) column of the line where it appears, and the length of the room name it prints is 29 characters, not 32 (it could be 30, but the last character of each room name is used for the colour of the room name in this room). So, offsets #B6 and #B7 of the first page of the room data hold the address of the in-game tune for this room. For example, the data for room 00, “The Comet”, starts at #C000 in Memory Bank 1. At #C0B6 and #C0B7, you’ll find #08 and #DB. This means that the in-game tune in this room starts at #DB08. This is an example of a tune that resides in Memory Bank 0. The space at #DB00–#DBFF was free, and I used it for in-game tunes. Now, let’s have a look at room 02, “The Grail”, whose data starts at #C800 in Memory Bank 1. The values at #C8B6 and #C8B7 are #E0 and #81, so the in-game tune for this room starts at #81E0. In JSW64 games (at least in the variant that The Temple uses), each room occupies four pages of data (4 × 256 bytes). When the room is initiated, these pages are copied to the current-room buffer in Memory Bank 0 from #8000 to #83FF. So, when The Grail is initiated, its data from #C8.. in Bank 1 is copied to #80.. in Bank 0, its data from #C9.. in Bank 1 is copied to #81.. in Bank 0, and so on for the remaining two pages. This means that while the two bytes in the room names show the address of the start of the in-game tune as #81E0, this effectively corresponds to #C9E0 in Bank 1, because data from #C9.. in Bank 1 gets copied to #81.. in Bank 0 when this room is initiated. I hope this is clear. The Grail is also a good example of how the tune data can be divided. Let’s look at the header of its in-game tune at #C9E0–#C9E5: #E6 81 C9 DB E6 81. This means that the data of the first and third channels start at #81E6, while the data of the second channel starts at #DBC9 (all in Memory Bank 0). The reason for this arrangement is that there wasn’t enough room in the room data for The Grail to store the whole in-game tune. So I put the (identical) data for the first and third channels in the room data, starting at #C9E6 in Bank 1 (when the room is initiated, this data gets copied to #81E6 in Bank 0), while the data for the second channel starts at #DBC9 in Bank 0 (there was spare space there). All right, so the offsets #B6 and #B7 in the room name show where the header of the tune is, and the actual tune data can either follow the header or be located elsewhere, according to what the header indicates. The in-game music in The Temple also has the following feature: when the player enters a new room (either coming from a previous room or respawning in the same room after losing a life), there is a check for which tune was playing before the player entered this room. If the same tune was playing, it continues from the note where it stopped. If a different tune is playing, it always starts from the first note. All this is achieved as follows. There is a call from the “Initialise the current room” routine at #8965 (in Memory Bank 0) to #FAFE (also in Bank 0), where the following code is placed (to quote the actual opcodes): 2A C9 FE ED 5B B6 80 7D D6 06 6F A7 ED 52 28 04 EB CD C6 FE That translates to: LD HL,(&FEC9) LD DE,(#80B6) LD A,L SUB A,06 LD L,A AND A SBC HL,DE JR Z,04 EX DE,HL CALL #FEC6 Most of this code was kindly provided by John Elliott. I added one part, which I will explain in a second. This code checks the value of addresses #FEC9–#FECA against the value at #80B6–#80B7, to see if the tune in the current room was already playing before entering this room. #FEC9–#FECE is the address to which the game engine (the built-in music player) copies the 6-byte header of the last tune selected. This means that addresses #FEC9–#FECA hold the address of the first channel of the tune (the first two bytes of the tune’s header). Since addresses #80B6–#80B7 hold the address of the header (not of the first channel), I added the instruction SUB A,06 to subtract 6 bytes from the value of #FECA. Thus, the value compared against #80B7 is the header address, not the address of the first channel. If the values checked match, the program jumps four bytes to the code that follows (unrelated to music). If the values do not match, the routine at #FEC6 is called, which changes the in-game tune (and the new tune starts playing from the first note). Thanks to the subtraction I added, the code works, but this solution introduces one serious limitation: the first channel of the tune always has to follow immediately after the tune’s header (so that the difference between the lower bytes in their addresses is exactly 6). So, while in general the tune’s header and the data for each of the three channels could be stored in completely different locations, for this solution to work, the data of the first channel only must follow immediately after the tune’s header (the data for the other two channels can be placed elsewhere in memory). Jet Set Willie 1 Quote Link to comment Share on other sites More sharing options...
jetsetdanny Posted January 12 Author Report Share Posted January 12 Conclusion This concludes my write-up on coding the 128K music. I hope it will be useful to those who wish to code their own tunes 😊. I also look forward to any questions, comments, or suggestions for improving what I have presented here 🙂. Hervé AST and Jet Set Willie 2 Quote Link to comment Share on other sites More sharing options...
Jet Set Willie Posted January 13 Report Share Posted January 13 Thank you a LOT, Danny, for your time consuming efforts to make the JSW(-ED) game developers life much more easier! 😃 May I add few things to your Excellent JSW music reference book? 🙂 On 1/12/2026 at 4:47 AM, jetsetdanny said: #F9 – set the waveform effect (like "W" in the PLAY command). The parameter byte is the waveform <-- I am quoting this from John Elliott’s description. I didn’t use this instruction in my coding and cannot say anything more about it. I believe the “PLAY command” refers to the PLAYTUNE app, but I didn't use it, so I cannot say anything about it. This refers to the BASIC language PLAY command on the 128K Spectrum and the MSX computers. 🙂 Just in case if a Spectrum BASIC programmer wants to try it on the MSX, or vice versa, you will find out that the PLAY command is not exactly the same on them, things are done a bit different way. You might want to refer to the BASIC manuals first. 🙂 Also, reading those BASIC manuals about PLAY command will help you to understand some things here. 🙂 That "W" is "S" on the MSX, by the way. 🙂 On 1/12/2026 at 5:30 AM, jetsetdanny said: Grace (SKY), programmed by Ian Collier. The original JSW128 in-game tune, which I don't think anyone liked. If it is what I think it is, it is a good tune, but it just ends just after it has started and then repeats. Somebody should continue the composing! 🙂 On 1/12/2026 at 5:44 AM, jetsetdanny said: The in-game music in The Temple also has the following feature: when the player enters a new room (either coming from a previous room or respawning in the same room after losing a life), there is a check for which tune was playing before the player entered this room. If the same tune was playing, it continues from the note where it stopped. If a different tune is playing, it always starts from the first note. That´s a great thing to have! 🙂 I don´t know if I will use it in my games, but I think it´s great! 🙂 I will have to read these instructions again several times to consume it properly! 😄 🥩 I will repeat, thank you a LOT, Danny! 🫡 jetsetdanny 1 Quote Link to comment Share on other sites More sharing options...
jetsetdanny Posted January 14 Author Report Share Posted January 14 Thank you for your kind words, Willie! I'm very glad my "essay" may be useful for you 😊. Thank you also for your further comments! I would like to add that if anyone would like to use in their games the music I've coded for Herve's games, please feel free to do so, while I would appreciate your mentioning somewhere (in the Readme or release notes or wherever appropriate/possible) that it was coded by me (Daniel Gromann, for credits I prefer my real name than any possible nicknames). Most of the tunes I coded (with the exception of one, well, perhaps 'one and a half') are tunes composed in real life by someone other than myself. I'm curious if those who have played Willy in the Islands of Mystery. Part II: The Temple have recognised any of them. I would prefer not to reveal their identity for the time being (to give people some time to play the game without this spoiler), but I will gladly do so later on, after more time has passed since the release. Should anyone need to know the identity of any tune right now or in the near future, do let me know here at the forum or by e-mail and I will let you know what it is 😊. If anyone should want to use any of the tunes I've coded, as described above, you can identify where an in-game tune starts by looking at the value of the offsets #B6 and #B7 of the first page of the room data (in other words, what would normally be the first two characters of the room name) and you will know where the header is. Then you would need to look at the header and see where the data for each of the three channels is placed. And then copy it to your game (probably manually, or however you manage to transfer these bytes) and adjust the header in your game to show correctly show the addresses of the three channels where their data will end up in your game. For the title-screen tune and the tunes placed where the in-game tune and cheat-mode tune reside by default (the latter two starting at #F700 and at #F902, respectively, in both cases in Memory Bank 0), you could try using the JSWED Music editor to export them. I wouldn't be quite sure if it would work though. Without checking it, I would *think* that if JSWED generally works well in this respect, it *should* work for the title-screen tune and the in-game tune in Welcome to Willy's Fun Park! because in that game new music was coded in place of the default tunes and nothing else was modified (I'm not mentioning the cheat-mode tune because it wasn't modified there, it should be the default one). In case of Willy in the Islands of Mystery. Part II: The Temple, the modifications were more far-reaching. A part of the space in memory meant for the title-screen tune is occupied by other data (the title-screen tune does not occupy the whole length of that space, so the rest of it was used for other data). Where the in-game and cheat-mode tune normally are there may be more than one tune, I used that space to the maximum as well. So I'm not sure what the JSWED music editor would do about these, whether it would be able to export these tunes successfully or not. In any case, they can always be copied manually, which would be my preferred solution, actually. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.