Opinon needed: Data structure for my sound engine
Moderator: Moderators
Opinon needed: Data structure for my sound engine
I did most of the Nerdy Nights sound tutorials. I'm at chapter 7 now. But now the tutorial includes all the really advanced features, like volume envelopes, opcodes etc.
But my music engine is supposed to be simple since it is intended for a simple game.
So, I created my own sound data structure that I want to base my code on. And I'd like you to judge it. Is this a reasonable attempt?
O.k., the first data structure is a tune.
A tune consists of the following values:
1. Volume and duty cycle (1 byte)
Just 1:1 the value that you would write into the corresponding APU port.
2. Note or length (1 byte, can occur multiple times)
If bit 7 of this value is 0, then it is a note. Or rather: It is the index to a lookup table that includes the 96 frequency values from this list, each of them consisting of two bytes: www.freewebs.com/the_bott/NotesTableNTSC.txt
If bit 7 of the value is a 1, then this value (or rather: value AND %01111111) is the length (in frames) of all the following notes until the next length value is read.
3. $FF as an indicator for the end of the tune.
That was the "inner" data structure.
The second data structure, the "outer" data structure, is a song.
A song consists of the following values:
1. Address to a tune on square wave channel 1 (2 bytes, can occur multiple times)
2. Value $0000 (2 bytes) as an indicator that the song needs to be looped now, i.e. it starts at the first tune again.
3-8: Like 1 and 2, only for square wavel channel 2, triangle channel and noise channel.
If any of the tune address values is 0, this channel is deactivated for the song.
So, if we have the song "For he's a jolly good fellow", we would have the following tunes:
Tune 1: "For he's a jolly good"
Tune 2: "fellow."
Tune 3: "fehellooow."
Tune 4: "Which nobody can deny."
And the song array (for one channel) would look like this:
Tune 1
Tune 2
Tune 1
Tune 2
Tune 1
Tune 3
Tune 4
0
Sound effects are not included yet. But they're basically the same, only that they don't loop and will probably just consist of one tune anyway. When they happen, they "steal" one of the sound channels from the corresponding music stream.
What do you say?
But my music engine is supposed to be simple since it is intended for a simple game.
So, I created my own sound data structure that I want to base my code on. And I'd like you to judge it. Is this a reasonable attempt?
O.k., the first data structure is a tune.
A tune consists of the following values:
1. Volume and duty cycle (1 byte)
Just 1:1 the value that you would write into the corresponding APU port.
2. Note or length (1 byte, can occur multiple times)
If bit 7 of this value is 0, then it is a note. Or rather: It is the index to a lookup table that includes the 96 frequency values from this list, each of them consisting of two bytes: www.freewebs.com/the_bott/NotesTableNTSC.txt
If bit 7 of the value is a 1, then this value (or rather: value AND %01111111) is the length (in frames) of all the following notes until the next length value is read.
3. $FF as an indicator for the end of the tune.
That was the "inner" data structure.
The second data structure, the "outer" data structure, is a song.
A song consists of the following values:
1. Address to a tune on square wave channel 1 (2 bytes, can occur multiple times)
2. Value $0000 (2 bytes) as an indicator that the song needs to be looped now, i.e. it starts at the first tune again.
3-8: Like 1 and 2, only for square wavel channel 2, triangle channel and noise channel.
If any of the tune address values is 0, this channel is deactivated for the song.
So, if we have the song "For he's a jolly good fellow", we would have the following tunes:
Tune 1: "For he's a jolly good"
Tune 2: "fellow."
Tune 3: "fehellooow."
Tune 4: "Which nobody can deny."
And the song array (for one channel) would look like this:
Tune 1
Tune 2
Tune 1
Tune 2
Tune 1
Tune 3
Tune 4
0
Sound effects are not included yet. But they're basically the same, only that they don't loop and will probably just consist of one tune anyway. When they happen, they "steal" one of the sound channels from the corresponding music stream.
What do you say?
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Re: Opinon needed: Data structure for my sound engine
You could increase the range of the length (from 128 to 256 frames) if only the note byte used bit 7 as a flag: if the bit is 0, play the note using the previous length, if the bit is 1, play the note using the length specified in the next byte (all 8 bits of it), which becomes the new default length.DRW wrote:2. Note or length (1 byte, can occur multiple times)
If bit 7 of this value is 0, then it is a note. Or rather: It is the index to a lookup table that includes the 96 frequency values from this list, each of them consisting of two bytes: http://www.freewebs.com/the_bott/NotesTableNTSC.txt
If bit 7 of the value is a 1, then this value (or rather: value AND %01111111) is the length (in frames) of all the following notes until the next length value is read.
Re: Opinon needed: Data structure for my sound engine
You can represent the same information in 36 bytes. To do that, you'd use a table of 12 frequency values and 12 "rounding" values. The frequency value is right-shifted to the correct octave, and the "rounding" either keeps the result or subtracts 1 to arrive at the correct value.DRW wrote:Or rather: It is the index to a lookup table that includes the 96 frequency values from this list, each of them consisting of two bytes:
I also worked out tables that round to the nearest pitch* instead of the nearest frequency. I can give you a copy if you're interested. (Really, it probably belongs on the wiki.)
*If you have an offset the same number of Hz above and below the target frequency, below will sound further away because pitch does not map linearly to frequency.
Re: Opinon needed: Data structure for my sound engine
I dont understand this. Bit 7 is already there to check whether it's a length at all or whether it's a note.tokumaru wrote:You could increase the range of the length (from 128 to 256 frames) if only the note byte used bit 7 as a flag
Can you write down what you mean in pseudo code with a few ifs and elses please?
I don't think I will do this. The lookup table is merely 96 bytes for the whole program. So, with your method I would save 30 bytes. But it would add logic to the function. And time per frame is a much more serious issue than a few constants in the ROM.Joe wrote:You can represent the same information in 36 bytes. To do that, you'd use a table of 12 frequency values and 12 "rounding" values. The frequency value is right-shifted to the correct octave, and the "rounding" either keeps the result or subtracts 1 to arrive at the correct value.
Sure, post it. I'd need some of the music experts on this forum to compare which one is better, though. Since I have no idea.Joe wrote:I also worked out tables that round to the nearest pitch* instead of the nearest frequency. I can give you a copy if you're interested. (Really, it probably belongs on the wiki.)
Which rounding method does the Famitracker use? (Since my composer works with that program, so if he enters a certain note and I paste it into my game, then it should of course still sound the same in my own engine.)
Another question: How important is it to be able to change the volume and duty cycle of the square waves in the middle of the song? I'm thinking about putting these values at the beginning of the whole song instead of the start of every tune. Saves me a few bytes, but most important: It saves me time since those values only need to be set once in the beginning instead writing (and even checking for them) every tune.
So, do songs with a constant duty cycle per square wave stream still sound good?
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Re: Opinon needed: Data structure for my sound engine
96 - 36 = 60DRW wrote:So, with your method I would save 30 bytes.
Fair enough. It's an option you can consider if ROM space becomes more important than CPU time.DRW wrote:But it would add logic to the function. And time per frame is a much more serious issue than a few constants in the ROM.
Here's the NTSC table. It looks like I stopped when I started getting duplicate values for adjacent notes, so the (basically useless) highest values are missing. There might be some typos since I did all of the math with a graphing calculator.DRW wrote:Sure, post it. I'd need some of the music experts on this forum to compare which one is better, though. Since I have no idea.
Code: Select all
A | 7F0 | 3F8 | 1FB | FD | 7E | 3F | 1F | F
Bb | 77E | 3BF | 1DF | EF | 77 | 3B | 1D | E
B | 713 | 389 | 1C4 | E1 | 70 | 38 | 1B | D
C | 6AD | 356 | 1AA | D5 | 6A | 34 | 1A | C
C# | 64D | 326 | 192 | C9 | 64 | 31 | 18 |
D | 5F2 | 2F9 | 17C | BD | 5E | 2F | 17 |
Eb | 59D | 2CE | 166 | B3 | 59 | 2C | 15 |
E | 54C | 2A6 | 152 | A9 | 54 | 29 | 14 |
F | 500 | 27F | 13F | 9F | 4F | 27 | 13 |
F# | 4B8 | 25C | 12D | 96 | 4B | 25 | 12 |
G | 474 | 23A | 11C | 8E | 46 | 23 | 11 |
Ab | 434 | 21A | 10C | 86 | 42 | 21 | 10 |
That's probably up to your composer. For something like this, it's very important.DRW wrote:Another question: How important is it to be able to change the volume and duty cycle of the square waves in the middle of the song?
Re: Opinon needed: Data structure for my sound engine
Yeah, that's all a matter of opinion. That's what I like so much about mathematics: There isn't the one correct answer.Joe wrote:96 - 36 = 60DRW wrote:So, with your method I would save 30 bytes.
So, what do the others say? Which list is better? Joe's or this one?
Also, which list does the Famitracker use? How do they convert the notes into 11 bit hex values?
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Re: Opinon needed: Data structure for my sound engine
That table is wonky. Are you using A440? Or are you doing something clever with tuning harmonics?Joe wrote:Code: Select all
A | 7F0 | 3F8 | 1FB | FD | 7E | 3F | 1F | F Bb | 77E | 3BF | 1DF | EF | 77 | 3B | 1D | E
All five periods here are within two cents of 12-TET A55, but 7F0 is not what I'd've picked if I was trying to be as close as possible.my program wrote: 7F0 → A-1 +01.5
7F1 → A-1 +00.7
7F2 → A-1 -00.1
7F3 → A-1 -00.9
7F4 → A-1 -01.8
On the NES, with its NTSC-based timing, there may be something to be said for detuning to 13 or 15 cents flat; you get more in-tune notes in the highest octaves that way. There may also be something to be said for using D-6=1202.8Hz (40 cents sharp, period of exactly 93): then the noise channel's tonal noise mode is in tune.
Anyway, here's some simple math to convert from a divisor (as used by the NES) to a note name and a detuning in cents.
Code: Select all
frequency = 39375000 / 22 / 16 / divisor;
pitch = 1200*log(frequency / 440)/log(2);
semitones = floor( (pitch + 50) / 100);
cents = pitch - semitones * 100;
octave = (semitones - 3) / 12 + 5;
notenameindex = (semitones + 120 - 3) % 12;
Re: Opinon needed: Data structure for my sound engine
My point is you stop using it to tell notes and lengths apart, and assume all bytes are notes, but when the bit is set, you load a new length, which will be specified in the next byte.DRW wrote:I dont understand this. Bit 7 is already there to check whether it's a length at all or whether it's a note.
I find it redundant to have a bit identifying notes and lengths, because there's no need (as far as I can tell) to have consecutive length bytes, since you're using the concept of persistent lengths that are changed sporadically. Notes on the other hand are absolutely necessary all the time, and since you have the free bit, you can simply have a note "request" that a new length be loaded when the current one is not what it needs.
Re: Opinon needed: Data structure for my sound engine
I wasn't trying to do anything clever beyond rounding to the nearest pitch, so you probably found a mistake.lidnariq wrote:That table is wonky. Are you using A440? Or are you doing something clever with tuning harmonics?
Isn't that off by one? The wiki says this:lidnariq wrote:Code: Select all
frequency = 39375000 / 22 / 16 / divisor;
Code: Select all
f = CPU / (16 * (t + 1))
Re: Opinon needed: Data structure for my sound engine
No, the length won't be specified in the next byte, but in the current byte:tokumaru wrote:My point is you stop using it to tell notes and lengths apart, and assume all bytes are notes, but when the bit is set, you load a new length, which will be specified in the next byte.
Code: Select all
ReadNextNote()
{
start:
value = ReadNextValue();
if (value AND %10000000 == 0)
CurrentNote = Notes[value];
else
{
CurrentLength = value AND %01111111;
goto start;
}
}
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Re: Opinon needed: Data structure for my sound engine
I know how it is, I just think that using a bit to identify the length byte is a waste of a perfectly good bit. Here's what I mean:DRW wrote:No, the length won't be specified in the next byte, but in the current byte:
Code: Select all
ReadNextNote() {
Value = ReadNextValue();
if (Value AND %10000000 != 0) {
CurrentLength = ReadNextValue();
Value = Value AND %01111111;
}
CurrentNote = Notes[Value];
}
0: play this note using the previous length;
1: play this note use a new length, provided in the next byte;
And then you don't need to "sign" each byte as being either a note or a length... They're all notes, except for the bytes that follow notes that request new lengths.
Re: Opinon needed: Data structure for my sound engine
I don't know why this is better than the other method. You still have a bit to indicate that a length is coming and you still need an additional byte for the length. So, why does this make a difference?
The only thing that I noticed here is that you have a strange order in your song data:
My data: LengthA, NoteA1, NoteA2, NoteA3, LengthB, NoteB1, NoteB2, LengthC, NoteC1, NoteC2 ...
Your data: NoteA1, LengthA, NoteA2, NoteA3, NoteB1, LengthB, NoteB2, NoteC1, LengthC, NoteC2 ...
I guess I'm missing something here, but I just don't know what exactly. What do I gain from switching the two values around?
The only thing that I noticed here is that you have a strange order in your song data:
My data: LengthA, NoteA1, NoteA2, NoteA3, LengthB, NoteB1, NoteB2, LengthC, NoteC1, NoteC2 ...
Your data: NoteA1, LengthA, NoteA2, NoteA3, NoteB1, LengthB, NoteB2, NoteC1, LengthC, NoteC2 ...
I guess I'm missing something here, but I just don't know what exactly. What do I gain from switching the two values around?
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Re: Opinon needed: Data structure for my sound engine
The length can be 1..256 instead of 1..128, that's all. You shouldn't need such long lengths very often, but if you do...DRW wrote:So, why does this make a difference?
Re: Opinon needed: Data structure for my sound engine
Oh, right. Now I understand it. Yeah, that's better. Thanks.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Re: Opinon needed: Data structure for my sound engine
I think this whole conversation points out that Famitracker could use a better tool (like Famitone) to specifically use Famitracker songs in a game. The easiest approach (IMHO) would be to modify Famitone to accept more/all Famitracker features.
If I were smarter, I would write a tool to convert MIDI files directly to usable game song data. (And write an engine designed to play it). But that may be above my skill level.
If I were smarter, I would write a tool to convert MIDI files directly to usable game song data. (And write an engine designed to play it). But that may be above my skill level.
nesdoug.com -- blog/tutorial on programming for the NES