Script for changing ALT pronunciations

Can we get a script or feature to quickly change multiple phonemes/words from DFLT to ALT1 etc? Maybe even a randomizer?

And while we’re at it…maybe a script that can move the phoneme/duration sliders randomly (within constraints)?

「いいね!」 2

This script will create a duplicate of the currently active track, with the alt phonemes shifted to the next value. It will also randomize the tF0Offset, tF0Left, tF0Right, and tNoteOffset based on the slider value.

It’s a bit of a kludge in places, because there’s currently no way to get some values using Lua, such as the number of phonemes. So it just assumes there are a maximum of 10 phonemes per note.

No warranties or guaranteed, be sure to back up your files, etc. It’s not coded to handle groups, so I don’t know what it will do if it finds one.

Edit: Updated version of code
Vocal Doubler.zip (2.2 KB)

「いいね!」 4

dcuny comes through again! Thanks so much!

I might try to figure it out myself when I get time, but if you get to it first…

Seems the slider doesn’t quite work as expected. I only had a minute to test, but seems like setting the slider to lower numbers results in “note offset” being very heavily biased to the left.

Yes, you’re absolutely right.   :woozy_face:

There were a lot of problems with the jitter code that needed correction, and I’ve posted a new version above. Feel free to compare it to the old version as to find all the stupid coding errors, like completely ignoring what the parameter values actually are supposed to do.   :roll_eyes:

I’ve also added some sanity tests to make sure the values aren’t jittered out of range.

Hopefully this version works better for you!

Seems better already! Biggest problem, from my quick test - the Pitch Transition Duration Left/Right vary too wildly based on the “Amount of Change” slider. I would think that “amount of Change” set to 0, would not change the pitch transition - but it actually sets them both TO 0.

I’ll try messing around with the code at some point. If nothing else, I might just isolate the “change phonemes” part of it, and maybe tame the phoneme duration change a bit.

Yep, sounds like there’s still a bug in there.

For now, you can just comment this whole section out:

-- variation scale
local scale = variation / 100
local tF0Offset = jitter( attribs[tF0Offset], .1, true, scale, 0, .5 )
local tF0Left = jitter( attribs[tF0Left], .5, false, scale, 0, .5 )
local tF0Right = jitter( attribs[tF0Right], .5, false, scale, 0, .5 )
local tNoteOffset = jitter( attribs[tNoteOffset], .1, true, scale, -.1, .1 )

Or you can modify the maximum change to a parameter by modifying the second parameter passed to jitter. The third parameter indicates if the change value can also be negative.

For example, this line:

local tF0Offset = jitter( attribs[tF0Offset], .1, true, scale, 0, .5 )

jitters the value of the pitch offset by ±.1, and clips it to keep it in the range between (0...5).

The code that modifies the durations is this:

-- replace duration with random value from (.2 .. 1.8)
durs[k] = math.random(20, 180)/100

Changing the high/low values in math.random(20, 180)/100 will give you something closer to what you’re looking for.

Dude, thanks so much for the coding lesson! Definitely gonna try some of these tricks on a copy of the script!

I’m starting to really understand.
-- replace duration with random value from (.2 .. 1.8)
durs[k] = math.random(20, 180)/100

Does lua allow to simply edit the number, instead of a hard replacement? i.e. say I manually changed a phoneme to 50% length. I would want the doubled track to be some percentage of that.

Sure.

But there are some subtleties of the code you might need to be aware of first.

The routine randomizeNote is passed in two parameters - theNote is the note that’s being altered, and variation is the value from the slider, which ranges from (0..100).

Notice that variation is divided by 100 and stored in the variable scale, so it has a value from (0 .. 1). This is the value that comes from the slider.

local scale = variation / 100

The code then grabs the attributes from the the note using the getAttributes method:

local attribs = theNote:getAttributes()

Here’s where I run into a bit of trouble. SynthV documentation says that you can get a table of the phonemes in the note. But that code doesn’t work, the last time I checked it.

So I can’t ask SynthV for the note’s phonemes. So how do I know how many phonemes to process in the note?

I can get the attributes of the phonemes, which I do with this code:

-- alternate phonemes and durations of phonemes
local alts = attribs[alt]
local durs  = attribs[dur]

