Stress amplitude cue 1.26 to 1.30 (secondary 1.14): rendered primary stress +1.7 to +2.0 dB mean over five vowels, /i/ in /nibu/ +0.99 to +1.64 dB
The f0 rise alone moves the level -1.3 to +1.8 dB by vowel through the harmonic/F1 interaction, so the amplitude factor is held to the rendered result. Duration and f0 cues unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4eab1979bd
commit
b7c1dc0a41
3 changed files with 38 additions and 8 deletions
|
|
@ -84,14 +84,16 @@ public sealed record WeksaWordPlan(IReadOnlyList<(IpaGestureExperimentTarget Tar
|
|||
|
||||
/// <summary>
|
||||
/// Primary stress: duration 220/160 = 1.375 (stressed against unstressed syllable in initial-stress pairs, Sluijter and van
|
||||
/// Heuven 1996), amplitude 2 dB = 1.26 (the 1-3 dB overall-intensity difference they report), f0 +6 percent (their unaccented
|
||||
/// f0 difference). Secondary stress is placed halfway, in dB and in percent: a DESIGN VALUE, no secondary-stress measurement was
|
||||
/// retrieved. Unheard.
|
||||
/// Heuven 1996), amplitude 1.30 = 2.3 dB on the source flow, f0 +6 percent (their unaccented f0 difference). The amplitude is set
|
||||
/// by the rendered result, not the nominal 2 dB (their 1-3 dB overall-intensity difference): the f0 rise alone moves the harmonics against
|
||||
/// F1 and changes the rendered level by -1.3 to +1.8 dB by vowel (i -0.7, a -0.3, u -1.3, e -0.8, o +1.8), so 1.26 rendered +1.7 dB on
|
||||
/// average over those five and 1.30 renders +2.0. Secondary stress is placed halfway, in dB and in percent: a DESIGN VALUE, no
|
||||
/// secondary-stress measurement was retrieved. Unheard.
|
||||
/// </summary>
|
||||
public static StressCue CueFor(WeksaStress stress) => stress switch
|
||||
{
|
||||
WeksaStress.Primary => new(220f / 160f, 1.26f, 1.06f),
|
||||
WeksaStress.Secondary => new(1.19f, 1.12f, 1.03f),
|
||||
WeksaStress.Primary => new(220f / 160f, 1.30f, 1.06f),
|
||||
WeksaStress.Secondary => new(1.19f, 1.14f, 1.03f),
|
||||
_ => new(1f, 1f, 1f)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -147,4 +147,32 @@ public sealed class StressAndReleaseRenderTests(ITestOutputHelper output)
|
|||
output.WriteLine($"/{ipa}/ murmur {db:F1} dB re the flanking vowels");
|
||||
Assert.InRange(db, -16, -8);
|
||||
}
|
||||
|
||||
// The primary-stress cue as the plan applies it (amplitude and f0 ratio, same vowel length) against the unstressed vowel, over five vowels:
|
||||
// the f0 rise alone moves the harmonics against F1 and the level by -1.3 to +1.8 dB by vowel, so the amplitude factor is held to the mean
|
||||
// rendered result, 2 dB (Sluijter and van Heuven 1996: 1 to 3 dB overall). Measured 1.7 dB with the amplitude at 1.26 (outside the band), 2.0 dB at 1.30.
|
||||
[Fact]
|
||||
public void PrimaryStressRendersAboutTwoDecibelsLouderOnAverageOverVowels()
|
||||
{
|
||||
var cue = WeksaWordPlan.CueFor(WeksaStress.Primary);
|
||||
var diffs = new List<double>();
|
||||
foreach (var vowel in new[] { "i", "a", "u", "e", "o" })
|
||||
{
|
||||
double Level(float ratio, float intensity)
|
||||
{
|
||||
var script = IpaGestureExperiment.BuildWordScript([(new IpaGestureExperimentTarget($"w_0_{vowel}", vowel, "", 0.25f, PitchRatio: ratio), intensity)], 140, 0.2f);
|
||||
var x = Render(script, 0.45f);
|
||||
return x is null ? double.NaN : Rms(x, 0.12, 0.22);
|
||||
}
|
||||
|
||||
var flat = Level(1f, 1f);
|
||||
if (double.IsNaN(flat)) return;
|
||||
var db = 20 * Math.Log10(Level(cue.PitchRatio, cue.Amplitude) / flat);
|
||||
output.WriteLine($"/{vowel}/ stressed against unstressed: {db:F2} dB");
|
||||
diffs.Add(db);
|
||||
}
|
||||
|
||||
output.WriteLine($"mean {diffs.Average():F2} dB");
|
||||
Assert.InRange(diffs.Average(), 1.8, 2.3);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,11 +105,11 @@ public sealed class WeksaPhonesTests
|
|||
Assert.Equal(1f, none[1].Intensity);
|
||||
Assert.Equal(WeksaWordPlan.VowelSeconds * 220f / 160f, primary[1].Target.DurationSeconds, 5);
|
||||
Assert.Equal(1.06f, primary[1].Target.PitchRatio);
|
||||
Assert.Equal(1.26f, primary[1].Intensity);
|
||||
Assert.Equal(1.30f, primary[1].Intensity);
|
||||
// Secondary sits strictly between unstressed and primary on every cue.
|
||||
Assert.InRange(secondary[1].Target.DurationSeconds, none[1].Target.DurationSeconds + 1e-4f, primary[1].Target.DurationSeconds - 1e-4f);
|
||||
Assert.InRange(secondary[1].Target.PitchRatio, 1.001f, 1.059f);
|
||||
Assert.InRange(secondary[1].Intensity, 1.001f, 1.259f);
|
||||
Assert.InRange(secondary[1].Intensity, 1.001f, 1.299f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -124,7 +124,7 @@ public sealed class WeksaPhonesTests
|
|||
// pitch_ratio surface range 0.5..2: normalized (1.06 - .5) / 1.5; unstressed holds 1 -> (1 - .5) / 1.5.
|
||||
Assert.Equal((1.06f - .5f) / 1.5f, First(primary, "w_1_i_source_pitch", "/vocal/sources/0/pitch_ratio"), 4);
|
||||
Assert.Equal((1f - .5f) / 1.5f, First(none, "w_1_i_source_pitch", "/vocal/sources/0/pitch_ratio"), 4);
|
||||
Assert.Equal(First(none, "w_1_i_source_level", "/vocal/sources/0/flow_scale") * 1.26f, First(primary, "w_1_i_source_level", "/vocal/sources/0/flow_scale"), 4);
|
||||
Assert.Equal(First(none, "w_1_i_source_level", "/vocal/sources/0/flow_scale") * 1.30f, First(primary, "w_1_i_source_level", "/vocal/sources/0/flow_scale"), 4);
|
||||
// The b after the stressed vowel starts later by the vowel's extra length.
|
||||
float Start(SynthPatch p) => p.PhonemeGestures.Single(g => g.Ipa == "b").StartSeconds;
|
||||
Assert.Equal(WeksaWordPlan.VowelSeconds * (220f / 160f - 1f), Start(primary) - Start(none), 4);
|
||||
|
|
|
|||
Loading…
Reference in a new issue