A constant-cycle music engine

Discuss NSF files, FamiTracker, MML tools, or anything else related to NES music.

Moderator: Moderators

User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: A constant-cycle music engine

Post by GradualGames »

pubby wrote:
GradualGames wrote::shock: Well, this is humbling! By the time ggsound burns 760 cycles, it has just had its coffee and is still waking up in the morning. Ah, time to actually make sounds now! :lol: I can really learn something from this code. Thanks for sharing.
The biggest optimization comes from dividing the work out across multiple frames. I divided it into 4, and well, that's why it's 4x faster than famitone.
I'm having trouble imagining how I would do that. If I have an envelope with a loop point for example, I'm going to update the envelope and the associated channel registers on every frame. What work can you get away with spreading out? Maybe tempo and length counters as one example?
User avatar
pubby
Posts: 583
Joined: Thu Mar 31, 2016 11:15 am

Re: A constant-cycle music engine

Post by pubby »

Reading the notes and preparing/assigning the instruments and doing other bookkeeping can be broken up across frames. In my case, it saved about ~450 cycles.

Reading the sequences and writing the APU registers has to be done every frame. I didn't implement any instrument compression, so that's a pretty big speed advantage over libraries that do.
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: A constant-cycle music engine

Post by GradualGames »

pubby wrote:Reading the notes and preparing/assigning the instruments and doing other bookkeeping can be broken up across frames. In my case, it saved about ~450 cycles.

Reading the sequences and writing the APU registers has to be done every frame. I didn't implement any instrument compression, so that's a pretty big speed advantage over libraries that do.
That's a great idea, this is making me want to refactor ggsound to work a similar way. Highly doubt it'll come close to the same speed, but I like the idea of making it the best it can be. Thanks for the inspiration. Sounds like I may need a little bit more ram, so that I can read the next envelope being prepared and then swap it into place for actual playback, where as at the moment I only store the current envelope.

I have another question. Do you precompile instruments such that their volume, pitch, duty sequences are all predetermined as register values? In my engine, I modify the current volume, pitch and duty values per channel per envelope at runtime. Is that what you're referring to? That might be a nice improvement as well...
User avatar
pubby
Posts: 583
Joined: Thu Mar 31, 2016 11:15 am

Re: A constant-cycle music engine

Post by pubby »

Volume+duty is combined into a single sequence that maps directly to the APU register. Pitch and Arpeggio are separate sequences and a bit of arithmetic is done each frame to calculate them.

I only implemented one of FamiTracker's relative/absolute/fixed behavior (I used the default setting) for each sequence type, so that may also explain the difference.
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: A constant-cycle music engine

Post by GradualGames »

pubby wrote:Volume+duty is combined into a single sequence that maps directly to the APU register. Pitch and Arpeggio are separate sequences and a bit of arithmetic is done each frame to calculate them.

I only implemented one of FamiTracker's relative/absolute/fixed behavior (I used the default setting) for each sequence type, so that may also explain the difference.
Ah! Yeah I was thinking about this last night and realized it wouldn't be possible to precompile pitch or arpeggio since it has to work for any note that is playing. But I do have a lot of rather fiddly arithmetic in place to juggle the duty cycle values and volume values. If I leave in the capability to have a duty cycle envelope complete with loop points, I might not be able to pre-bake these if the loop points cause glitches in the duty cycle (like if the lengths of each envelope are not multiples of each other, and when the volume envelope goes back the duty cycle would have been in a different location). It looks like famitone and your engine perhaps only allow a single duty value to be used? *edit* I might want to change ggsound to work this way because I don't think I ever do anything more than one different duty value at the beginning for a crisp attack sound. having several values in a row sounds too rough/odd.

I actually have the ability to totally compile out arpeggios and dpcm from my engine because I never use them myself, haha. Arpeggios sound like telephones to me and dpcm causes that controller bug; don't want to deal with it :lol:

I also realized if famitone uses precompiled register values for sfx, that ggsound's sfx may be more flexible since they are basically tiny songs on their own that terminate by default (but could loop if desired)

Everything is a trade off!
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: A constant-cycle music engine

Post by tepples »

GradualGames wrote:I do have a lot of rather fiddly arithmetic in place to juggle the duty cycle values and volume values. If I leave in the capability to have a duty cycle envelope complete with loop points, I might not be able to pre-bake these if the loop points cause glitches in the duty cycle (like if the lengths of each envelope are not multiples of each other, and when the volume envelope goes back the duty cycle would have been in a different location).
In my engine, I just decided that volume envelopes aren't allowed to loop, and duty and arpeggio envelopes will loop only to the length of the volume envelope and stop. The envelope format is 1 byte for duty (2 bits), volume (4 bits), and whether arpeggio on this frame is nonzero (1 bit), followed by an arpeggio envelope value if needed. If you want to preserve ability to loop envelopes while baking duty into them, you could take the least common multiple of the volume and duty envelope loop lengths, use that as the overall loop length, and possibly emit a diagnostic in the converter if the resulting length exceeds the greater of the volume or duty envelope loop length.
GradualGames wrote:having several values in a row sounds too rough/odd.
In some styles, you want rough.
GradualGames wrote:I actually have the ability to totally compile out arpeggios and dpcm from my engine because I never use them myself, haha.
Likewise with PENTLY_USE_* flags.
GradualGames wrote:Arpeggios sound like telephones to me and dpcm causes that controller bug; don't want to deal with it :lol:
Sometimes you want them to sound like telephones, as in my cover of "Disconnected" by Inspector K.
GradualGames wrote:I also realized if famitone uses precompiled register values for sfx, that ggsound's sfx may be more flexible since they are basically tiny songs on their own that terminate by default (but could loop if desired)
Sound effects in Pently behave more like instruments, but they do have their own "speed" value such that you could encode a jingle in one.
User avatar
pubby
Posts: 583
Joined: Thu Mar 31, 2016 11:15 am

Re: A constant-cycle music engine

Post by pubby »

GradualGames wrote:It looks like famitone and your engine perhaps only allow a single duty value to be used?
Famitone is like this but my engine handles duty sequences. The converter does a little work to comebine the duty sequence with the volume sequence in a way that preserves both.

It breaks down if you loop both sequences and the loop lengths aren't multiple of each other, but that's extremely rare to run into.
Post Reply