Jump to content

Recommended Posts

8 hours ago, spudje said:

Can somebody create an LSD file? Thanks! When I try the LSD creator of GadgetUK, my the tool completely freezes and can't even be killed from the task manager, scary...

This is how I create lsd files for the SD cart.

Save your image in Lynx resolution as image.pcx.

Create  your palette.s file by whatever tool you happen to use.

This info could be extracted from the image - I have not just done this part yet.

Type 'make' and you should have your image.lsd file.

lsd.zip

  • Like 1
Link to comment
Share on other sites

Since a number of people asked about our music driver and its development process,
I will share a couple of details here.
I started off with a simple tool to compute the particular pairs of timer values for the lynx registers
that would match actual notes the closest.
For such tools I always use Python.
This formular gives you the proper frequency of a note n relative to the frequency of a'4.
freq=2**(1/12.0))**(n-49)*a4

I wrote a routine that tries all feasible combinations of timers to get closest to each particular note.

In the end it was even simpler and more straight forward than I anticipated :)
This is part of the output:

n       note    wanted  got     %diff   timer1  timer2
...
49      a4      440.0   440.1   0.0     10      8e
50      a#4     466.2   466.4   0.1     10      86
51      b4      493.9   494.1   0.0     08      fd
52      c5      523.3   523.0   0.0     08      ef
53      c#5     554.4   555.6   0.2     08      e1
...

Unlike with other 8bit machines, the 16bit timers of the Lynx provide basically perfect note matches!
(that is also why Atari2600 music is quite often VERY much out of tune)

The magic of Lynx audio sits in the feedback and shift registers.
While their concept is easy enough to understand when you are used to such technical descriptions,
it is not very (or: at all) intuitive to predict which settings will produce what kind of wave form.
I wrote another graphical tool to experiment with that. Sources provided by Karri and Sage helped alot in confirming
the actually mechanics behind the feedback+shift.
Below is a snapshot of my tool (Chipper has a rather similar view).
It also allows you to play that waveform in a loop (and very important: a STOP button to bring that madness to an end ;-)
This was to get familiar with how sound on Lynx works at all.
If you are interested I will continue with the data- and instrument format of the driver in the following.

 

 

waveform.png

  • Like 2
Link to comment
Share on other sites

The whole player is 100% chiptune, so no samples are used.
It runs at 60Hz (vblank usually) and takes up a few rasterlines (but there is room for optimisation).
The song data is organised in patterns of 64 rows and 3 channels. We keep the 4th channel for streamed samples for the game SFX (hblank). The title-music does not use it at all.
I constantly work on the song-data-format and even since the deadline it has changed notably again but for Assembloids I stored the song data as such.
1. controlbyte:
- if it is 0xff then this row is finished (go to next of 64 rows per pattern).
- otherwise bits 0+1 contain the channel number the following data is valid for (0..3), so it can be extended to 4 channel music without samples

only channels that actually change have data stored!
- bits 2-7 contain the note to play
you can compute the actual note as this:
datanote=(controlbyte>>2)
note=(datanote)%12-1
octave=(datanote)/12+1
(-1 because 0 will mean 'no instrument')

 

if 1st byte wasn't 0xff a 2nd byte follows in the data:
bit 0-4 contains the instrument number (0...31)
if bit 7 is set, the next byte will be the new volume
if bit 6 is set, the next byte (possibly after volume if required) will be a command/effect

the volume (if required) is a full byte

 

the command byte is again splitted:
if bit 7 is set, bits0-5 will be the new player tempo (number of frames till next row is read)
if bit 6 is set, bits0-5 will be the next pattern to continue the song (so a BREAK or JUMP command )
in fact if the next pattern is set to be 0x3f then this is a mere BREAK command = jump to next pattern
if it is < 0x3f it will jump to the given pattern.

 

