In the music room, the progress bar controls the playing time of music

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
luoying
Newbie
Posts: 7
Joined: Wed Apr 06, 2022 8:31 pm
Contact:

In the music room, the progress bar controls the playing time of music

#1 Post by luoying » Thu Apr 07, 2022 7:26 am

I made a music room. I hope it can drag and change the progress like a player (the progress bar can be dragged), and drag the progress bar to change the time point of music. I am a novice and have no programming experience, which is too difficult for me.What should I do? My English is very poor. I'm sorry.

Code: Select all

init python:
    def get_audio_duration():
        duration = renpy.music.get_duration()
        return convert_format(int(duration))

    def get_audio_position():
        music_pos = renpy.music.get_pos()
        if music_pos:
            return convert_format(int(music_pos))
        return "0"

    def convert_format(second):
        minute = second // 60
        second = second % 60
        result = ""
        if minute:
            result = str(minute) + ":"
            if second < 10:
                result += '0'
        elif second>9:
            result +='0:'
        else:
            result +='0:0'
        result += str(second)
        return result

screen audio:
    #timer 0.1 action [Hide('audio'), Show('audio')]
    timer 0.1:
        action [SetVariable('duration',get_audio_duration()),SetVariable('music_pos',get_audio_position())]
        repeat True
    vbox:
        xpos 0.5
        ypos 0.2
        python:
            duration = get_audio_duration()
            music_pos = get_audio_position()
        hbox:
            spacing 20
            text music_pos
            text "/"
            text duration

        bar:
            value AudioPositionValue("music", update_interval=0.1)
            xalign 0.5
            yalign 0.5
            xsize 500
            xmaximum 500

init python:

  
    mr = MusicRoom(fadeout=1.0)


    mr.add("audio/1.ogg", always_unlocked=True)
    mr.add("audio/2.ogg", always_unlocked=True)
    mr.add("audio/3.ogg", always_unlocked=True)

    class PlayerButton:
        def __init__(self, channel='music', icon_path='gui/', mr=mr):
            self.channel = channel
            self.icon_path = icon_path
            self.mr = mr

        def get_icon(self):
            if not renpy.music.is_playing() and not renpy.music.get_pause():
                return self.icon_path + "play_%s.png"
            if renpy.music.get_pause(self.channel):
                return self.icon_path + "play_%s.png"
            return self.icon_path + "pause_%s.png"

        def click(self):
            if not renpy.music.is_playing() and not renpy.music.get_pause():
                self.mr.play()
                return
            renpy.music.set_pause(not renpy.music.get_pause(self.channel),
                channel=self.channel)

    play_button = PlayerButton(mr=mr)

label start:
    #play music "music/voice.ogg"
    show screen music_room
    call screen audio
    pause


screen music_room:
    #timer 1 action [Hide('music_room'), Show('music_room')]
    timer 0.1:
        action [SetVariable('duration',get_audio_duration()),SetVariable('music_pos',get_audio_position())]
        repeat True
    frame:
        xpos 0.3
        ypos 0.3
        has vbox

        
        textbutton "Track 1" action mr.Play("audio/1.ogg")
        textbutton "Track 2" action mr.Play("audio/2.ogg")
        textbutton "Track 3" action mr.Play("audio/3.ogg")

        null height 20

      
        textbutton "Next" action mr.Next()
        textbutton "Previous" action mr.Previous()

        null height 20

     
        textbutton "Main Menu" action ShowMenu("main_menu")
        python:
            duration = str(int(renpy.music.get_duration()))
            music_pos = renpy.music.get_pos()
            if not music_pos:
                music_pos = 0
            music_pos = str(int(music_pos))
        text duration
        text music_pos

        bar:
            value AudioPositionValue(channel='music', update_interval=0.1)
            xalign 0.5
            yalign 0.5
            xsize 500
            xmaximum 500
        fixed:
            pos(0.5, 0.6)
            hbox:
                vbox:
                    text str(renpy.music.get_pause())
                    imagebutton:
                        auto play_button.get_icon()
                        focus_mask True
                        action Function(play_button.click)

  
    on "replace" action mr.Stop()

  
    on "replaced" action Play("music", "track1.ogg")

