TIC-80
TIC-80 copied to clipboard
Add the ability to poke 0x13FFE to resume music after pause (or similar behaviour)
Music suggestion: Adding the ability to poke address 0x13FFE (row value) to be able to correctly resume paused music after being peeked. Or implement a similar functionality since row depends on internal time counter.
- How are you pausing the music in the first place?
- Can this be done currently via the
music
API and if so could you provide an example?
@Bentic Ping.
Hi @joshgoebel,
Really sorry, it's been a while. I'm still not too used to GitHub so I didn't see your reply.
The music function has been enhanced in 0.90 so there are frame and row options now (among other things).
What I was doing in https://tic80.com/play?cart=125 (in the pause_music() function) is that I peeked and stored 0x13FFD, 0x13FFE and 0x13FFF for frame, row and flags and stopped the music with music(), then when resuming, I started the music by providing the track with music(trackID) then poked the values I stored. That at least started it at the frame it stopped at, but not the row.
I tried adding the stored frame and row as parameters of music() when resuming it and it now works correctly 🙂 There are just the flags left to poke, or translate the value peeked into loop and sustain parameters (there seem to be more modes available by poking than what is available as music() parameters though).
So that now works with music() but poking the row doesn't (but that might not be necessary now).
Ok in taking a closer look I think the desire to set row (to start/stop music) is misguided. Actual playing state is determined by mode
(see docs) so if one wanted to pause/resume outside the API you'd want to peek/poke SFX_STATE mode
with 0, 1 or 2... You wouldn't touch row
(other than perhaps to preserve it, etc)...
I'm not sure if this would work in practice because there is internal audio state (outside the RAM) that is sometimes reset via resetMusicChannels
... but now that we have the ability to start/stop and also jump to a given frame/row using the music()
API then I don't see a real need to make this possible via peek
.
The music
API is now fully sufficient to play/pause in your original use case, yes?
Actually looking more it might work just fine... Audio processing loop get killed pretty early if mode = 0 ("stop")... so I think you could pause/unpause with 0/non-zero
... What music
gets you (when stopping) is that it ALSO calls resetMusicChannels(memory);
... I imagine this is important to remove sound already queued in the buffer, etc...
So trying to hard STOP (and then resume an entirely different place) without using the API might be broken, but I think pausing would work, because at least at a glance it looks like "stop" is really more like "pause"... could you confirm that poking SOUND_STATE.FLAGS.mode
(0x13FFF, only the correct 4 bits, not sure if they are high or low) executes a successful pause/unpause?
If so I'm not sure there is further work to do here. :)
Right now at a glance I think pause would preserve that register (mode
, 4 bits) and set it to 0 ("stop"), and resume would merely restore it to it's prior value (resuming the audio).
Hi @joshgoebel,
Thank you for your response.
Yes, poking 0 or 1 (I still don't know if there's a difference) in 0x13FFF stops the music, and poking 5 (loop track, as I was using) resumes it from the same row. Poking any number at the row address doesn't seem to do anything.
Would there be a way to achieve a proper resume only with the music
API ? (by either getting the current row (to add it as a parameter) in a way without peeking, or resuming from the position it had when last stopped)
Yes, poking 0 or 1 (I still don't know if there's a difference)
Oh, it's because mode
is actually LOOP + STATUS with loop being the low bits... so setting mode to 0 or 1 is just guaranteeing STATUS = 0
which is the important part, 0 = STOP. Loop makes no difference if you're stopped.
struct
{
u8 music_loop:1;
u8 music_status:2; // enum tic_music_status
u8 music_sustain:1;
u8 unknown:4;
} flag;
Poking any number at the row address doesn't seem to do anything.
Row (as a register) is never actually used directly... what you're peeking is really a calculated value from audio "ticks"... you're not actually (often?) ever at row 3 exactly, you're more likely to be in-between rows.... so internally the ticks are counted and row is just updated as a niceness I presume so that poking it yields something useful. Calling music
coverts the desired row into the correct "ticks" - thereby seeming to "set" it, but only indirectly.
Would there be a way to achieve a proper resume only with the musicAPI ?
Depends what you mean by "proper"... you can't resume in the middle of a "tick" (ie, row 3.4338) using music()
... but I think for most cases just peeking the row
and then passing that back to music()
again would be more than sufficient, no? If the music has been paused for a little time I'm not sure someone would notice a difference between resuming at row 3 or 3.2827272...
Thoughts?
We could probably special case the audio state to handle poke(rows, 3)
:
// ie, we've updated `rows`, but not via the music API
if (previous_audio_state.rows != current_audio_state.rows && music_api_not_called) {
state.ticks = rows2Ticks(current_audio_state.rows)
}
Or the music API itself is only allowed to update rows
(no now we don't care how rows was updated) and then the internal engine has to figure out when that happened to properly setup ticks
.... that's probably the "simple" way to get this behavior, but I worry that could have other side effects or be more complex than I'm thinking.
I personally this sounds like an edge case we can already handle with music()
, making this a #wontfix IMHO.
My original request was made when music()
only had the track ID as parameter. So the only way to pause the music I found back then was peeking and poking (poking the row didn't work, but starting from the last pattern was better than nothing).
Depends what you mean by "proper"
Now that we can add pattern and row parameters to music()
, no need to poke anything, but I feel that it would be easier, especially for beginners, if we didn't have to peek anything at all.
So either being able to get pattern and row through the API (to provide them manually when starting the music again), or just having the ability to pause/resume the currently playing music without having to check its status.
So either being able to get pattern and row through the API (to provide them manually when starting the music again)
pattern, row = peek()..., peek()...
// resume
music(...,patten, row)
Or some such.. no? This should work now... row
in RAM is kept updated, it's just not potable in any useful sense without using music()
Yes, we can do it like that now, but I was just wondering if it would be interesting to be able to do it without peeking the RAM for people learning programming, checking music()
, seeing they can start on a specific pattern and row but having no clue they have to check the RAM status, know its layout and understand addresses to find out the current music status to be able to resume after a pause.
If they don't know that, they would maybe start using a timer to count the ticks, convert that to patterns and rows, etc. (at least that's what I would do) That might be a bit overwhelming.
Just a thought to make it easier to use. If not, it's OK :)
Should we ask nesbox his thoughts about it ?
I simply think this use case is not common (pausing and resuming at an exact spot) and we strive to have a minimal core API. It's easy to peek these values from RAM if someone needs them... and music()
is already solved in that it allows passing them. If we were going to add API here I think it would be pauseMusic()
and resumeMusic()
but as these helpers are super trivial to write (as discussed) I think it's better if they lived on the Wiki or in a helper library, not the core API.
@nesbox I assume he's always reading and thus my thoughts are always trying to make a case. :-)
I would say pausing a game is not that uncommon that beginners wouldn't think to implement that functionality in their games, and we're familiar enough with what generally happens when we pause a game, but I understand the will to keep it minimal. Well, at least if someone new would want to do that, they'll learn more things on the way (that's what I did) 😛
Would adding an example using peek and these frame and row options on the music()
wiki page be a good idea ?
Also... I suddenly wanted to verify a thought, and indeed, the game menu pauses and resumes the music properly 😄 It's just that it only shows on exported games if I'm not mistaken so I never see it when testing the game from the editor. But I guess it's still good knowledge when making our own custom pause menu.
(by the way, if we were to rely on the game menu for pausing instead of making our own, wasn't there a way to add elements to it ? I can't seem to find it... And is there a way to trigger it with a non-exported game to test it out ?)
wasn't there a way to add elements to it
Yes, there is a new meta tag (and callback) for that, but I forget the name. It's not documented on the wiki yet.
And is there a way to trigger it with a non-exported game to test it out ?)
Build 1.0 from source, it's very (almost annoyingly) easy to get to the menu in 1.0.
Would adding an example using peek and these frame and row options on the music() wiki page be a good idea ?
Sure. https://github.com/nesbox/TIC-80/wiki/code-examples-and-snippets
I would say pausing a game is not that uncommon
Pausing a game doesn't require pausing the music of course... :-) My larger point was that for an extended pause it's not super noticeable if it resumes from a slightly different point anyways - since most soundtracks are just loops...