This (so I figured) is a VERY compact data arrangement.
Rows with little action are kept short and only things than actually change are stored (ie only volume when required etc).
Fully empty rows only take up 1 byte.
In the end RAM wasn't THAT tight for music as I compressed large parts of the game and kept them packed in RAM to depack on-the-fly. But by now I use a data scheme of storing music column per column and not row by row which can be even shorter. Let me know if you want to know about that, too ;-)
So the music driver counts down frames (depending on the set tempo which can be changed anytime in the song) and then reads
the data for the next row to be played. Up to 3 different instruments with different volumes and different effects can be started. Effects like 'jump to a new pattern' will only be parsed once, of course.
Coming next will be the handling of actual instruments ;-)

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Obviously you have no priorities. So it is not possible to mute one channel when there is many sfx sounds at the same time.

 

What about pause?

 

I really like the idea of just streaming out register values instead of having a player.

 

Is there any limitation on the row length?

Link to comment
Share on other sites

Oh it is not streaming register data. That would be way too large. It uses Instrument definitions. You can easily pause it by not progressing the reading or setting it arbitrarily slow. The sample/sfx player in Assembloids works independently but it could actively mute any channel. For now we set music to 50% level to keep the SFX well audible without fading the music. Our music is arranged in patterns or chunks of 64 rows and 3 channels. The Assembloids game uses 18 of such patterns that are sometimes repeated or re-used. A row takes (iirc) 6 frames.

Link to comment
Share on other sites

The way most demo-scene people compose music for retro plattforms often referred to as chiptunes usually differs alot from the
approach you see in MIDI files. Many classic machines were quite limited in the number of channels so several instruments occupy the same channel and are interweaved. It is one of the main ingredients to 'chiptune art' to do this in such a way as to mimic more things going on in parallel as there actually are. This and of course the rapid change of wave forms, frequencies, bascially everything to artificially 'construct' a proper effective wave form.


The image shows the start of Jammer's Assembloids music in a typical track/pattern music editor.
C-4 05 A07 means:  Note C, 4th octave, Instrument #5 and Tempo 7 (next row every 7 frames).
You can see channel 1 only toggles instruments, not notes. This is the percussion! We will get to that.
Channel 2 shows are variety of both, whereas channel 3 merely plays instrument #10 which is basically 'silence' and only used at the very start to initialize that channel.
The data stream of the data shown looks like this:
(small part of a hexdump)

00000000  64 45 87 35 01 36 0a ff  64 05 ff 5d 02 ff 64 05  |dE.5.6..d..]..d.|
00000010  ff 64 04 35 03 ff ff 64  05 59 02 ff 64 05 55 01  |.d.5...d.Y..d.U.|

The first row is this:
64 45 87 35 01 36 0a ff
You can try to decipher it with the information given in the posts above :)
You can see in the pattern image that the 5th row is not occupied by any instrument, so it is completely empty.
in the data stream this shows up as 0xff directly after the 0xff that signaled the end of the row before.

The hexdump is of my data format, the editors/trackers use a very different one, but

my tools can read and work with the output of such tracker-editors directly.
Now the tricky part, the instruments themselves.
Without going too much into the coding details of implementation, this is what happens in short.
When the pattern-reader hits a new note it will decipher its note, octave, volume, effect.
Then the player starts playing that instrument. It checks which instrument number should be played, then fetches the address offset of the corresponding instrument definition and starts interpreting THAT.

For example this is one of the arpeggio instruments:

instrument_arp037A
.byte 2, OFFSET | WAVE | MODE, 10, %00000000, %00000101, INTEGRATE
arp037A_loopstart
.byte 2, OFFSET, 13
.byte 2, OFFSET, 17
.byte 2, OFFSET, 20
arp037A_loopend
.byte 2, OFFSET | LOOP, 10, arp037A_loopend - arp037A_loopstart

The instrument description works as following:
1st byte = number of frames to wait till the next change comes
2nd byte are different flags what things are changed this time (here: OFFSET, WAVE and MODE), followed by the correponding bytes.
Now how to read it. The BASE frequency is set by the not to play.
The OFFSET is relative to that BASE to tune the instrument. As you know different SHIFT and FEEDBACK settings produce different 'audible periods'. WAVE sets the SHIFT and FEEDBACK registers (hence 2 bytes, yes only 8 bit for us) and
MODE decides to use  integrate or square mode.
You can nicely see that the Arpeggio then changes the OFFSET = fine tune of the note's frequency very 2 frames.
Some cool feature is the LOOP option in the last row. It tells the player to repeat the instrument-description from a certain point on. So it keeps 'whabbering' forever (until another instrument plays on that channel, or the volume is faded).
This is how one of the snares looks like, here the note from the music editor plays no role as the instrument itself
sets the frequency (= timers) via 'NOTE'.

