Official Thimbleweed Park Forums

Adding voice acting

Unfortunately, the _startSayline(actor, text) function doesn’t take the text id, so it’s going to be hard since the audio file need to be saved based on the text id. Also, several lines are formed by string concatenation which also presents issues.

Let me look at the changes need for the engine to pass a text id to _startSayline().

@Frenzie, thanks a lot! I will check this out!

@RonGilbert, thank you so much! I hope it is not too much work. My wife and I are very excited to try to add voice acting to the game :slight_smile:

I changed this right before releasing the code, so I was misremembering how it finally worked.

The callback function _startSayline(actor, text) receives the text before translation, so it will be in the form of “id:text”. You can do what the TR() function does local id = strslice(text, 0, 4) to get the id and use that for form a FMOD event like event:/talkies/es/10567

local id = strslice(text, 0, 4)
local event = "event:/talkies/"+currentLanguage()+"/"+id
actor.talkie_sid <- playsound(event)

then in _doneSayline() do

if (actor?.talkie_sid) {
    stopsound(actor.talkie_sid)
    actor.talkie_sid <- 0
}

That should get to basic talking. There might be some edge cases that will need to be worked out once it’s functioning.

For now, add the talkie events to the main FMOD .bank file, they are easy to copy to a new .bank file when/if that gets implemented.

Keep in mind that that above code is untested and might have small errors in it.

5 Likes

Hey @RonGilbert! I updated the code and created a .bank file for testing and it is working :slight_smile: thanks for that!

However, ‘playSound’ always returns 0, so when _doneSayline is called, the sound doesn’t stop. Do you know what is happening and how I could fix it? This is the added code:

function _startSayline(actor, text) {
local text_scale = 3.0
local image = createTextImage(SAYLINE_FONT, TR(text), ALIGN_CENTER|(SCREEN_WIDTH*(0.80/text_scale)))
imageAt(image, point(SCREEN_CENTER_X, SCREEN_HEIGHT*0.90))
imageScale(image, text_scale)
imageColor(image, actor.talkColor())
imageTextOutline(image, COLOR_BLACK)
actor._sayline_image <- image
if (SETTING(jibber)) {
	actor._jibber_sid <- playSound(actor?.jibber_sid)
}

local id = strslice(text, 0, 4)
local event = "event:/talkies/es/"+id
//local event = "event:/talkies/"+currentLanguage()+"/"+id
print("Playing event: " + event)

actor.talkie_sid <- playSound(event)
print("TalkieID: " + actor.talkie_sid)

return image
}

Besides that, I guess ‘_doneSayline’ is called from the engine, right? Can I override the text duration with the audio length for each sentence?

Thanks!

Yeah, you’re right. It always returns 0. I don’t use that form so never saw this issue. I’ll fix it and upload a new engine today or tomorrow.

I’ll also add a way to override the time so you can track the audio and end it when needed. To do that, you’ll need to start a thread that does a breakwhilesound(sid), then does a sayLine(actor, null) to stop them talking.

