Text scrolling/ dialogue sounds, per word

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.
Message
Author
User avatar
Kinmoku
Miko-Class Veteran
Posts: 591
Joined: Mon Aug 11, 2014 9:39 am
Completed: One Night Stand
Projects: VIDEOVERSE
Tumblr: gamesbykinmoku
itch: kinmoku
Location: Germany
Contact:

Text scrolling/ dialogue sounds, per word

#1 Post by Kinmoku »

Hi guys,

I have a question which I've not been able to find an answer to on the forums. I saw this but an answer didn't seem to be found:
Crazy Li wrote:
AlfieMachica123 wrote:base from the code you have given me, where do i insert the loop?

Code: Select all

def beepy_voice(event, interact=True, **kwargs):
        if not interact:
            return
        if event == "show_done":
            renpy.sound.play("beeps.ogg")
        elif event == "slow_done":
            renpy.sound.stop()
and also, is it possible to make the sound play accordingly with every word (one word plays one beep sound)? just to make the sound not so constant, just like ace attorney., i hope you understand what i am trying to say here, :) thank you so much, opinion is much appreciated.
To loop, just add in a Loop=True. So it becomes...

Code: Select all

def beepy_voice(event, interact=True, **kwargs):
        if not interact:
            return
        if event == "show_done":
            renpy.sound.play("beeps.ogg", loop=True)
        elif event == "slow_done":
            renpy.sound.stop()
Though that doesn't sync of with the word display, it just keeps repeating the sound until the text finishes displaying. If you can change your text speed to a CPS that makes one word appear at a time, it might get close.
I'm wondering if you can play a bleep every time a word appears in the dialogue, so just like Ace Attorney when sometimes it will sound fast, and other times it will pause and be slower.

I tried the usual method but it ocassionally crops the sound file awkwardly, making pop/ snap noises. It's not very nice :(

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Text scrolling/ dialogue sounds, per word

#2 Post by xela »

It is likely to get really tricky, really fast... because even if you got the what string from your characters say, analyzed it and came up with "delays to play sounds" list based of a what.split(" ") string to len(word)/cps, it would not account for any possible extend statements or any of the text tags that modify text cps/wait/etc.

==>
Modification to Text class itself is a very difficult task and you'd prolly not be asking this if you knew how to do it. So if you're willing to compromise and leave Ren'Py features like extend and some of the text tags out of your game, you could come up with a simplified system as described above that (in theory) might be reasonably simple to code or at least to give that a shot. If you need those features, it'll be close to impossible to do unless you know Python/UDD (Text Displayable) really, really well.
Like what we're doing? Support us at:
Image

User avatar
Kinmoku
Miko-Class Veteran
Posts: 591
Joined: Mon Aug 11, 2014 9:39 am
Completed: One Night Stand
Projects: VIDEOVERSE
Tumblr: gamesbykinmoku
itch: kinmoku
Location: Germany
Contact:

Re: Text scrolling/ dialogue sounds, per word

#3 Post by Kinmoku »

Hmm, I thought it would be too difficult :(

If there was a sound played every time there's a space/ break instead, is that easier to do?

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Text scrolling/ dialogue sounds, per word

#4 Post by xela »

Kinmoku wrote:If there was a sound played every time there's a space/ break instead, is that easier to do?
I'd expect it's the same thing... I sadly don't remember the Text class well enough to claim that it might be offhand, I once wanted to add a particle special effect to Text render but dropped the idea cause it seemed more trouble than it's worth and it was for just for fun.


===>>
Simplest possible version that comes to mind is what I suggested above but you'd have to drop extend to use it and text tags that control the speed (that you can actually fix I think with some extra work). The situation is this:

1) We only have access to a string so we don't really know what part if a text is being displayable at the moment. In order to get to that info, we need to address the Text Displayable that renders it.
2) Since we do have access to both, the string and the cps, we can calculate ourselfs when each of the words in the string is going to be displayed.
3) Since we can do that, we can act accordingly from within that function you suggested above, both calculations this simple and even string manipulations which python is kinda slow at, would still be lightning fast and should create no delay. So once we have the timings, we should be able to cook up a solution to play them at the correct intervals.
4) It should also be possible to end the sounds playing if the event supplied to the function is finished (like if a player fast clicked to see the whole text or is skipping).

Problems:

Extend statement: I am not sure how to get just to the extend statement from that function... it should sure as hell be possible and I have some ideas but it's "kind of a problem".
Tags that change texts speed (or any other tags): Would have to be accounted for with the code since as far as Python is concerned, those are just parts of code. And the speed changers also need to be accounted for in calculations...

So, the concept itself is simple and sounds perfectly manageable, but if you want to keep using extend and text tags, it suddenly becomes messy...