jeffster
Regular
Posts: 123
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: In the music room, the progress bar controls the playing time of music

#2 Post by jeffster » Thu Apr 07, 2022 10:19 am

In my games I tried to use AudioPositionValue, but it seems that it freezes sometimes. So I ended up creating my own creator-defined displayables (CDD):

https://www.renpy.org/doc/html/cdd.html

One was to replace AudioPositionValue:

Code: Select all

    class SeekableBar(BarValue):
        def __init__(self):
            pos = renpy.music.get_pos()
            if pos == None:
                pos = 0.0
            self.range = renpy.music.get_duration()
            self.adj   = ui.adjustment(range = self.range, value = pos)

        def get_adjustment(self):
            pos = renpy.music.get_pos()
            if pos == None:
                pos = 0.0
            self.adj.change(pos)
            return self.adj

        def periodic(self, st):
            if self.range != renpy.music.get_duration():
                renpy.restart_interaction()
            self.get_adjustment()
            return 0.1

        def set_pos(self, pos):
            renpy.music.play("".join(("<from ", str(pos), ">",
                current_music_file())))
            music_paused = False
            update_queue()
Another bar was placed on top of the first, and intended to detect mouse positions on clicks:

Code: Select all

            fixed:
                xysize (1440, 32)
                bar:
                    id "seek_bar"
                    xysize (1440, 32)
                    pos (0, 0)
                    value SeekableBar()
                    style "audio_bar"

                # to overlap with the Seek Bar:
                add DetectionBar(pos = (0, 0))
Its class:

Code: Select all

#### To set position on an audio bar, put this displayable over it:

## To prevent second action on mouse up in the same position
default bar_seek_pos = 0.0

# To set effects on hover / unhover
default my_bar_hovered = False

# To show tooltip
default my_bar_tooltip = ""

## The displayable:

init python:
    class DetectionBar(renpy.Displayable):
        """ Gives a transparent overlay for audio bar to click and detect events.
        """
        def __init__(self, xysize = (1440, 32),
            hovered = None, unhovered = None,
            **kwargs):

            super(DetectionBar, self).__init__(**kwargs)
            self.img = renpy.displayable(Solid("#3333", xysize = xysize))
            self.width, self.height = xysize
            self.hovered = hovered
            self.unhovered = unhovered

        def render(self, width, height, st, at):

            own_render = renpy.render(self.img, width, height, st, at)

            render = renpy.Render(self.width, self.height)
            render.blit(own_render, (0, 0))

            return render

        def event(self, ev, x, y, st):
            global bar_seek_pos, my_bar_hovered, my_bar_tooltip
            if 0 <= x < self.width and 0 <= y < self.height:

                # Hovered:
                if not my_bar_hovered:
                    my_bar_hovered = True
                    self.hovered()

                # Audio position:
                dur = renpy.music.get_duration()
                seek_pos = round(1.0 * dur * x / self.width, 1)

                # Tooltip:
                secs = int(seek_pos)
                mins = secs // 60
                secs %= 60
                my_bar_tooltip = "%2d:%02d" % (mins, secs)

                # Change the track position on click:
                if ev.type in (pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP):

                    # If button up in the same position basically -
                    # skip double action
                    if abs(seek_pos - bar_seek_pos) < 2.0 and \
                        ev.type == pygame.MOUSEBUTTONUP:
                            return
                    bar_seek_pos = seek_pos

                    # Set playing position
                    bar = renpy.get_displayable("preferences", "seek_bar")
                    bar.value.set_pos(seek_pos)
            else:
                # Unhovered:
                if my_bar_hovered:
                    my_bar_hovered = False
                    my_bar_tooltip = ""
                    self.unhovered()