I uploaded a new DeloresDev engine (version 1.0.4) to the website (https://thimbleweedpark.com/deloresdev/)

FIX: playSound("event...") now correctly returns a playing sound id.
ADD: gameprops("sayLineForever", true) will allow sayLines to remain on screen until stopped.

If you’re using talkies, you’ll want to call the gameprops() once and stop the lines when then audio is done. There might be some edge cases that will need to be address, but I can’t think of any right now.

P.S. When I say forever, I really mean 9999 seconds (2.7 hours). So, if you end up taking more the 2.7 hours to say a single line, it will get cut off.

5 Likes

Awesome! Thanks! The sound now stops correctly.

I have been trying implement the timing override, but I get the error “Can’t find var gameprops”. I tried it with camel case without luck.

Could you help me with that, please? As soon as I get this working, I think I will be able to implement the rest with the instructions you gave to me.

And a question regarding the “sayLineForever” flag: can I enable it if there is a sound for one sentence, and disable it if there is none? Like this (in “_startSayline” function):

	if( actor?.talkie_sid ) {
	    gameprops( "sayLineForever", true )

		startthread( @{
			breakwhilesound( actor.talkie_sid )
			sayLine( actor, null )
		} );
	
	} else {
		gameprops( "sayLineForever", false );
	
	}

P.S. When I say forever, I really mean 9999 seconds (2.7 hours). So, if you end up taking more the 2.7 hours to say a single line, it will get cut off.

That sounds like an interesting idea… :sunglasses: (just kidding :joy:)

My mistake, it’s called gameprop(). It’s also used in Options.dinky.

You need to call gameprop( "sayLineForever", true) before the sayLine happens, which means it’s too late by the time you get the callback. This should be set once (probably in boot.dinky) if you have talkies.

This is all kind of a kludge, if I was doing this “for real”, talking would be built into the system (because of lipsync, etc).

3 Likes

Maybe it’s a good opportunity to build it into the engine, let us do some beta testing and get it ready for the next “real” project…? :wink:

Actually, I thought this was one of the reasons for publishing Delores sources.

1 Like

Thanks, @RonGilbert! gameprop( … ) is working now.

This is all kind of a kludge, if I was doing this “for real”, talking would be built into the system (because of lipsync, etc).

No worries! I just need something quick & dirty to start going. I only hope I’m not being a PITA :slight_smile:

I think “breakwhilesound” is not working properly or I’m doing something wrong, because it doesn’t halt the thread. This is the code I have used (inside _startSayline):

if( actor?.talkie_sid ) {
	startthread( @{
		print( "Waiting talkie" )
		
		breakwhilesound( actor.talkie_sid )
		sayLine( actor, null )
		
		print( "Talkie done" )
	} );
	
} else {
	startthread( @{
		print( "Waiting seconds" )

		breaktime( 5.0 )
		sayLine( actor, null )
		
		print( "Done" )
	} );
	
}

Do you know what could be happening?

1 Like

You you post (or send me via DM) the entire _startSayline() function you are using and I’ll try it on my end.

Don’t worry at all about being a PITA, it’s fun for me.

4 Likes

We briefly thought about doing full voice for Delores. The actor that plays Delores is a good friend and we though it would be fun if she did Delores again and I did all the other voice.

Doing everything voice needs in the engine is a good month of work, so it’s a major undertaking. I’ll do it before my next game, but not anytime in the near future.

5 Likes

Um, that’s a shame. I thought you could rather easily copy the talking code from TWP and modify it to fit Delores within a day or two but a month is really a lot…

While from the outside they seem similar, the internal architecture is very different. One of the reasons I wanted to rewrite the TWP engine was it’s a complete mess. Talking was put in towards the end and it was a kludge.

7 Likes

There you have, @RonGilbert:

function _startSayline(actor, text) {
local text_scale = 3.0
local image = createTextImage(SAYLINE_FONT, TR(text), ALIGN_CENTER|(SCREEN_WIDTH*(0.80/text_scale)))
imageAt(image, point(SCREEN_CENTER_X, SCREEN_HEIGHT*0.90))
imageScale(image, text_scale)
imageColor(image, actor.talkColor())
imageTextOutline(image, COLOR_BLACK)
actor._sayline_image <- image
if (SETTING(jibber)) {
	actor._jibber_sid <- playSound(actor?.jibber_sid)
}

local id = strslice(text, 0, 4)
local event = "event:/talkies/es/"+id
//local event = "event:/talkies/"+currentLanguage()+"/"+id
print("Playing event: " + event)

actor.talkie_sid <- playSound(event)
print("TalkieID: " + actor.talkie_sid)

if( actor?.talkie_sid ) {
	startthread( @{
		print( "Waiting talkie" )
		
		breakwhilesound( actor.talkie_sid )
		sayLine( actor, null )
		
		print( "Talkie done" )
	} );
	
} else {
	startthread( @{
		print( "Waiting seconds" )

		breaktime( 5.0 )
		sayLine( actor, null )
		
		print( "Done" )
	} );
	
}

return image

}

Thanks a lot for spending time helping me!

Eh … we thought to record all Thimbleweed Park voices in Italian in “several months”. We were too much optimistic, almost 3 years have passed (but now we are near the end).
It’s not as quick as one can think.

3 Likes

I know things usually take much more time then I expected. Yesterday I added another new feature to a game (Bubble Bobble clone) that we started making in 1996. =)

On the other hand, I usually start programing a new project by opening an older project of mine and removing useless features and I still believe it saves time. (Even though I end up rewriting half of it and then copying rewritten parts of code from the project B back to the project A. =)

7 Likes

1996! DOS or Win’95?

We started with DOS, then we moved to DirectX 6 in 1999 and the game was published in 2000. However I keep maintaining it and reworking it. It still does not include all the features that we had planned for the initial release… =}

1 Like

Well, we did record in “months”. If you remove the time spent looking for volunteers, pinging them whenever they disappeared and so on, the recording itself didn’t take much :stuck_out_tongue:

I think now, knowing how it goes, we could do Delores in a short time… if it weren’t that Delores isn’t one of us! :sob: if only it were a Ransome game…

2 Likes
Official Thimbleweed Park Forums powered by Discourse.