===>>
As I've said, it is worth trying in a simple test case (give me 10 mins to try testing a simple version of it), to find out if it is working at all and if it works well, consider if it is worth trying to get a handle on special cases like tags/extend.
Like what we're doing? Support us at:
Image

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Text scrolling/ dialogue sounds, per word

#5 Post by xela »

Written as proof of concept:

Code: Select all

init python:
    def get_screen_var(screen, name):
        cs = renpy.get_screen(screen)
        if cs is not None:
            return cs.scope[name]
        
    def beepy_voice(event, interact=True, **kwargs):
        if not interact:
            return
            
        if event == "show_done":
            s = get_screen_var("say", "what")
            
            if s:
                intervals = [] 
                words = s.split()
                speed = float(_preferences.text_cps)
                for w in words:
                    intervals.append((len(w)+1)/speed) # We add 1 to account for spaces...
                renpy.show_screen("silly_timers", intervals, "beep-02.mp3")
        elif event == "slow_done":
            renpy.hide_screen("silly_timers")
            
default x = Character("X", callback=beepy_voice)

screen silly_timers(intervals, sfx):
    default play_time = 0.001 # We play the very first sound asap.
    for i in intervals:
        timer play_time action Play("sound", sfx)
        $ play_time += i

label start:
    x "M, Meow, Meoooooow, Meooooooooooooooooooooow, ..., fsmfeofmfo, didsnfsdnfdisf, nsdfnds nfsdicd."
    x "M, Meow, Meo00000w, Meo00000000000000000000w"

we're making a fair amount of assumptions here... such as that there are only single spaces (we're adding just 1 to intervals to account for them). That there are no tags of any kinds present and that the text will not be extended with the cool extend statement. Also that we're using a default say screen with the character...

==================>>>
We can add more and more conditions to account for all scenarios and it'll work and prolly work well, but I am not the one who's gonna do all of that :D but it looks like it can be done like this reliably so it is definitely worth pursuing if it's a desired feature of the game, it should be really easy to add different sounds depending on words length, making it sound even cooler.
Like what we're doing? Support us at:
Image

DragoonHP
Miko-Class Veteran
Posts: 758
Joined: Tue Jun 22, 2010 12:54 am
Completed: Christmas
IRC Nick: DragoonHP
Location: Zion Island, Solario
Contact:

Re: Text scrolling/ dialogue sounds, per word

#6 Post by DragoonHP »

This is a cookbook worthy entry. Though there's a bug, I think. The loop should end if the speed is zero, otherwise it will crash the game.

Code: Select all

                for w in words:
                    if not speed: # If speed is zero, exit the loop
                        break
                    intervals.append((len(w)+1)/speed) # We add 1 to account for spaces...
                renpy.show_screen("silly_timers", intervals, "beep-02.mp3")
Also a question xela, instead of writing a function to get the current dialogue, couldn't you have just used _last_say_what. I suppose you didn't because it's an internal variable which is subject to change?

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Text scrolling/ dialogue sounds, per word

#7 Post by xela »

DragoonHP wrote:This is a cookbook worthy entry.
It's not really safe. Even if all tags are removed from the string, extend and tags that change text speed will fail unless accounted for.

If OP adapts this and at least simpler special cases are fixed (removing all {...}, counting spaces and adjusting pauses and etc.), it might become usable, but it's still a decent blueprint for a code setup, everyone would have their own kinks they'd add like soundbites of different length and etc.
DragoonHP wrote:The loop should end if the speed is zero, otherwise it will crash the game.

Code: Select all

                for w in words:
                    if not speed: # If speed is zero, exit the loop
                        break
                    intervals.append((len(w)+1)/speed) # We add 1 to account for spaces...
                renpy.show_screen("silly_timers", intervals, "beep-02.mp3")
I was under a false impression that the event will not trigger if cps is 0. Nice catch.
DragoonHP wrote:Also a question xela, instead of writing a function to get the current dialogue, couldn't you have just used _last_say_what. I suppose you didn't because it's an internal variable which is subject to change?
I knew there was a variable set to it but didn't remember what it was and had no clue on where to find the full list of these variables :) The only once I remember are _return and main_menu.