I hope my code would help you to go in the right direction. I wrote it quite some time ago, so I can't really explain in details what it does. But if you study CDD and a perhaps bit of pygame, you could see that there's nothing really complex, and you could do even better.

Best wishes!

shadowsword68
Newbie
Posts: 4
Joined: Fri Apr 08, 2022 3:45 am
Contact:

Re: In the music room, the progress bar controls the playing time of music

#3 Post by shadowsword68 » Fri Apr 08, 2022 5:46 am

There seems to be some problems with the layer of clicking and detecting events. It can't run correctly. When the mouse pointer moves over the layer, the project can't run. I think there's any way to solve it? thank you!

luoying
Newbie
Posts: 7
Joined: Wed Apr 06, 2022 8:31 pm
Contact:

Re: In the music room, the progress bar controls the playing time of music

#4 Post by luoying » Fri Apr 08, 2022 9:11 pm

When I move the mouse over the bar, it will report an error. This is its feedback. I just add the code to my file and try to run it.

Code: Select all

```
I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/script.rpy", line 187, in script
    call screen audio
  File "renpy/common/000statements.rpy", line 570, in execute_call_screen
    store._return = renpy.call_screen(name, *args, **kwargs)
  File "game/script.rpy", line 44, in event
    self.hovered()
TypeError: 'NoneType' object is not callable

-- Full Traceback ------------------------------------------------------------

Full traceback:
  File "game/script.rpy", line 187, in script
    call screen audio
  File "renpy/ast.py", line 1969, in execute
    self.call("execute")
  File "renpy/ast.py", line 1957, in call
    return renpy.statements.call(method, parsed, *args, **kwargs)
  File "renpy/statements.py", line 278, in call
    return method(parsed, *args, **kwargs)
  File "renpy/common/000statements.rpy", line 570, in execute_call_screen
    store._return = renpy.call_screen(name, *args, **kwargs)
  File "renpy/exports.py", line 2983, in call_screen
    rv = renpy.ui.interact(mouse="screen", type="screen", roll_forward=roll_forward)
  File "renpy/ui.py", line 298, in interact
    rv = renpy.game.interface.interact(roll_forward=roll_forward, **kwargs)
  File "renpy/display/core.py", line 3101, in interact
    repeat, rv = self.interact_core(preloads=preloads, trans_pause=trans_pause, pause=pause, pause_start=pause_start, **kwargs)
  File "renpy/display/core.py", line 3910, in interact_core
    rv = root_widget.event(ev, x, y, 0)
  File "renpy/display/layout.py", line 1053, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "renpy/display/layout.py", line 1053, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "renpy/display/layout.py", line 1053, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "renpy/display/screen.py", line 720, in event
    rv = self.child.event(ev, x, y, st)
  File "renpy/display/layout.py", line 1053, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "renpy/display/layout.py", line 1243, in event
    rv = super(Window, self).event(ev, x, y, st)
  File "renpy/display/layout.py", line 245, in event
    rv = d.event(ev, x - xo, y - yo, st)
  File "renpy/display/layout.py", line 1053, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "renpy/display/layout.py", line 1053, in event
    rv = i.event(ev, x - xo, y - yo, cst)
  File "game/script.rpy", line 44, in event
    self.hovered()
TypeError: 'NoneType' object is not callable

Windows-7-6.1.7601-SP1
Ren'Py 7.4.4.1439
53 1.0
Sat Apr  9 09:07:20 2022
```

luoying
Newbie
Posts: 7
Joined: Wed Apr 06, 2022 8:31 pm
Contact:

Re: In the music room, the progress bar controls the playing time of music

#5 Post by luoying » Fri Apr 08, 2022 9:15 pm

It seems to be an error, but I don't know how to change it. This is the code I changed. If there is an error, I hope you can point it out. Thank you for your help!

