You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi, I wrote an asm implementation of the QuadTone engine as a first step towards a ROM builder. However, my C++ skills are rusty and I don't have time to take a deep dive into Furnace's code, so I was wondering if anybody would be up for building the converter part.
Naturally, I had to cut a few corners to make this work, but the code should be good enough for most use cases. The engine runs at 9114 Hz (sampling twice as fast internally), so the viable note range would be somewhere from C-0 - C-7. Updates are 60Hz. For the overlay channel, I opted for 8-bit PWM samples, which should be functionally equivalent to 1-bit PCM while compressing better in most cases. This also allows us to get rid of the 2048 sample limit. I cut the sample rate to 27210 Hz. That's half of what it should be, if I understood correctly, but 54420 Hz is not doable on the target hardware, and 27210 Hz gives us a decent lower frequency bound of 106 Hz for 8-bit PWM.
The data format is a very basic LZ scheme, keeping in mind that decoding needs to be fast for 1-bit sound. In order to produce data compatible with the engine, the converter would need to do the following:
Flatten the entire song structure into a list of rows, including all fx, arpeggios and so forth.
Build a dictionary of all unique rows.
Recreate the song structure as a sequence of pointers to the dictionary.
Convert PCM samples to 8-bit PWM.
Rows are formatted as follows:
If the first byte is non-zero, it denotes the row length, ie. number of ticks until the next update. It is followed by a flag byte.
bit
function
0
reload frequency ch1
1
reload duty/volume ch1
...
7
reload duty/volume ch4
Then, for each set flag, the respective data follows, either a 16-bit frequency (as divider of a 9114 Hz clock), or one byte encoding 8 bits of duty followed by one byte encoding 2 bits of volume.
If the first byte is zero, it signals that an overlay sample should be played. It will be followed by a pointer to the sample data, followed by a 16-bit pitch value (relative to a sample rate of 27210 Hz, pitch down only). After this, one byte for the note length follows. If the pitched sample is shorter than the row, then the remainder of the row will be played normally. That means, the remaining row data should follow as described above, starting at the flag byte. If the sample is longer than the row length, regular row data should be omitted, as it will be ignored and the next row will be read. In either case, this means that the converter must calculate the sample's play length somehow (don't trust my math, but should be something like, sum up all the PWM sample values, multiply by (65536/pitch_value), divide by 3*151).
Each PWM sample data chunk must be 0-terminated.
In the output, the sequence must come first. It must be terminated with a 0-word, and followed by a pointer to the song's loop point. Then, row and PWM sample data may follow in arbitrary order.
Soooo... anybody up for building this? If so, I'll open a PR with the asm code to get things going. Am I right in assuming that this should go into src/asm/z80? Btw, the code is for sjasmplus, shouldn't be too hard to adapt though since it doesn't use any fancy macros or anything.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Hi, I wrote an asm implementation of the QuadTone engine as a first step towards a ROM builder. However, my C++ skills are rusty and I don't have time to take a deep dive into Furnace's code, so I was wondering if anybody would be up for building the converter part.
Naturally, I had to cut a few corners to make this work, but the code should be good enough for most use cases. The engine runs at 9114 Hz (sampling twice as fast internally), so the viable note range would be somewhere from C-0 - C-7. Updates are 60Hz. For the overlay channel, I opted for 8-bit PWM samples, which should be functionally equivalent to 1-bit PCM while compressing better in most cases. This also allows us to get rid of the 2048 sample limit. I cut the sample rate to 27210 Hz. That's half of what it should be, if I understood correctly, but 54420 Hz is not doable on the target hardware, and 27210 Hz gives us a decent lower frequency bound of 106 Hz for 8-bit PWM.
The data format is a very basic LZ scheme, keeping in mind that decoding needs to be fast for 1-bit sound. In order to produce data compatible with the engine, the converter would need to do the following:
Rows are formatted as follows:
If the first byte is non-zero, it denotes the row length, ie. number of ticks until the next update. It is followed by a flag byte.
Then, for each set flag, the respective data follows, either a 16-bit frequency (as divider of a 9114 Hz clock), or one byte encoding 8 bits of duty followed by one byte encoding 2 bits of volume.
If the first byte is zero, it signals that an overlay sample should be played. It will be followed by a pointer to the sample data, followed by a 16-bit pitch value (relative to a sample rate of 27210 Hz, pitch down only). After this, one byte for the note length follows. If the pitched sample is shorter than the row, then the remainder of the row will be played normally. That means, the remaining row data should follow as described above, starting at the flag byte. If the sample is longer than the row length, regular row data should be omitted, as it will be ignored and the next row will be read. In either case, this means that the converter must calculate the sample's play length somehow (don't trust my math, but should be something like, sum up all the PWM sample values, multiply by (65536/pitch_value), divide by 3*151).
Each PWM sample data chunk must be 0-terminated.
In the output, the sequence must come first. It must be terminated with a 0-word, and followed by a pointer to the song's loop point. Then, row and PWM sample data may follow in arbitrary order.
Soooo... anybody up for building this? If so, I'll open a PR with the asm code to get things going. Am I right in assuming that this should go into
src/asm/z80
? Btw, the code is for sjasmplus, shouldn't be too hard to adapt though since it doesn't use any fancy macros or anything.Beta Was this translation helpful? Give feedback.
All reactions