instrument_mainsnare
.byte 1, WAVE | VOL, %00000000, %00000000,127
.byte 1, NOTE | VOL | WAVE | MODE, 61, %00000000, %11110000,127,SQUARE
.byte 1, NOTE | VOL | WAVE, 50, %00000000, %00001000, 72
.byte 1, NOTE | VOL | WAVE, 48, %00000000, %00001001, 71
.byte 1, NOTE | VOL | WAVE, 46, %00000000, %00001000, 70
.byte 1, NOTE | VOL | WAVE, 44, %00000000, %00001001, 69
.byte 1, NOTE | VOL | WAVE, 42, %00000000, %00001000, 68
.byte 1, NOTE | VOL | WAVE, 40, %00000000, %00001001, 67
.byte 1, NOTE | VOL | WAVE, 38, %00000000, %00001000, 66
.byte 1, NOTE | VOL | WAVE, 36, %00000000, %00001001, 65
.byte 1, NOTE | VOL | WAVE, 34, %00000000, %00001000, 64
.byte 1, VOL, 0
.byte 0

In the hands of some super talentend chip tune composer this approach allows all sorts of sounds like you hear in Assembloids (bass, kickdrum, hihat, snare etc).

This little demo uses an older version of the player but with the same method for instruments:
Funky X-Mas Lynx audio demo
Please play it online or in your emu and press button A several times to toggle the individual channels:
https://atarigamer.com/lynx/play/FunkyXmas2018/742408636
You will see/hear that for example channel0 interweaves percussion and bass.

I hope these posts were not too much of an 'wall of text' and that it might inspire others to go deeper into Lynx audio coding ;-)
The biggest problem we faced was that with the few options the Lynx has, it can be extremely timing critical when and how to set different shift- or feedback registers or audio registers in general without causing audible clicks or glitches that the emulators often don't cover.

This is ongoing work, however and just these days I changed the data format again :)

pattern.png

  • Like 1
  • Thanks 2
Link to comment
Share on other sites

Could be low batteries or a not-so-good cart connector pin? Please let us know if that happens again.

I had that very issue (freeze with short sound loop, i.e. trapped IRQ) in Wyvern Tales and we have pretty much confirmed that it became instable at low batteries.

Edited by enthusi
Link to comment
Share on other sites

Well, you never know but yes, Im somewhat confident :)

There are 41 highscore submissions so far. Some obviously played for quite some time without any bugs reported. A simple overflow bug would show in emulators as well as real hardware.

This here appears to be real hardware and I had the exact same happening to me for a different game once and that could be traced to this particular unit running on batteries (never occured with PSU plugged in).

Link to comment
Share on other sites

Especially if you use McWill screen a small drop in voltage creates problems.

 

I actually ran out of stack in my final boss fight. The sampled sounds + sfx + bgmusic was just too  much. So I had to leave out the screams of my hero. He now dies in silence.

259454861_Screenshotfrom2019-09-1313-31-41.png.d8ecb7d0724b79ac61ecbc2a1f46f0d9.png

Link to comment
Share on other sites

Happy to report that it never occured again despite many hours of gameplay ;-) Thanks for playing so hard!

 

I am also quite happy end even a bit proud :) that the 'difficulty system' I worked out represents the actual real-world difficulty to play the game.

This does not matter much for the player but it is very satisfying for the programmer.

In the code I brought it all down to a single variable that takes up the value of  1, 2, 5 according to level 1, 2, 3.

It is NOT simply linear of course.

Yet some people handed it scores for all three levels and by taking those ratios I see that they follow 1:2:5 wonderfully well ;-)

http://enthusi.de/scores.html

 

Example (all by JakobN!)

level 1: 23790

level 2: 11330

level 3:   4370

-> level1/ level 2 = 2.1

-> level1/ level 3 = 5.4

 

For another case it is 2.0 and 4.3.

 

 

Link to comment
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...