Code: Select all

#### To set position on an audio bar, put this displayable over it:

## To prevent second action on mouse up in the same position
default bar_seek_pos = 0.0

# To set effects on hover / unhover
default my_bar_hovered = False

# To show tooltip
default my_bar_tooltip = ""

## The displayable:

init python:
    class DetectionBar(renpy.Displayable):
        """ Gives a transparent overlay for audio bar to click and detect events.
        """
        def __init__(self, xysize = (1440, 32),
            hovered = None, unhovered = None,
            **kwargs):

            super(DetectionBar, self).__init__(**kwargs)
            self.img = renpy.displayable(Solid("#3333", xysize = xysize))
            self.width, self.height = xysize
            self.hovered = hovered
            self.unhovered = unhovered

        def render(self, width, height, st, at):

            own_render = renpy.render(self.img, width, height, st, at)

            render = renpy.Render(self.width, self.height)
            render.blit(own_render, (0, 0))

            return render

        def event(self, ev, x, y, st):
            global bar_seek_pos, my_bar_hovered, my_bar_tooltip
            if 0 <= x < self.width and 0 <= y < self.height:

                # Hovered:
                if not my_bar_hovered:
                    my_bar_hovered = True
                    self.hovered()

                # Audio position:
                dur = renpy.music.get_duration()
                seek_pos = round(1.0 * dur * x / self.width, 1)

                # Tooltip:
                secs = int(seek_pos)
                mins = secs // 60
                secs %= 60
                my_bar_tooltip = "%2d:%02d" % (mins, secs)

                # Change the track position on click:
                if ev.type in (pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP):

                    # If button up in the same position basically -
                    # skip double action
                    if abs(seek_pos - bar_seek_pos) < 2.0 and \
                        ev.type == pygame.MOUSEBUTTONUP:
                            return
                    bar_seek_pos = seek_pos

                    # Set playing position
                    bar = renpy.get_displayable("preferences", "seek_bar")
                    bar.value.set_pos(seek_pos)
            else:
                # Unhovered:
                if my_bar_hovered:
                    my_bar_hovered = False
                    my_bar_tooltip = ""
                    self.unhovered()
    class SeekableBar(BarValue):
        def __init__(self):
            pos = renpy.music.get_pos()
            if pos == None:
                pos = 0.0
            self.range = renpy.music.get_duration()
            self.adj   = ui.adjustment(range = self.range, value = pos)

        def get_adjustment(self):
            pos = renpy.music.get_pos()
            if pos == None:
                pos = 0.0
            self.adj.change(pos)
            return self.adj

        def periodic(self, st):
            if self.range != renpy.music.get_duration():
                renpy.restart_interaction()
            self.get_adjustment()
            return 0.1

        def set_pos(self, pos):
            renpy.music.play("".join(("<from ", str(pos), ">",
                current_music_file())))
            music_paused = False
            update_queue()

init python:
    def get_audio_duration():
        duration = renpy.music.get_duration()
        return convert_format(int(duration))

    def get_audio_position():
        music_pos = renpy.music.get_pos()
        if music_pos:
            return convert_format(int(music_pos))
        return "0"

    def convert_format(second):
        minute = second // 60
        second = second % 60
        result = ""
        if minute:
            result = str(minute) + ":"
            if second < 10:
                result += '0'
        elif second>9:
            result +='0:'
        else:
            result +='0:0'
        result += str(second)
        return result

screen audio:
    #timer 0.1 action [Hide('audio'), Show('audio')]
    timer 0.1:
        action [SetVariable('duration',get_audio_duration()),SetVariable('music_pos',get_audio_position())]
        repeat True
    vbox:
        xpos 0.5
        ypos 0.2
        python:
            duration = get_audio_duration()
            music_pos = get_audio_position()
        hbox:
            spacing 20
            text music_pos
            text "/"
            text duration

        bar:
            value AudioPositionValue("music", update_interval=0.1)
            xalign 0.5
            yalign 0.5
            xsize 500
            xmaximum 500

