TIC-80 icon indicating copy to clipboard operation
TIC-80 copied to clipboard

Add the ability to poke 0x13FFE to resume music after pause (or similar behaviour)

Open Bentic opened this issue 7 years ago • 14 comments

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.

Bentic avatar Jun 17 '17 13:06 Bentic

  • 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?

joshgoebel avatar Nov 05 '21 03:11 joshgoebel

@Bentic Ping.

joshgoebel avatar Feb 23 '22 21:02 joshgoebel

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).

Bentic avatar Feb 24 '22 18:02 Bentic

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?

joshgoebel avatar Feb 24 '22 18:02 joshgoebel

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).

joshgoebel avatar Feb 24 '22 19:02 joshgoebel

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 musicAPI ? (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)

Bentic avatar Feb 24 '22 19:02 Bentic

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?

joshgoebel avatar Feb 24 '22 20:02 joshgoebel

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.

joshgoebel avatar Feb 24 '22 20:02 joshgoebel

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.

Bentic avatar Feb 26 '22 19:02 Bentic

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()

joshgoebel avatar Feb 26 '22 20:02 joshgoebel

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 ?

Bentic avatar Mar 01 '22 16:03 Bentic

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. :-)

joshgoebel avatar Mar 02 '22 15:03 joshgoebel

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 ?)

Bentic avatar Mar 02 '22 17:03 Bentic

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...

joshgoebel avatar Mar 02 '22 17:03 joshgoebel