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?pubby wrote: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.GradualGames wrote: 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! I can really learn something from this code. Thanks for sharing.
A constant-cycle music engine
Moderator: Moderators
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
Re: A constant-cycle music engine
Re: A constant-cycle music engine
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.
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.
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
Re: A constant-cycle music engine
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.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.
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...
Re: A constant-cycle music engine
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.
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.
- GradualGames
- Posts: 1106
- Joined: Sun Nov 09, 2008 9:18 pm
- Location: Pennsylvania, USA
- Contact:
Re: A constant-cycle music engine
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.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.
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
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!
Re: A constant-cycle music engine
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: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 some styles, you want rough.GradualGames wrote:having several values in a row sounds too rough/odd.
Likewise with PENTLY_USE_* flags.GradualGames wrote:I actually have the ability to totally compile out arpeggios and dpcm from my engine because I never use them myself, haha.
Sometimes you want them to sound like telephones, as in my cover of "Disconnected" by Inspector K.GradualGames wrote:Arpeggios sound like telephones to me and dpcm causes that controller bug; don't want to deal with it
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.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)
Re: A constant-cycle music engine
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.GradualGames wrote:It looks like famitone and your engine perhaps only allow a single duty value to be used?
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.