55using System . Numerics ;
66using K4os . Hash . xxHash ;
77using OpenUtau . Core . Ustx ;
8+ using Serilog ;
89
910namespace OpenUtau . Core . Render {
1011 public class RenderNote {
@@ -185,8 +186,7 @@ public class RenderPhrase {
185186 public readonly string wavtool ;
186187
187188 internal RenderPhrase ( UProject project , UTrack track , UVoicePart part , IEnumerable < UPhoneme > phonemes ) {
188- var uNotes = new List < UNote > ( ) ;
189- uNotes . Add ( phonemes . First ( ) . Parent ) ;
189+ var uNotes = new List < UNote > { phonemes . First ( ) . Parent } ;
190190 var endNote = phonemes . Last ( ) . Parent ;
191191 while ( endNote . Next != null && endNote . Next . Extends != null ) {
192192 endNote = endNote . Next ;
@@ -228,6 +228,7 @@ internal RenderPhrase(UProject project, UTrack track, UVoicePart part, IEnumerab
228228 int pitchStart = position - part . position - leading ;
229229 pitches = new float [ ( end - part . position - pitchStart ) / pitchInterval + 1 ] ;
230230 int index = 0 ;
231+ // Create flat pitches
231232 foreach ( var note in uNotes ) {
232233 while ( pitchStart + index * pitchInterval < note . End && index < pitches . Length ) {
233234 pitches [ index ] = note . tone * 100 ;
@@ -239,6 +240,7 @@ internal RenderPhrase(UProject project, UTrack track, UVoicePart part, IEnumerab
239240 pitches [ index ] = pitches [ index - 1 ] ;
240241 index ++ ;
241242 }
243+ // Vibrato
242244 foreach ( var note in uNotes ) {
243245 if ( note . vibrato . length <= 0 ) {
244246 continue ;
@@ -253,6 +255,7 @@ internal RenderPhrase(UProject project, UTrack track, UVoicePart part, IEnumerab
253255 pitches [ i ] = point . Y * 100 ;
254256 }
255257 }
258+ // Pitch points
256259 foreach ( var note in uNotes ) {
257260 var pitchPoints = note . pitch . data
258261 . Select ( point => {
@@ -291,7 +294,78 @@ internal RenderPhrase(UProject project, UTrack track, UVoicePart part, IEnumerab
291294 lastPoint = point ;
292295 }
293296 }
297+ // Mod plus
298+ if ( track . TryGetExpDescriptor ( project , Format . Ustx . MODP , out var modp ) && renderer . SupportsExpression ( modp ) ) {
299+ foreach ( var phoneme in phonemes ) {
300+ var mod = phoneme . GetExpression ( project , track , Format . Ustx . MODP ) . Item1 ;
301+ if ( mod == 0 ) {
302+ continue ;
303+ }
304+
305+ try {
306+ if ( phoneme . TryGetFrq ( out var frqFix , out var frqStretch , out double average , out int hopSize ) ) {
307+ UTempo [ ] noteTempos = project . timeAxis . TemposBetweenTicks ( part . position + phoneme . position , part . position + phoneme . End ) ;
308+ var tempo = noteTempos [ 0 ] . bpm ; // compromise 妥協!
309+ var frqIntervalTick = MusicMath . TempoMsToTick ( tempo , ( double ) 1 * 1000 / 44100 * hopSize ) ;
310+ double consonantStretch = Math . Pow ( 2f , 1.0f - phoneme . GetExpression ( project , track , Format . Ustx . VEL ) . Item1 / 100f ) ;
311+
312+ var preutter = MusicMath . TempoMsToTick ( tempo , Math . Min ( phoneme . preutter , phoneme . oto . Preutter * consonantStretch ) ) ;
313+ int startIndex = Math . Max ( 0 , ( int ) Math . Floor ( ( phoneme . position - pitchStart - preutter ) / pitchInterval ) ) ;
314+ int position = ( int ) Math . Round ( ( double ) ( ( phoneme . position - pitchStart ) / pitchInterval ) ) ;
315+ int startStretch = position + ( int ) Math . Round ( MusicMath . TempoMsToTick ( tempo , ( phoneme . oto . Consonant - phoneme . oto . Preutter ) * consonantStretch ) / pitchInterval ) ;
316+ int endIndex = Math . Min ( pitches . Length , ( int ) Math . Ceiling ( phoneme . End - pitchStart - MusicMath . TempoMsToTick ( tempo , phoneme . tailIntrude - phoneme . tailOverlap ) ) / pitchInterval ) ;
317+
318+ frqFix = frqFix . Select ( f => f - average ) . ToArray ( ) ;
319+ frqStretch = frqStretch . Select ( f => f - average ) . ToArray ( ) ;
320+ double stretch = 1 ;
321+ if ( frqStretch . Length * frqIntervalTick < ( ( double ) endIndex - startStretch ) * pitchInterval ) {
322+ stretch = ( ( double ) endIndex - startStretch ) * pitchInterval / ( frqStretch . Length * frqIntervalTick ) ;
323+ }
324+ var env0 = new Vector2 ( 0 , 0 ) ;
325+ var env1 = new Vector2 ( ( phoneme . envelope . data [ 1 ] . X - phoneme . envelope . data [ 0 ] . X ) / ( phoneme . envelope . data [ 4 ] . X - phoneme . envelope . data [ 0 ] . X ) , 100 ) ;
326+ var env3 = new Vector2 ( ( phoneme . envelope . data [ 3 ] . X - phoneme . envelope . data [ 0 ] . X ) / ( phoneme . envelope . data [ 4 ] . X - phoneme . envelope . data [ 0 ] . X ) , 100 ) ;
327+ var env4 = new Vector2 ( 1 , 0 ) ;
328+
329+ for ( int i = 0 ; startStretch + i <= endIndex ; i ++ ) {
330+ var pit = startStretch + i ;
331+ if ( pit >= pitches . Length ) break ;
332+ var frq = i * ( pitchInterval / frqIntervalTick ) / stretch ;
333+ var frqMin = Math . Clamp ( ( int ) Math . Floor ( frq ) , 0 , frqStretch . Length - 1 ) ;
334+ var frqMax = Math . Clamp ( ( int ) Math . Ceiling ( frq ) , 0 , frqStretch . Length - 1 ) ;
335+ var diff = MusicMath . Linear ( frqMin , frqMax , frqStretch [ frqMin ] , frqStretch [ frqMax ] , frq ) ;
336+ diff = diff * mod / 100 ;
337+ diff = Fade ( diff , pit ) ;
338+ pitches [ pit ] = pitches [ pit ] + ( float ) ( diff * 100 ) ;
339+ }
340+ for ( int i = 0 ; startStretch + i - 1 >= startIndex ; i -- ) {
341+ var pit = startStretch + i - 1 ;
342+ if ( pit > endIndex || pit >= pitches . Length ) continue ;
343+ var frq = frqFix . Length + ( i * ( pitchInterval / frqIntervalTick ) / consonantStretch ) ;
344+ var frqMin = Math . Clamp ( ( int ) Math . Floor ( frq ) , 0 , frqFix . Length - 1 ) ;
345+ var frqMax = Math . Clamp ( ( int ) Math . Ceiling ( frq ) , 0 , frqFix . Length - 1 ) ;
346+ var diff = MusicMath . Linear ( frqMin , frqMax , frqFix [ frqMin ] , frqFix [ frqMax ] , frq ) ;
347+ diff = diff * mod / 100 ;
348+ diff = Fade ( diff , pit ) ;
349+ pitches [ pit ] = pitches [ pit ] + ( float ) ( diff * 100 ) ;
350+ }
351+ double Fade ( double diff , int pit ) {
352+ var percentage = ( double ) ( pit - startIndex ) / ( endIndex - startIndex ) ;
353+ if ( phoneme . Next != null && phoneme . End == phoneme . Next . position && percentage > env3 . X ) {
354+ diff = diff * Math . Clamp ( MusicMath . Linear ( env3 . X , env4 . X , env3 . Y , env4 . Y , percentage ) , 0 , 100 ) / 100 ;
355+ }
356+ if ( phoneme . Prev != null && phoneme . Prev . End == phoneme . position && percentage < env1 . X ) {
357+ diff = diff * Math . Clamp ( MusicMath . Linear ( env0 . X , env1 . X , env0 . Y , env1 . Y , percentage ) , 0 , 100 ) / 100 ;
358+ }
359+ return diff ;
360+ }
361+ }
362+ } catch ( Exception e ) {
363+ Log . Error ( e , "Failed to compute mod plus." ) ;
364+ }
365+ }
366+ }
294367
368+ // PITD
295369 pitchesBeforeDeviation = pitches . ToArray ( ) ;
296370 var pitchCurve = part . curves . FirstOrDefault ( c => c . abbr == Format . Ustx . PITD ) ;
297371 if ( pitchCurve != null && ! pitchCurve . IsEmpty ) {
0 commit comments