Better (also filters out text tags {tag...} so we don't get bad intervals):

Code: Select all

init python:
    def beepy_voice(event, interact=True, **kwargs):
        if not interact:
            return
            
        if event == "show_done":
            s = _last_say_what
            speed = float(_preferences.text_cps)
            if s and speed:
                import re
                s = re.sub(r"{.+?}", "", s)
                intervals = [] 
                words = s.split()
                for w in words:
                    intervals.append((len(w)+1)/speed) # We add 1 to account for spaces...
                renpy.show_screen("silly_timers", intervals, "beep-02.mp3")
        elif event == "slow_done":
            renpy.hide_screen("silly_timers")
            
default x = Character("X", callback=beepy_voice)

screen silly_timers(intervals, sfx):
    default play_time = 0.001 # We play the very first sound asap.
    for i in intervals:
        timer play_time action Play("sound", sfx)
        $ play_time += i

label start:
    x "M, Meow, Meoooooow, Meooooooooooooooooooooow, ..., {color=#F00}fsmfeofmfo{/color}, didsnfsdnfdisf, nsdfnds nfsdicd."
    x "M, Meow, Meo00000w, Meo00000000000000000000w"

===============>>
Just realized, paired with UDD, this can be used to make a near perfect "talking" animation :D
Like what we're doing? Support us at:
Image

DragoonHP
Miko-Class Veteran
Posts: 758
Joined: Tue Jun 22, 2010 12:54 am
Completed: Christmas
IRC Nick: DragoonHP
Location: Zion Island, Solario
Contact:

Re: Text scrolling/ dialogue sounds, per word

#8 Post by DragoonHP »

It's not really safe. Even if all tags are removed from the string, extend and tags that change text speed will fail unless accounted for.
You can easily account for extend by using _last_raw_what instead of _last_say_what.

And supporting text tags might not be that hard since most of them are rather simple in their design.

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Text scrolling/ dialogue sounds, per word

#9 Post by xela »

DragoonHP wrote:
It's not really safe. Even if all tags are removed from the string, extend and tags that change text speed will fail unless accounted for.
You can easily account for extend by using _last_raw_what instead of _last_say_what.
Awesome!

Code: Select all

init python:
    def beepy_voice(event, interact=True, **kwargs):
        if not interact:
            return
            
        if event == "show_done":
            s = _last_raw_what
            speed = float(_preferences.text_cps)
            if s and speed:
                import re
                s = re.sub(r"{.+?}", "", s)
                intervals = [] 
                words = s.split()
                for w in words:
                    intervals.append((len(w)+1)/speed) # We add 1 to account for spaces...
                renpy.show_screen("silly_timers", intervals, "beep-02.mp3")
        elif event == "slow_done":
            renpy.hide_screen("silly_timers")
            
default x = Character("X", callback=beepy_voice)

screen silly_timers(intervals, sfx):
    default play_time = 0.001 # We play the very first sound asap.
    for i in intervals:
        timer play_time action Play("sound", sfx)
        $ play_time += i

label start:
    x "M, Meow, Meoooooow, Meooooooooooooooooooooow, ..., {color=#F00}fsmfeofmfo{/color}, didsnfsdnfdisf, nsdfnds nfsdicd."
    extend "M, Meow, Meo00000w, Meo00000000000000000000w"
DragoonHP wrote:And supporting text tags might not be that hard since most of them are rather simple in their design.
And that is where I draw the line because I don't really need this functionality and it becomes cat and mouse game with analyzing the string by making sure that there are no spaces in weird places (or multiple spaces) that may throw off the timings, accounting for \n scenarios and for tags that control text speed and other special cases like {} that might not be tags...

===>>
The above solution should account all other tags (like fonts, styles, colors and etc.), it would be really easy to add different sounds or series of sounds based on length of the word to make it sound better and now it even allows the use of the extend statement :)

So it is usable, getting it absolutely perfect is a tedious task which may even be meaningless to the OP as it was never mentioned.
Like what we're doing? Support us at:
Image

DragoonHP
Miko-Class Veteran
Posts: 758
Joined: Tue Jun 22, 2010 12:54 am
Completed: Christmas
IRC Nick: DragoonHP
Location: Zion Island, Solario
Contact:

Re: Text scrolling/ dialogue sounds, per word

#10 Post by DragoonHP »

Code: Select all

init python:
    def check_for_tags(s):
        
        if "{fast}" in s:
            s = s.split("{fast}")
            s = s[-1]
        
        if "{w" in s:
            s = s.replace("{w", " |##")
        
        if "[p" in s:
            s = s.replace("{p", " |##")
        
        s = s.replace("}", "} ")
        
        return s
    
    def beepy_voice(event, interact=True, **kwargs):
        global _last_beeped_text
        
        if not interact:
            return
           
        if event == "show_done":
            # Using _last_raw_what makes the callback compatible with extend character.
            # Fair warning: _last_raw_what is undocumented and subject to change.
            s = _last_raw_what
            speed = float(_preferences.text_cps)
            
            if s and speed:
                import re
                
                intervals = []
                padding = 0
                
                if not _last_beeped_text:
                    if "{fast}" in s or "{w" in s or "{p" in s:
                        s = check_for_tags(s)
                        
                    s = re.sub(r"{.+?}", "", s)
                    
                    # Whenever we encounter a click-to-continue tag, split the string.
                    _last_beeped_text = s.split("|##}")
                
                words = _last_beeped_text.pop(0).split()

                for w in words:
                    # If number of seconds is provided, add it to interval.
                    if "|##=" in w:
                        try:
                            padding += int(w[4:-1])
                        except ValueError:
                            padding += float(w[4:-1])
                        continue
                        
                    if w == "," or w == ".":
                        intervals[-1] += 1
                        continue
                        
                    intervals.append((len(w)+1+padding)/speed) # We add 1 to account for spaces...
                    
                renpy.show_screen("silly_timers", intervals, "Button_1.ogg")
                
        elif event == "slow_done":
            renpy.hide_screen("silly_timers")