init python:

   
    mr = MusicRoom(fadeout=1.0)

  
    mr.add("audio/1.ogg", always_unlocked=True)
    mr.add("audio/2.ogg", always_unlocked=True)
    mr.add("audio/3.ogg", always_unlocked=True)

    class PlayerButton:
        def __init__(self, channel='music', icon_path='gui/', mr=mr):
            self.channel = channel
            self.icon_path = icon_path
            self.mr = mr

        def get_icon(self):
            if not renpy.music.is_playing() and not renpy.music.get_pause():
                return self.icon_path + "play_%s.png"
            if renpy.music.get_pause(self.channel):
                return self.icon_path + "play_%s.png"
            return self.icon_path + "pause_%s.png"

        def click(self):
            if not renpy.music.is_playing() and not renpy.music.get_pause():
                self.mr.play()
                return
            renpy.music.set_pause(not renpy.music.get_pause(self.channel),
                channel=self.channel)

    play_button = PlayerButton(mr=mr)

label start:
    #play music "music/voice.ogg"
    show screen music_room
    call screen audio
    pause


screen music_room:
    #timer 1 action [Hide('music_room'), Show('music_room')]
    timer 0.1:
        action [SetVariable('duration',get_audio_duration()),SetVariable('music_pos',get_audio_position())]
        repeat True
    frame:
        xpos 0.3
        ypos 0.3
        has vbox

      
        textbutton "Track 1" action mr.Play("audio/1.ogg")
        textbutton "Track 2" action mr.Play("audio/2.ogg")
        textbutton "Track 3" action mr.Play("audio/3.ogg")

        null height 20

     
        textbutton "Next" action mr.Next()
        textbutton "Previous" action mr.Previous()

        null height 20

      
        textbutton "Main Menu" action ShowMenu("main_menu")
        python:
            duration = str(int(renpy.music.get_duration()))
            music_pos = renpy.music.get_pos()
            if not music_pos:
                music_pos = 0
            music_pos = str(int(music_pos))
        text duration
        text music_pos

        bar:
            value AudioPositionValue(channel='music', update_interval=0.1)
            xalign 0.5
            yalign 0.5
            xsize 500
            xmaximum 500
        fixed:
            xysize (1440, 32)
            bar:
                id "seek_bar"
                xysize (1440, 32)
                pos (0, 0)
                value SeekableBar()
                style "audio_bar"

            # to overlap with the Seek Bar:
            add DetectionBar(pos = (0, 0))
        #bar value Preference ("music_room_mixer volume") style "music_room_volume_bar"
        fixed:
            pos(0.5, 0.6)
            hbox:
                vbox:
                    text str(renpy.music.get_pause())
                    imagebutton:
                        auto play_button.get_icon()
                        focus_mask True
                        action Function(play_button.click)

   
    on "replace" action mr.Stop()

   
    on "replaced" action Play("music", "track1.ogg")

jeffster
Regular
Posts: 123
Joined: Wed Feb 03, 2021 9:55 pm
Contact:

Re: In the music room, the progress bar controls the playing time of music

#6 Post by jeffster » Fri Apr 08, 2022 11:14 pm

Hi.
I think I used functions .hovered() and .unhovered() for something like mouse cursor changes or whatnot. I think the error message says that probably there are no such functions in your code. You should maybe add them or even better remove them at all. The code will be simpler. Remember, I didn't say my snippets would work out of the box. They just show you the principles I used. Good luck!

luoying
Newbie
Posts: 7
Joined: Wed Apr 06, 2022 8:31 pm
Contact:

Re: In the music room, the progress bar controls the playing time of music

#7 Post by luoying » Sat Apr 09, 2022 2:26 am

I see. Thank you for your help. Thank you very much

Post Reply

Who is online

Users browsing this forum: enaielei