Ren'Py RenPy 8.4 update - Next music button

Playstorepers

Member
May 24, 2020
169
82
173
Hey there,


A few years ago I created my own "previous/next song"-button. It was crude but working nicely.
Ever since I updated from Ren'Py 8.0.3 to Ren'Py to 8.4.1 it seems bugged.

I suggest reading the bug first but you can also start with the code.

You don't have permission to view the spoiler content. Log in or register now.

This code was working as intended
...in RenPy 8.0.3.

You don't have permission to view the spoiler content. Log in or register now.


It's impossible for me to explain those bugs or even fix them since everything used to work in 8.0.3 and still makes sense (even if it's poorly written code ofc), so any clue or idea would be appreciated.

My best guess is that the Return() function and the renpy.restart_interaction are now slightly changed since it memorizes the old song for some reason. But that doesn't explain the first/second click gimmick.


Thank you in advance for any suggestions and sorry for the long post.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
12,725
20,887
1,026
Starts by writing the actions correctly and see if it solve your issue.

Functions must be used through the screen action, not passed directly.
Code:
           action [ Function( renpy.music.stop ),
                Queue("music", entirelistprevious, loop = True),
                Function( renpy.restart_interaction )]
That the bogus syntax you used worked until now doesn't mean that it was the right one. Depending on what changed in Ren'Py core, it can perfectly be why it doesn't works anymore.

Especially with the way you describe the bug. Without the call to renpy.restart_interaction(), there's no reason for the screen to update itself, and therefore take count of the change made.
 
  • Like
Reactions: Playstorepers

Playstorepers

Member
May 24, 2020
169
82
173
Starts by writing the actions correctly and see if it solve your issue.

Functions must be used through the screen action, not passed directly.
Code:
           action [ Function( renpy.music.stop ),
                Queue("music", entirelistprevious, loop = True),
                Function( renpy.restart_interaction )]
That the bogus syntax you used worked until now doesn't mean that it was the right one. Depending on what changed in Ren'Py core, it can perfectly be why it doesn't works anymore.

Especially with the way you describe the bug. Without the call to renpy.restart_interaction(), there's no reason for the screen to update itself, and therefore take count of the change made.
Thank you for the suggestion.


I'm not that familiar with Ren'Py so I'm still struggling to truly grasp what you're saying.

Is there a particular reason why Function() is required when using action for a button?
And if so, why is renpy.music.stop and renpy.restart_interaction a function but not the queue music or play if I want to play a sfx?

And I used the renpy.restart_interaction to autorefresh the label and generate new lists to queue up if the button is pressed again. Is there a more elegant solution to it?

Either way: So far your suggestion unfortunately doesn't change anything. It behaves exactly the same.

But I would still love to hear why the Function() is necessary and what the "right" syntax is. I think it could be a good learning experience.

Thank you in advance if you find the time to answer and bother.

Edit:
Nvm. Everything with renpy. is a function, right?
Still, I would like to know why Function() is the "correct way".

And whether you might have other ideas why it doesn't work or maybe if everything could be solved more elegantly. Because so far the code still makes sense to me. And the fact that something IS working makes me want to think that Function can't be the only problem, right?

Edit2:
I don't know if it helps but adding arguments to the renpy.restart_interaction part (renpy.restart_interaction() instead of only renpy.restart_interaction) will lead to an error when trying to enter the phone screen: renpy.restart_interaction() was called 100 times without processing any input.

Which leads me to believe that this was changed between 8.0.3 and 8.4.1. Are there more elegant solutions or "right solutions" to refresh a screen then?
 
Last edited:

osanaiko

Engaged Member
Modder
Jul 4, 2017
3,357
6,447
707
Everything with renpy. is a function, right?
Renpy script *is* *not* *python*. It is a Domain Specific Language (DSL) that is compiled to a bytecode that the renpy engine executes.

It *looks* like python because that's how RenpyTom designed it.

AND you can mix in normal python with your renpy script BUT ONLY in specifically denoted sections ("python:" blocks, or the "$ " single-line-block short syntax).