But I don’t know how many values are in the table.

You might be thinking: Well, dcuny, just count them.

But the number of items in the table isn’t necessarily the same as the number of phonemes. In fact, most of the time it isn’t.

To explain, consider the case where you had 3 phonemes, each with an alt value of 0, the default. You would expect the alts table to look like this:

alts = { 0, 0, 0 }

and… you’d almost be right. That’s because Lua uses association tables, storing things in key/value pairs. So the table would look like this:

alts = { [1]=0, [2]=0, [3]=0 }

But wait! SynthV has an interesting behavior. If a parameter is at a default setting, it’s returned as nil.

This is irritating, for a lot of reasons. One is that there’s no easy way to ask SynthV what the default value of a parameter is, because that feature doesn’t exist. You’ve got to look it up.

So you might think the table is actually:

alts = { [1]=nil, [2]=nil, [3]=nil }

And that’s almost right… but not quite.

When you have a key/value pair where the value is nil, Lua doesn’t store the value in the table. Lua never stores a value of nil in a table, because to Lua, that’s an empty key.

So the table actually looks like this:

alts = {}

That’s right - it’s an empty table.

Even though there are three phonemes, because SynthV uses nil to indicate they are set to their default values, And Lua doesn’t store the nil values in the table, so the function returns an empty table.

Whew Got all that? :grimacing: (Yes, I’ve posted this as a bug).

Sort of makes it hard to count the phonemes when they aren’t there.

So the bottom line is, without SynthV returning the phonemes properly, there’s no way to know the number of phonemes. So the script punts, and just assumes there are a maximum of 10 phonemes:

for k = 1, 10 do
	-- get current phoneme choice
	-- if nil, it's default of 0
	local v = alts[k] or 0
	local v = v + 1
	if v > 2 then
		v = 0
	end
	alts[k] = v

	-- replace duration with random value from (.2 .. 1.8)
	durs[k] = math.random(20, 180)/100
end

The line:

local v = alts[k] or 0

is a Lua-ism. The or returns the value on the left if it’s non-nil (false and nil are the same in Lua), and the value on the right if it is nil. So if the value was nil, it assumes that it was the default value, and returns a 0, which is the default value for an alt.

It then increments the value, and if it’s larger than 2 (alt can only range from 0..2) it wraps it around to a 0.

It then stores the value back in the table, and the SynthV writes the table back when it exits the loop:

-- save values
theNote:setAttributes{
	dur = durs,
	alt = alts,
	tF0Offset = tF0Offset,
	tF0Left = tF0Left,
	tF0Right = tF0Right,
	tNoteOffset = tNoteOffset,
}

Anyway, you can see that my code doesn’t even look at what the dur of the note is, it just picks a random value from (0.2..1.8):

-- replace duration with random value from (.2 .. 1.8)
durs[k] = math.random(20, 180)/100

So, for example, you could use the jitter routine to add some random amount instead, like:

durs[k] = jitter( durs[k] or 1, .1, true, 1, .2, 1.8 )

Edit: Corrected the code above, which was untested and (of course) didn’t handle the default value, so crashed when it got a nil for the default value… :face_with_raised_eyebrow:

In the example above, it would add a random value between (-0.05..0.05) to the durs value, keeping it in the range from (-0.1 .. 0.1). The scale value would obviously influence the value.

Did that make sense?

「いいね!」 1

Awesome explanation! It makes a lot of sense for the ALTs, the way you explained it. (Glad you reported it as a bug, too).

So, does the same “Table” glitch apply to grabbing durations? If a duration is at 100% (default), does that make for the nil/“blank table” problem? And that’s why it must be set, instead of edited?

(the suggested durs[k] = jitter doesn’t work - it gives an error about editing a nil value)

You can still get the dur value, you just need to also handle the default value.

Which I didn’t do… even after that long explanation. The moral? Never post untested code. :roll_eyes:

Since the default value is 1, this should fix it with the or handling the value if it’s nil:

durs[k] = jitter( durs[k] or 1, .1, true, 1, .2, 1.8 )

This time I actually tested it before posting.

The safer way to handle it would be to add a parameter for the default value in to the jitter routine, so it would always check the value before trying to modify it.