define _last_beeped_text = []
default x = Character("X", callback=beepy_voice)

screen silly_timers(intervals, sfx):
    default play_time = 0.001 # We play the very first sound asap.
    
    for i in intervals:
        timer play_time action Play("sound", sfx)
        $ play_time += i

label start:
    x "M, Meow, Meoooooow, Meooooooooooooooooooooow, {fast}..., {color=#F00}fs{w}mfeofmfo{/color}, didsnfsdnfdisf, nsdfnds nfsdicd."
    x "M, Meow, Meo00000w, Meo00000000000000000000w"
This should work for all text tags which might break the callback. Not the best solution but definitly the easiest solution.
And that is where I draw the line because I don't really need this functionality and it becomes cat and mouse game with analyzing the string by making sure that there are no spaces in weird places (or multiple spaces) that may throw off the timings, accounting for \n scenarios and for tags that control text speed and other special cases like {} that might not be tags...
Fair enough I suppose.
And in any case, check for cases like that can easily be added by whoever needs to use it.

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Text scrolling/ dialogue sounds, per word

#11 Post by xela »

The above setup doesn't work (for me), timings become messed up, especially if more words are added... + it doesn't account for a bunch of other stuff that I mentioned (or/and didn't even think of).

It's definitely doable, all of it, it just requires someone with decent knowledge of Python and motivated enough to drill the string for all possible cases :)
Like what we're doing? Support us at:
Image

DragoonHP
Miko-Class Veteran
Posts: 758
Joined: Tue Jun 22, 2010 12:54 am
Completed: Christmas
IRC Nick: DragoonHP
Location: Zion Island, Solario
Contact:

Re: Text scrolling/ dialogue sounds, per word

#12 Post by DragoonHP »

The above setup doesn't work (for me), timings become messed up, especially if more words are added... + it doesn't account for a bunch of other stuff that I mentioned (or/and didn't even think of).
Can you give me any examples? I admit to only testing it rather briefly before and I would like to fix any potential problems.
And I only wanted to support text tags which breaks the normal flow (and I just found out there is a cps tag too v_v Might add it sometime later.)

EDIT: Found a bug. When a text tag is enclosed by spaces on both sides, the timing will mess up. The easiest solution right now is to not do that. :lol:
Last edited by DragoonHP on Thu Apr 28, 2016 3:23 pm, edited 1 time in total.

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Text scrolling/ dialogue sounds, per word

#13 Post by xela »

I've just copy pasted the code (adjusting only name of the sound file) and sound became off soon after the {w} tag. I've changed the string to this (just adding extra words):

Code: Select all

label start:
    x "M, Meow, Meoooooow, Meooooooooooooooooooooow, {fast}..., {color=#F00}fs{w}mfeofmfo{/color}, didsnfsdnfdisf, nsdfnds nfsdicd jfnsjfsfjnnfs dsfjnsfjnmdsjifsn fsfinij fs rere fdfdfdfd fd"
And found it out of sync completely. Set cps really low as you test and find a short sound file, bad sync becomes really clear then.
Like what we're doing? Support us at:
Image

DragoonHP
Miko-Class Veteran
Posts: 758
Joined: Tue Jun 22, 2010 12:54 am
Completed: Christmas
IRC Nick: DragoonHP
Location: Zion Island, Solario
Contact:

Re: Text scrolling/ dialogue sounds, per word

#14 Post by DragoonHP »

It's in complete sync for me. I am testing it out at the lowest text speed possible.

But I found a different bug: When a text tag is enclosed by spaces on both sides, the timing will mess up. The easiest solution right now is to not do that. :lol:

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Text scrolling/ dialogue sounds, per word

#15 Post by xela »

DragoonHP wrote:It's in complete sync for me. I am testing it out at the lowest text speed possible.

But I found a different bug: When a text tag is enclosed by spaces on both sides, the timing will mess up. The easiest solution right now is to not do that. :lol:
I cannot explain it. Sync is completely off in an empty project using that exact code and that exact string, it works fine in my simplified "tagless" version.
Like what we're doing? Support us at:
Image

Post Reply

Who is online

Users browsing this forum: Bing [Bot]