Thus it is possible to define some *Python* functions that exist in the program space that your compiled renpy script runs.

Still, I would like to know why Function() is the "correct way".
The manual says it is the correct way.
Anne O'nymous, a very experienced dev, says it is the right way.
Please don't make it hard for people to help you by ignoring their advice.

The *only* correct way to call a general python function from a screen action is to use the specific syntax defined to do so. This is the "Function(...)" action.

Function(callable, *args, _update_screens=True, **kwargs)



It takes one or more parameters. The parameters are
callable -> this is the NAME of the function you want to call. it is not a python function call thus does not have parentheses.
*args, -> these are the positional arguments IF the function you are calling needs any.
(and named param "_update_screens" and potentiatlly other named params, but we can ignore these for your case)

The functon in question is defined here

in a python block (or line) it looks like: renpy.restart_interaction()
But to call it from the screen action "Function", you only need the name.

Therefore syntax to call the built-in-renpy function "restart_interaction" from a screen action is exactly as Anne O wrote.
Code:
    action Function( renpy.restart_interaction )
In your specific case you want to have three things happen as the action, therefore they are put into a list with the square brackets.
 
Last edited:

Playstorepers

Member
May 24, 2020
169
82
173
Renpy script *is* *not* *python*. It is a Domain Specific Language (DSL) that is compiled to a bytecode that the renpy engine executed.

it *looks* like python because that's how RenpyTom designed it.

AND you can mix in normal python with your renpy script BUT ONLY in specifically denoted sections ("python:" blocks, or the "$ " single-line-block short syntax).

Thus it is possible to define some *Python* functions that exist in the program space that your compiled renpy script runs.



The manual says it is the correct way.
Anne O'nymous, a very experienced dev, says it is the right way.
Please don't make it hard for people to help you by ignoring their advice.

The *only* correct way to call a general python function from a screen action is to use the specific syntax defined to do so. This is the "Function(...)" action.

Function(callable, *args, _update_screens=True, **kwargs)



It takes one or more parameters. The parameters are
callable -> this is the NAME of the function you want to call. it is not a python function call thus does not have parentheses.
*args, -> these are the positional arguments IF the function you are calling needs any.
(and named param "_update_screens" and potentiatlly other named params, but we can ignore these for your case)

The functon in question is defined here

in a python block (or line) it looks like: renpy.restart_interaction()
But to call it from the screen action "Function", you only need the name.

Therefore syntax to call the built-in-renpy function "restart_interaction" from a screen action is exactly as Anne O wrote.
Code:
    action Function( renpy.restart_interaction )
In your specific case you want to have three things happen as the action, therefore they are put into a list with the square brackets.
Thank you for your answer.

I've been trying to look into everything myself and you're right:
renpy. -stuff doesn't require the parentheses in a screen action, so that doesn't seem to be the culprit.

Your explanation of everything is pretty good, thanks for that.

I never intended to ignore any advice and I'm trying to use Function as often as possible now.
I'm just wondering why I should do it, even though nothing changes whether I add it or don't use it.
It's cleaner and catches exceptions, I guess?




And after hours of further testing I believe it is the next/previous song playlist determination itself and how variables have been handled in a screen, so maybe someone can help me find a fix with this information.

What I did:

I made a very simple version of the phone screen with one simple test.

Python:
#inside the phonescreen:

$entirelist = renpy.music.get_loop()

#I changed the next button to check the variable
        imagebutton auto "gui/phone/nextmusic_%s.png":
            xalign posbuttonnext
            yalign yposaudiobuttons
            focus_mask True
            action [SetVariable("entirelist", Function(renpy.music.get_loop)]
When you open the screen now, open the console (shft+o) and enter entirelist it still gives you: None
if you enter renpy.music.get_loop() you get the correct active playlist.
If you press the button and then open the console and press entirelist again you get:
store.Function object at 0x000000001
and NOT the active playlist.

So my new guess is that the variables after the Ren'Py update in a screen are not static but a reference.
That would explain the entire hocus pocus in a way.
But I still don't understand why that is and how to fix it.

If I find more time tomorrow, I will downgrade Ren'Py and try to narrow down the mistake from there, I guess.



Thank you in advance for everyone who's trying to help.

EDIT: It looks like I can circumvent most problems by just simulating a music room with my phone screen and using the mr.Next function.

Will look into it by tomorrow.



EDIT 2:
If it helps, I found another feature to explain the problem:

Back when I was using 8.0.3 and a song was already playing in the music-channel, you could load a savefile which had the same song in its playlist. the song would continue without a pause with the next song in the playlist and the entire playlist queued.

If I now load a savefile in 8.4.1 with the same song already playing, it will actively stop the song and start it anew.
That's most definetely part of the reason, I'd say.
Will look into it tomorrow
 
Last edited:

osanaiko

Engaged Member
Modder
Jul 4, 2017
3,357
6,447
707
I haven't dug into your script or really understand what you are trying to do, so I can't respond to those questions.

However this question, I think I know the answer (anne O'nymous is this correct?)

I'm trying to use Function as often as possible now.
I'm just wondering why I should do it, even though nothing changes whether I add it or don't use it.
It's cleaner and catches exceptions, I guess?
The reason why it might seem like it doesn't matter if the parens are present or not: When you have a screen action with a python function call (i.e. with parentheses) that is not wrapped in the Function action, the engine actually goes off and executes that function during screen evaluation and then uses the return value as a string to continue processing the screen code. If this python function has only idempotent side effects AND only returns something "safe" (like null), then you might not notice anything is going wrong. Basically, you would only ever use this if you wanted your function to modify the screen code definition on the fly!

It's not a good idea to use actual python functions in a screen action unless you really know what you are doing. One of the reasons is that screens have "speculative execution" - the renpy engine is repeatedly running the screen code in the background, even when the user has NOT yet clicked anything.

From the docs:
Screens must not cause side effects that are visible from outside the screen. Ren'Py will run a screen multiple times, as it deems necessary. It runs a screen as part of the image prediction process, before the screen is first shown. As a result, if running a screen has side effects, those side effects may occur at unpredictable times.
(this page: )

Furthermore, the behavior of screen actions in the renpy cpde might change in the future - and RenpyTom will only be taking care of the case where the documentation was followed - python function calls should be executed via the Function( _name, *args, ...) action.
 

Playstorepers

Member
May 24, 2020
169
82
173
I haven't dug into your script or really understand what you are trying to do, so I can't respond to those questions.

However this question, I think I know the answer (anne O'nymous is this correct?)



The reason why it might seem like it doesn't matter if the parens are present or not: When you have a screen action with a python function call (i.e. with parentheses) that is not wrapped in the Function action, the engine actually goes off and executes that function during screen evaluation and then uses the return value as a string to continue processing the screen code. If this python function has only idempotent side effects AND only returns something "safe" (like null), then you might not notice anything is going wrong. Basically, you would only ever use this if you wanted your function to modify the screen code definition on the fly!

It's not a good idea to use actual python functions in a screen action unless you really know what you are doing. One of the reasons is that screens have "speculative execution" - the renpy engine is repeatedly running the screen code in the background, even when the user has NOT yet clicked anything.

From the docs:
Screens must not cause side effects that are visible from outside the screen. Ren'Py will run a screen multiple times, as it deems necessary. It runs a screen as part of the image prediction process, before the screen is first shown. As a result, if running a screen has side effects, those side effects may occur at unpredictable times.
(this page: )

Furthermore, the behavior of screen actions in the renpy cpde might change in the future - and RenpyTom will only be taking care of the case where the documentation was followed - python function calls should be executed via the Function( _name, *args, ...) action.
I completely forgot about that feature with screens running multiple times even before the first screen is shown, so thanks for the reminder.

And it's as I expected then. It basically autocompletes the Function but it's safer to just give it to it every time.
So thank you for your answer.



Update on the problem:
So far I can't tell where the problem lies but I can say this if it helps:
Even in 8.0.3 the console (shft + o) yields "None" if you ask for variables like entirelistprevious or even trackname.
Everything does work perfectly there though.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
12,725
20,887
1,026
And I used the renpy.restart_interaction to autorefresh the label and generate new lists to queue up if the button is pressed again. Is there a more elegant solution to it?
*sigh* I provided the link for a reason... Following it and reading what is said would have answered your question:
"_update_screens
When true, the interaction restarts and the screens are updated after the function returns."


But I would still love to hear why the Function() is necessary and what the "right" syntax is. I think it could be a good learning experience.
Because a screen action do way more than just performing an action. Once again something that you would have been able to understand by yourself by just following the link I provided.


Everything with renpy. is a function, right?
Why the hell would they all be a function?
There's variables, like renpy.version_name, modules, like renpy.display, namespace, like renpy.sound, as well as classes, like renpy.Render().


[...] or maybe if everything could be solved more elegantly. Because so far the code still makes sense to me.
your code:
Python:
    $track = renpy.music.get_playing(channel='music')
    #write the full songname + artist:
    if track == None:
        $trackname = "No song"
    elif track == "audio/abc.mp3":
        $trackname = "ABC by XYZ"
        [...]
    else:
        $trackname = "Unknown"
a basic code:
Python:
define audioTracks = { "None": "No song", "audio/abc.mp3": "ABC by Jackson Five", [...] }

screen phone():
    [...]
    # Since you only use /track/ for this it's not even needed.
    $ trackname = audioTracks.get( renpy.music.get_playing(channel='music'), "Unknown" )

It *looks* like python because that's how RenpyTom designed it.
More precisely because PyTom is familiar with Python and used Python to build the engine. This permit to standardise the syntax and therefore to be easier to use as a whole.


The functon in question is defined here
Funnily it's not even necessary to call it when there's another function (and therefore Function()) in the chain of actions. When put at the end of the chain, the "_uptade_screens" argument would replace it in a secure way.


In your specific case you want to have three things happen as the action, therefore they are put into a list with the square brackets.
Funny not funny that Ren'Py strength is also its flaw; it's to lenient with the syntax.


However this question, I think I know the answer (anne O'nymous is this correct?)
It's correct.


It's not a good idea to use actual python functions in a screen action unless you really know what you are doing.
More globally, as said by the bit of doc that you quoted, it's not a good idea to use actual function in screens, whatever if it's as screen action or as inline python in the screen definition. And the same apply for computations that do not rely on purely local variables (declared throught the default screen statement).
There's case where it can't be avoided, but there's always a possible side effect since the screen will be proceessed (and therefore the function called) many time before the screen is shown (due to the prediction feature), then more or less 5 times by seconds once the screen will be displayed.
So, to stay basic, something like $ myVar += 1 would increase the value around ten times before the screen is displayed, then around 5 times every second as long as the screen will be displayed.


/!\ It's wrote on the fly and not tested, but this should works, or at least lead to the right direction. /!\
Python:
init python:
    def getQueued( channel="music" ):
        q = []
        for atom in renpy.audio.audio.channels[channel].queue:
            q.append( atom.filename )
        return q

   def shiftQueue( q, before=True ):
        marker = renpy.music.get_playing(channel='music')
        while q[0] != marker:
            q.append( q.pop( 0 ) )
        if before:
            q.insert( 0, q.pop() )
        else:
            q.append( q.pop( 0 ) )
        return q

   def prevSong( q ):
        renpy.music.queue( shiftQueue( q, True ), clear_queue=True, loop=True )

    def nextSong( q, marker ):
        renpy.music.queue( shiftQueue( q, False ), clear_queue=True, loop=True )

define audioTracks = { "None": "No song", "audio/abc.mp3": "ABC by Jackson Five", [...] }

#phone code reduced to the important bits
screen phone():
    tag menu
    modal True

    $ trackname = audioTracks.get( renpy.music.get_playing(channel='music'), "Unknown" )
    
    add "gui/phone.jpg"

    imagebutton auto "gui/phone/return_%s.png":
        xalign 0.5
        yalign 0.8
        focus_mask True
        action [Return()]
    
    text "Song: [trackname]":
        xalign 0.5
        yalign 0.9
    
    $activesong = renpy.music.get_playing()
    $ entirelist = getQueued()

    imagebutton auto "gui/phone/previousmusic_%s.png":
        xalign posbuttonprevious
        yalign yposaudiobuttons
        focus_mask True
        #  Only sensitive if not the first song of the queue
        sensitive entirelist.index[activesong] != 0
        action Function( prevSong, entirelist, _update_screens=True )

    imagebutton auto "gui/phone/nextmusic_%s.png":
        xalign posbuttonnext
        yalign yposaudiobuttons
        focus_mask True
        #  Only sensitive if not the last song of the queue
        sensitive entirelist.index[activesong] != len( entirelist ) - 1
        action Function( nextSong, entirelist, _update_screens=True )


Edit: corrected a bit the code.
 
Last edited:

Playstorepers

Member
May 24, 2020
169
82
173
*sigh* I provided the link for a reason... Following it and reading what is said would have answered your question:
"_update_screens
When true, the interaction restarts and the screens are updated after the function returns."




Because a screen action do way more than just performing an action. Once again something that you would have been able to understand by yourself by just following the link I provided.




Why the hell would they all be a function?
There's variables, like renpy.version_name, modules, like renpy.display, namespace, like renpy.sound, as well as classes, like renpy.Render().




your code:
Python:
    $track = renpy.music.get_playing(channel='music')
    #write the full songname + artist:
    if track == None:
        $trackname = "No song"
    elif track == "audio/abc.mp3":
        $trackname = "ABC by XYZ"
        [...]
    else:
        $trackname = "Unknown"
a basic code:
Python:
define audioTracks = { "None": "No song", "audio/abc.mp3": "ABC by Jackson Five", [...] }

screen phone():
    [...]
    # Since you only use /track/ for this it's not even needed.
    $ trackname = audioTracks.get( renpy.music.get_playing(channel='music'), "Unknown" )



More precisely because PyTom is familiar with Python and used Python to build the engine. This permit to standardise the syntax and therefore to be easier to use as a whole.




Funnily it's not even necessary to call it when there's another function (and therefore Function()) in the chain of actions. When put at the end of the chain, the "_uptade_screens" argument would replace it in a secure way.




Funny not funny that Ren'Py strength is also its flaw; it's to lenient with the syntax.




It's correct.




More globally, as said by the bit of doc that you quoted, it's not a good idea to use actual function in screens, whatever if it's as screen action or as inline python in the screen definition. And the same apply for computations that do not rely on purely local variables (declared throught the default screen statement).
There's case where it can't be avoided, but there's always a possible side effect since the screen will be proceessed (and therefore the function called) many time before the screen is shown (due to the prediction feature), then more or less 5 times by seconds once the screen will be displayed.
So, to stay basic, something like $ myVar += 1 would increase the value around ten times before the screen is displayed, then around 5 times every second as long as the screen will be displayed.


/!\ It's wrote on the fly and not tested, but this should works, or at least lead to the right direction. /!\
Python:
init python:
    def getQueued( channel="music" ):
        q = []
        for atom in renpy.audio.audio.channels[channel].queue:
            q.append( atom.filename )
        return q

    def shiftQueue( q, marker, before=True ):
        while q[0] != marker:
            q.append( q.pop( 0 ) )
        if before:
            q.insert( 0, q.pop() )
        else:
            q.append( q.pop( 0 ) )
        return q

    def prevSong( q, marker ):
        renpy.music.queue( shiftQueue( q, marker, True ), clear_queue=True, loop=True )

    def nextSong( q, marker ):
        renpy.music.queue( shiftQueue( q, marker, False ), clear_queue=True, loop=True )


define audioTracks = { "None": "No song", "audio/abc.mp3": "ABC by Jackson Five", [...] }

#phone code reduced to the important bits
screen phone():
    tag menu
    modal True

    $ trackname = audioTracks.get( renpy.music.get_playing(channel='music'), "Unknown" )
    
    add "gui/phone.jpg"

    imagebutton auto "gui/phone/return_%s.png":
        xalign 0.5
        yalign 0.8
        focus_mask True
        action [Return()]
    
    text "Song: [trackname]":
        xalign 0.5
        yalign 0.9
    
    $activesong = renpy.music.get_playing()
    $ entirelist = getQueued()

    imagebutton auto "gui/phone/previousmusic_%s.png":
        xalign posbuttonprevious
        yalign yposaudiobuttons
        focus_mask True
        #  Only sensitive if not the first song of the queue
        sensitive entirelist.index[activesong] != 0
        action Function( prevSong, entirelist, activesong, _update_screens=True )

    imagebutton auto "gui/phone/nextmusic_%s.png":
        xalign posbuttonnext
        yalign yposaudiobuttons
        focus_mask True
        #  Only sensitive if not the last song of the queue
        sensitive entirelist.index[activesong] != len( entirelist ) - 1
        action Function( nextSong, entirelist, activesong, _update_screens=True )
Thank you for the in-depth answer. Really.

I will read the documentation on screen actions again and try to understand your suggestions.
On first glance most of it makes sense to me but truly understanding it will take some time, I guess.
 
  • Like
Reactions: osanaiko

Playstorepers

Member
May 24, 2020
169
82
173
*sigh* I provided the link for a reason... Following it and reading what is said would have answered your question:
"_update_screens
When true, the interaction restarts and the screens are updated after the function returns."




Because a screen action do way more than just performing an action. Once again something that you would have been able to understand by yourself by just following the link I provided.




Why the hell would they all be a function?
There's variables, like renpy.version_name, modules, like renpy.display, namespace, like renpy.sound, as well as classes, like renpy.Render().




your code:
Python:
    $track = renpy.music.get_playing(channel='music')
    #write the full songname + artist:
    if track == None:
        $trackname = "No song"
    elif track == "audio/abc.mp3":
        $trackname = "ABC by XYZ"
        [...]
    else:
        $trackname = "Unknown"
a basic code:
Python:
define audioTracks = { "None": "No song", "audio/abc.mp3": "ABC by Jackson Five", [...] }

screen phone():
    [...]
    # Since you only use /track/ for this it's not even needed.
    $ trackname = audioTracks.get( renpy.music.get_playing(channel='music'), "Unknown" )



More precisely because PyTom is familiar with Python and used Python to build the engine. This permit to standardise the syntax and therefore to be easier to use as a whole.




Funnily it's not even necessary to call it when there's another function (and therefore Function()) in the chain of actions. When put at the end of the chain, the "_uptade_screens" argument would replace it in a secure way.




Funny not funny that Ren'Py strength is also its flaw; it's to lenient with the syntax.




It's correct.




More globally, as said by the bit of doc that you quoted, it's not a good idea to use actual function in screens, whatever if it's as screen action or as inline python in the screen definition. And the same apply for computations that do not rely on purely local variables (declared throught the default screen statement).
There's case where it can't be avoided, but there's always a possible side effect since the screen will be proceessed (and therefore the function called) many time before the screen is shown (due to the prediction feature), then more or less 5 times by seconds once the screen will be displayed.
So, to stay basic, something like $ myVar += 1 would increase the value around ten times before the screen is displayed, then around 5 times every second as long as the screen will be displayed.


/!\ It's wrote on the fly and not tested, but this should works, or at least lead to the right direction. /!\
Python:
init python:
    def getQueued( channel="music" ):
        q = []
        for atom in renpy.audio.audio.channels[channel].queue:
            q.append( atom.filename )
        return q

    def shiftQueue( q, marker, before=True ):
        while q[0] != marker:
            q.append( q.pop( 0 ) )
        if before:
            q.insert( 0, q.pop() )
        else:
            q.append( q.pop( 0 ) )
        return q

    def prevSong( q, marker ):
        renpy.music.queue( shiftQueue( q, marker, True ), clear_queue=True, loop=True )

    def nextSong( q, marker ):
        renpy.music.queue( shiftQueue( q, marker, False ), clear_queue=True, loop=True )


define audioTracks = { "None": "No song", "audio/abc.mp3": "ABC by Jackson Five", [...] }

#phone code reduced to the important bits
screen phone():
    tag menu
    modal True

    $ trackname = audioTracks.get( renpy.music.get_playing(channel='music'), "Unknown" )

    add "gui/phone.jpg"

    imagebutton auto "gui/phone/return_%s.png":
        xalign 0.5
        yalign 0.8
        focus_mask True
        action [Return()]

    text "Song: [trackname]":
        xalign 0.5
        yalign 0.9

    $activesong = renpy.music.get_playing()
    $ entirelist = getQueued()

    imagebutton auto "gui/phone/previousmusic_%s.png":
        xalign posbuttonprevious
        yalign yposaudiobuttons
        focus_mask True
        #  Only sensitive if not the first song of the queue
        sensitive entirelist.index[activesong] != 0
        action Function( prevSong, entirelist, activesong, _update_screens=True )

    imagebutton auto "gui/phone/nextmusic_%s.png":
        xalign posbuttonnext
        yalign yposaudiobuttons
        focus_mask True
        #  Only sensitive if not the last song of the queue
        sensitive entirelist.index[activesong] != len( entirelist ) - 1
        action Function( nextSong, entirelist, activesong, _update_screens=True )
Alright, I still don't fully grasp screens, functions and renpy but it's a good start, I believe.

I also understood what your code is trying to do and why it executes more elegantly.

It just doesn't work correctly when it comes to shiftQueue. The rest seems to be working nicely.

Taking your original code, I was able to open the phone screen. Clicking on the button however resulted in the game freezing. And no. Not the Ren'Py error screen. I'm talking about the game being stuck indefinetely and requiring a forced shutdown.

On further examination, I exchanged

Python:
action Function( nextSong, entirelist, activesong, _update_screens=True )

with

action Function( nextSong(entirelist, activesong), _update_screens=True )
it looked like the more correct syntax to me, is that right?


Either way, doing it this way ensures the game being frozen while trying to open the phone menu which would make sense if we consider Ren'Py's prediction trying to run every code on screen load-up.


The freeze indicated to me that the culprit is most likely the while clause in the shiftQueue function to rearrange the queue to start with the active song before shifting.

if I remove the while clause the game doesn't freeze anymore, just as expected, but this time returning a classic error-screen with "None-type" object is not callable which ultimately lets me believe that I still struggle to grasp how Ren'Py handles variables in screens, which is why I tried to further narrow it down and instead just run this phone screen without any buttons:

Python:
    $activesong = renpy.music.get_playing()
    $entirelist = getQueued()
    $test = shiftQueue(entirelooplist, activesong, True)

    text "Active: [activesong]":
        xalign 0.5
        yalign 0.1

    text "entire list: [entirelist]":
        xalign 0.5
        yalign 0.4

    text "test: [test]":
        xalign 0.5
        yalign 0.8
which put me into full confusion when i used a playlist with multiple songs and:

$renpy.random.shuffle(tracks1)
$renpy.music.queue(tracks1, loop = True)

in the script-file to play the list with 4 songs.

This screen shows on its labels:
active: audio/a.mp3
entirelist: ['audio/b.mp3', 'audio/c.mp3', 'audio/d.mp3']
test: ['audio/b.mp3', 'audio/c.mp3', 'audio/d.mp3']

with not only the actual active song missing but entirelist and test even being identical.

I assume the culprit for this is the fact that the while command was deleted by me...?

Then again: Why would entirelist be identical to test, though? Since entire list copies the filename with the getQueued command, it should also contain audio/a.mp3 right? I literally hear the song in the background.

Which lead me all to the conclusion that I still don't know shit about the way renpy handles variables in screens. There must be something fundamental that I'm misunderstanding, right?



Thank you all in advance for possible suggestions what I'm doing wrong.

EDIT: Oh wait. Could it be that the shiftQueue function edits entirelist? In that case the only mystery that remains is why the while clause doesn't work, right?
I'll look into it tomorrow.

You don't have permission to view the spoiler content. Log in or register now.
 
Last edited:

MissFortune

I Was Once, Possibly, Maybe, Perhaps… A Harem King
Respected User
Game Developer
Aug 17, 2019
6,790
10,503
872
I'll throw my own into the bucket. Don't know if it's any good, but it's working for me. The only difference is that it's not on a phone, it just pops up when you hover over the top right side of the screen.

So, basically I have a whole .rpy/.rpyc dedicated to the player. It starts with putting the music for each scene into a playlist:

You don't have permission to view the spoiler content. Log in or register now.

Your visual adjustments for the player will be under "screen music_player():". Make sure to get some imagebuttons for the play/pause/next/back/etc. Adjust the trigger accordingly, if needed
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
12,725
20,887
1,026
It just doesn't work correctly when it comes to shiftQueue.
Hmm?
From what you say it looks like there's a endless loop.
It would need to be confirmed, but I assume that Ren'Py drop the file out of the queue when it starts playing it.

This being said, and thinking a bit more about it, MissFortune approach, with playlist being stored in a separated list, is better. Ren'Py music queue do not advance in the list, or loop it, so whatever if the entry is dropped before or after, there's never a "previous" song in it.
 

Playstorepers

Member
May 24, 2020
169
82
173
I'll throw my own into the bucket. Don't know if it's any good, but it's working for me. The only difference is that it's not on a phone, it just pops up when you hover over the top right side of the screen.

So, basically I have a whole .rpy/.rpyc dedicated to the player. It starts with putting the music for each scene into a playlist:

You don't have permission to view the spoiler content. Log in or register now.

Your visual adjustments for the player will be under "screen music_player():". Make sure to get some imagebuttons for the play/pause/next/back/etc. Adjust the trigger accordingly, if needed
Damn. Thanks for offering your code.

I'll look into it and take what I can, thank you very much.
 

Playstorepers

Member
May 24, 2020
169
82
173
Okay, it's completely doomed with 8.4.1.

I made a phone screen to test something:

Code:
screen smartphone():
    tag menu
    modal True

    imagebutton auto "gui/button/phone/nextmusic_%s.png":
            xalign posbuttonnext
            yalign yposaudiobuttons
            focus_mask True
            action [Function(renpy.music.play(testtracks, loop=True))]
This screen is called via

Code:
screen phone():
    zorder 50
    imagebutton auto "gui/phonebutton_%s.png":
        xalign 0.05
        yalign 0.05
        focus_mask True
        action [ShowMenu("smartphone")]
        alternate [ShowMenu("smartphone")]
And when I HOVER over the phonebutton (the button that calls the phone screen, not my temporary "Next-music" button), it starts playing the testtracks.

So something fundamentally must have changed between the Ren'Py versions in terms of screens and predictions, right?

Does anyone know what I'm missing?
 

Playstorepers

Member
May 24, 2020
169
82
173
I see the error in my ways.

action [Function(renpy.music.play, testtracks, loop=True)]

does work now.

So thanks for hitting me over the head with the red paint. I needed that.


Which puts the next problem on the line.

Playing any music in the phone screen and then pressing the return button will somehow begin the old playlist that was queued before entering the phone screen.

I assume it has to do with the same changes that nowadays reboot the music if you load a savefile.
Back in 8.0.3 the music wouldn't stop and be played again if the active song is also in the queue of the savefile somewhere.


I'll try to use MissFortune's code tomorrow in the hopes that it doesn't show the same behaviour and then report back.