diff --git a/OpenUtau.Core/Commands/NoteCommands.cs b/OpenUtau.Core/Commands/NoteCommands.cs index 85bde011f..c5f48e79e 100644 --- a/OpenUtau.Core/Commands/NoteCommands.cs +++ b/OpenUtau.Core/Commands/NoteCommands.cs @@ -454,6 +454,113 @@ public override void Unexecute() { } } + public class VibratoVariationDepthCommand : VibratoCommand { + readonly UNote note; + readonly float newVariation; + readonly float oldVariation; + + public VibratoVariationDepthCommand(UVoicePart part, UNote note, float variation) : base(part, note) { + this.note = note; + newVariation = variation; + oldVariation = note.vibrato.variation; + } + + public override string ToString() { + return "Change vibrato variation depth"; + } + + public override void Execute() { + lock (Part) { + note.vibrato.variation = newVariation; + } + } + public override void Unexecute() { + lock (Part) { + note.vibrato.variation = oldVariation; + } + } + } + + public class VibratoPitchVariationCommand : VibratoCommand { + readonly UNote note; + readonly float newPitchVariation; + readonly float oldPitchVariation; + + public VibratoPitchVariationCommand(UVoicePart part, UNote note, float pitchVariation) : base(part, note) { + this.note = note; + newPitchVariation = pitchVariation; + oldPitchVariation = note.vibrato.pitchVariation; + } + + public override string ToString() { + return "Change vibrato pitch variation depth"; + } + + public override void Execute() { + lock (Part) { + note.vibrato.pitchVariation = newPitchVariation; + } + } + public override void Unexecute() { + lock (Part) { + note.vibrato.pitchVariation = oldPitchVariation; + } + } + } + + public class VibratoVariationFrequencyCommand : VibratoCommand { + readonly UNote note; + readonly float newVariationFreq; + readonly float oldVariationFreq; + + public VibratoVariationFrequencyCommand(UVoicePart part, UNote note, float frequency) : base(part, note) { + this.note = note; + newVariationFreq = frequency; + oldVariationFreq = note.vibrato.variationFreq; + } + + public override string ToString() { + return "Change vibrato variation frequency"; + } + + public override void Execute() { + lock (Part) { + note.vibrato.variationFreq = newVariationFreq; + } + } + public override void Unexecute() { + lock (Part) { + note.vibrato.variationFreq = oldVariationFreq; + } + } + } + + public class VibratoVariationSeedCommand : VibratoCommand { + readonly UNote note; + readonly int newVariationSeed; + readonly int oldVariationSeed; + + public VibratoVariationSeedCommand(UVoicePart part, UNote note, int seed) : base(part, note) { + this.note = note; + newVariationSeed = seed; + oldVariationSeed = note.vibrato.variationSeed; + } + + public override string ToString() { + return "Change vibrato variation seed"; + } + + public override void Execute() { + lock (Part) { + note.vibrato.variationSeed = newVariationSeed; + } + } + public override void Unexecute() { + lock (Part) { + note.vibrato.variationSeed = oldVariationSeed; + } + } + } public class PhonemeOffsetCommand : NoteCommand { readonly UNote note; diff --git a/OpenUtau.Core/Editing/NoteBatchEdits.cs b/OpenUtau.Core/Editing/NoteBatchEdits.cs index 4c3114876..31e0334ff 100644 --- a/OpenUtau.Core/Editing/NoteBatchEdits.cs +++ b/OpenUtau.Core/Editing/NoteBatchEdits.cs @@ -4,6 +4,7 @@ using System.Threading; using OpenUtau.Core.Ustx; using OpenUtau.Core.Format; +using SimplexNoise; namespace OpenUtau.Core.Editing { public class AddTailNote : BatchEdit { @@ -446,6 +447,83 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do } } + public class RandomPitchDeviation : BatchEdit { + public virtual string Name => name; + private string name; + public float noiseScale; + public float noiseFrequency; + + public RandomPitchDeviation(float noiseScale, float noiseFrequency) { + name = "pianoroll.menu.notes.randompitd"; + this.noiseScale = noiseScale; + this.noiseFrequency = noiseFrequency; + } + + public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { + if (selectedNotes.Count == 0) { + return; + } + docManager.StartUndoGroup("command.batch.note", true); + + Random random = new Random(); + int seed = random.Next(); + int numOctaves = 4; + float baseScale = 0.25F; + float coarseFrequency = 0.001F; + + var pitdCurve = part.curves.Find(c => c.abbr.Equals(Format.Ustx.PITD)); + if (pitdCurve == null) { + // hmmm... + if (project.expressions.TryGetValue(Format.Ustx.PITD, out var descriptor)) { + pitdCurve = new UCurve(descriptor); + part.curves.Add(pitdCurve); + } + } + + foreach(var note in selectedNotes) { + var start = note.position; + var end = note.position + note.duration; + int numSamples = note.duration; + + float[] noiseSum = new float[numSamples]; + Array.Fill(noiseSum, 0); + + // when summing together the noise values in this manner + // 510 is the highest possible sum (for values generated by Calc1D, [0, 255]). + // multiplying by (1 - 0.5 ^ numOctaves) calculates + // the precise highest value based on the number of octaves + + // i wanted to have a dialog for changing octaves and seed but that would be too + // many single dialogs for a user to have to click through, IMO + float highestValue = 510 * (1 - (float)Math.Pow(0.5, numOctaves)); + for (int i = 0; i < numOctaves; i++) { + Noise.Seed = seed; + + float octaveInfluence = (float)Math.Pow(2, i); + float octaveFrequency = (coarseFrequency * noiseFrequency) * (4 * octaveInfluence); + float[] noise = Noise.Calc1D(numSamples, octaveFrequency); + for (int j = 0; j < noise.Length; j++) { + noiseSum[j] += noise[j] / octaveInfluence; + } + + seed = random.Next(); + } + + for (int i = 0; i < noiseSum.Length; i++) { + int curveValue = pitdCurve.Sample(start + i); + float noiseValue = (noiseSum[i] - (highestValue / 2)) * baseScale; + curveValue += (int)Math.Round(noiseValue * noiseScale); + docManager.ExecuteCmd(new SetCurveCommand(project, part, Format.Ustx.PITD, + start + i, curveValue, + start + i, 0 + )); + } + } + + docManager.EndUndoGroup(); + } + } + public class LoadRenderedPitch : BatchEdit { public virtual string Name => name; diff --git a/OpenUtau.Core/Format/USTx.cs b/OpenUtau.Core/Format/USTx.cs index 4922d0918..c657c4dd7 100644 --- a/OpenUtau.Core/Format/USTx.cs +++ b/OpenUtau.Core/Format/USTx.cs @@ -10,7 +10,7 @@ namespace OpenUtau.Core.Format { public class Ustx { - public static readonly Version kUstxVersion = new Version(0, 9); + public static readonly Version kUstxVersion = new Version(0, 9, 1); public const string DYN = "dyn"; public const string PITD = "pitd"; diff --git a/OpenUtau.Core/ThirdParty/SimplexNoise.cs b/OpenUtau.Core/ThirdParty/SimplexNoise.cs new file mode 100644 index 000000000..8258fabed --- /dev/null +++ b/OpenUtau.Core/ThirdParty/SimplexNoise.cs @@ -0,0 +1,455 @@ +/* +BSD 3-Clause License + +Copyright (c) 2019, Benjamin Ward +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Simplex Noise for C# +// Copyright © Benjamin Ward 2019 +// See LICENSE +// Simplex Noise implementation offering 1D, 2D, and 3D forms w/ values in the range of 0 to 255. +// Based on work by Heikki Törmälä (2012) and Stefan Gustavson (2006). + +using System; + +namespace SimplexNoise +{ + /// + /// Implementation of the Perlin simplex noise, an improved Perlin noise algorithm. + /// Based loosely on SimplexNoise1234 by Stefan Gustavson: http://staffwww.itn.liu.se/~stegu/aqsis/aqsis-newnoise/ + /// + public static class Noise + { + /// + /// Creates 1D Simplex noise + /// + /// The number of points to generate + /// The scale of the noise. The greater the scale, the denser the noise gets + /// An array containing 1D Simplex noise + public static float[] Calc1D(int width, float scale) + { + var values = new float[width]; + for (var i = 0; i < width; i++) + values[i] = Generate(i * scale) * 128 + 128; + return values; + } + + /// + /// Creates 2D Simplex noise + /// + /// The number of points to generate in the 1st dimension + /// The number of points to generate in the 2nd dimension + /// The scale of the noise. The greater the scale, the denser the noise gets + /// An array containing 2D Simplex noise + public static float[,] Calc2D(int width, int height, float scale) + { + var values = new float[width, height]; + for (var i = 0; i < width; i++) + for (var j = 0; j < height; j++) + values[i, j] = Generate(i * scale, j * scale) * 128 + 128; + return values; + } + + /// + /// Creates 3D Simplex noise + /// + /// The number of points to generate in the 1st dimension + /// The number of points to generate in the 2nd dimension + /// The number of points to generate in the 3nd dimension + /// The scale of the noise. The greater the scale, the denser the noise gets + /// An array containing 3D Simplex noise + public static float[, ,] Calc3D(int width, int height, int length, float scale) + { + var values = new float[width, height, length]; + for (var i = 0; i < width; i++) + for (var j = 0; j < height; j++) + for (var k = 0; k < length; k++) + values[i, j, k] = Generate(i * scale, j * scale, k * scale) * 128 + 128; + return values; + } + + /// + /// Gets the value of an index of 1D simplex noise + /// + /// Index + /// The scale of the noise. The greater the scale, the denser the noise gets + /// The value of an index of 1D simplex noise + public static float CalcPixel1D(int x, float scale) + { + return Generate(x * scale) * 128 + 128; + } + + /// + /// Gets the value of an index of 2D simplex noise + /// + /// 1st dimension index + /// 2st dimension index + /// The scale of the noise. The greater the scale, the denser the noise gets + /// The value of an index of 2D simplex noise + public static float CalcPixel2D(int x, int y, float scale) + { + return Generate(x * scale, y * scale) * 128 + 128; + } + + + /// + /// Gets the value of an index of 3D simplex noise + /// + /// 1st dimension index + /// 2nd dimension index + /// 3rd dimension index + /// The scale of the noise. The greater the scale, the denser the noise gets + /// The value of an index of 3D simplex noise + public static float CalcPixel3D(int x, int y, int z, float scale) + { + return Generate(x * scale, y * scale, z * scale) * 128 + 128; + } + + static Noise() + { + _perm = new byte[PermOriginal.Length]; + PermOriginal.CopyTo(_perm, 0); + } + + /// + /// Arbitrary integer seed used to generate lookup table used internally + /// + public static int Seed + { + get => _seed; + set + { + if (value == 0) + { + _perm = new byte[PermOriginal.Length]; + PermOriginal.CopyTo(_perm, 0); + } + else + { + // Begin discontinuity fix by @ntark // + + // Fixes issue #7 and #8 + // https://github.com/WardBenjamin/SimplexNoise/pull/13 + // permutation matrix is duplicated to handle rollovers + _perm = new byte[512]; + + var perm = new byte[256]; + new Random(value).NextBytes(perm); + + Array.Copy(perm, 0, _perm, 0, 256); + Array.Copy(perm, 0, _perm, 256, 256); + + // End discontinuity fix // + } + + _seed = value; + } + } + + private static int _seed; + + /// + /// 1D simplex noise + /// + /// + /// + public static float Generate(float x) // made public + { + var i0 = FastFloor(x); + var i1 = i0 + 1; + var x0 = x - i0; + var x1 = x0 - 1.0f; + + var t0 = 1.0f - x0 * x0; + t0 *= t0; + var n0 = t0 * t0 * Grad(_perm[i0 & 0xff], x0); + + var t1 = 1.0f - x1 * x1; + t1 *= t1; + var n1 = t1 * t1 * Grad(_perm[i1 & 0xff], x1); + // The maximum value of this noise is 8*(3/4)^4 = 2.53125 + // A factor of 0.395 scales to fit exactly within [-1,1] + return 0.395f * (n0 + n1); + } + + /// + /// 2D simplex noise + /// + /// + /// + /// + public static float Generate(float x, float y) // made public + { + const float F2 = 0.366025403f; // F2 = 0.5*(sqrt(3.0)-1.0) + const float G2 = 0.211324865f; // G2 = (3.0-Math.sqrt(3.0))/6.0 + + float n0, n1, n2; // Noise contributions from the three corners + + // Skew the input space to determine which simplex cell we're in + var s = (x + y) * F2; // Hairy factor for 2D + var xs = x + s; + var ys = y + s; + var i = FastFloor(xs); + var j = FastFloor(ys); + + var t = (i + j) * G2; + var X0 = i - t; // Unskew the cell origin back to (x,y) space + var Y0 = j - t; + var x0 = x - X0; // The x,y distances from the cell origin + var y0 = y - Y0; + + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords + if (x0 > y0) { i1 = 1; j1 = 0; } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { i1 = 0; j1 = 1; } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + + var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + var y1 = y0 - j1 + G2; + var x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords + var y2 = y0 - 1.0f + 2.0f * G2; + + // Wrap the integer indices at 256, to avoid indexing perm[] out of bounds + var ii = Mod(i, 256); + var jj = Mod(j, 256); + + // Calculate the contribution from the three corners + var t0 = 0.5f - x0 * x0 - y0 * y0; + if (t0 < 0.0f) n0 = 0.0f; + else + { + t0 *= t0; + n0 = t0 * t0 * Grad(_perm[ii + _perm[jj]], x0, y0); + } + + var t1 = 0.5f - x1 * x1 - y1 * y1; + if (t1 < 0.0f) n1 = 0.0f; + else + { + t1 *= t1; + n1 = t1 * t1 * Grad(_perm[ii + i1 + _perm[jj + j1]], x1, y1); + } + + var t2 = 0.5f - x2 * x2 - y2 * y2; + if (t2 < 0.0f) n2 = 0.0f; + else + { + t2 *= t2; + n2 = t2 * t2 * Grad(_perm[ii + 1 + _perm[jj + 1]], x2, y2); + } + + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 40.0f * (n0 + n1 + n2); // TODO: The scale factor is preliminary! + } + + + public static float Generate(float x, float y, float z) // made public + { + // Simple skewing factors for the 3D case + const float F3 = 0.333333333f; + const float G3 = 0.166666667f; + + float n0, n1, n2, n3; // Noise contributions from the four corners + + // Skew the input space to determine which simplex cell we're in + var s = (x + y + z) * F3; // Very nice and simple skew factor for 3D + var xs = x + s; + var ys = y + s; + var zs = z + s; + var i = FastFloor(xs); + var j = FastFloor(ys); + var k = FastFloor(zs); + + var t = (i + j + k) * G3; + var X0 = i - t; // Unskew the cell origin back to (x,y,z) space + var Y0 = j - t; + var Z0 = k - t; + var x0 = x - X0; // The x,y,z distances from the cell origin + var y0 = y - Y0; + var z0 = z - Z0; + + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + // Determine which simplex we are in. + int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + + /* This code would benefit from a backport from the GLSL version! */ + if (x0 >= y0) + { + if (y0 >= z0) + { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0; } // X Y Z order + else if (x0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1; } // X Z Y order + else { i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1; } // Z X Y order + } + else + { // x0 0) ? ((int)x) : (((int)x) - 1); + } + + private static int Mod(int x, int m) + { + var a = x % m; + return a < 0 ? a + m : a; + } + + private static float Grad(int hash, float x) + { + var h = hash & 15; + var grad = 1.0f + (h & 7); // Gradient value 1.0, 2.0, ..., 8.0 + if ((h & 8) != 0) grad = -grad; // Set a random sign for the gradient + return (grad * x); // Multiply the gradient with the distance + } + + private static float Grad(int hash, float x, float y) + { + var h = hash & 7; // Convert low 3 bits of hash code + var u = h < 4 ? x : y; // into 8 simple gradient directions, + var v = h < 4 ? y : x; // and compute the dot product with (x,y). + return ((h & 1) != 0 ? -u : u) + ((h & 2) != 0 ? -2.0f * v : 2.0f * v); + } + + private static float Grad(int hash, float x, float y, float z) + { + var h = hash & 15; // Convert low 4 bits of hash code into 12 simple + var u = h < 8 ? x : y; // gradient directions, and compute dot product. + var v = h < 4 ? y : h == 12 || h == 14 ? x : z; // Fix repeats at h = 12 to 15 + return ((h & 1) != 0 ? -u : u) + ((h & 2) != 0 ? -v : v); + } + + private static float Grad(int hash, float x, float y, float z, float t) + { + var h = hash & 31; // Convert low 5 bits of hash code into 32 simple + var u = h < 24 ? x : y; // gradient directions, and compute dot product. + var v = h < 16 ? y : z; + var w = h < 8 ? z : t; + return ((h & 1) != 0 ? -u : u) + ((h & 2) != 0 ? -v : v) + ((h & 4) != 0 ? -w : w); + } + } +} diff --git a/OpenUtau.Core/Ustx/UNote.cs b/OpenUtau.Core/Ustx/UNote.cs index e4c8bd144..381c34719 100644 --- a/OpenUtau.Core/Ustx/UNote.cs +++ b/OpenUtau.Core/Ustx/UNote.cs @@ -6,6 +6,7 @@ using OpenUtau.Api; using OpenUtau.Core.Util; using YamlDotNet.Serialization; +using SimplexNoise; namespace OpenUtau.Core.Ustx { public class UNote : IComparable { @@ -281,6 +282,14 @@ public class UVibrato { float _drift = NotePresets.Default.DefaultVibrato.VibratoDrift; // Percentage of volume reduction in linkage with vibrato. When this is 100%, volume will be 1.2 times to 0.2 times regardless of depth. float _volLink = NotePresets.Default.DefaultVibrato.VibratoVolLink; + // Amount of variation in the shape of the vibrato curve. + float _variation = NotePresets.Default.DefaultVibrato.VibratoVariation; + // Amount of variation in the intensity of the vibrato curve. + float _pitchVariation = NotePresets.Default.DefaultVibrato.VibratoVariation; + // Frequency of variation in the shape of the vibrato curve. + float _variationFreq = NotePresets.Default.DefaultVibrato.VibratoVariationFreq; + // Random seed used for vibrato variation + int _variationSeed = NotePresets.Default.DefaultVibrato.VibratoVariationSeed; public float length { get => _length; set => _length = Math.Max(0, Math.Min(100, value)); } public float period { get => _period; set => _period = Math.Max(5, Math.Min(500, value)); } @@ -305,6 +314,11 @@ public float @out { public float drift { get => _drift; set => _drift = Math.Max(-100, Math.Min(100, value)); } public float volLink { get => _volLink; set => _volLink = Math.Max(-100, Math.Min(100, value)); } + public float variation { get => _variation; set => _variation = Math.Max(0, Math.Min(2, value)); } + public float pitchVariation { get => _pitchVariation; set => _pitchVariation = Math.Max(0, Math.Min(2, value)); } + public float variationFreq { get => _variationFreq; set => _variationFreq = Math.Max(0.1F, Math.Min(8, value)); } + public int variationSeed { get => _variationSeed; set => _variationSeed = value; } + [YamlIgnore] public float NormalizedStart => 1f - length / 100f; public UVibrato Clone() { @@ -316,7 +330,11 @@ public UVibrato Clone() { @out = @out, shift = shift, drift = drift, - volLink = volLink + volLink = volLink, + variation = variation, + pitchVariation = pitchVariation, + variationFreq = variationFreq, + variationSeed = variationSeed }; return result; } @@ -338,7 +356,19 @@ public Vector2 Evaluate(float nPos, float nPeriod, UNote note) { float nOut = length / 100f * @out / 100f; float nOutPos = 1f - nOut; float t = (nPos - nStart) / nPeriod + shift / 100f; - float y = (float)Math.Sin(2 * Math.PI * t) * depth + (depth / 100 * drift); + float y; + if(variation > 0 || pitchVariation > 0) { + Noise.Seed = variationSeed; // setting seed might need optimization + // * 0.33F so it has a reasonable base value, it's too much at unity + float variationPhaseShift = Noise.Generate(t * variationFreq) * 0.33F; + Noise.Seed = variationSeed + 1; + float pitchNoise = Noise.Generate(t * variationFreq) * 100 * pitchVariation; + t += variationPhaseShift * variation; + float normalizedDepth = 200 * ((depth - 5) / (200 - 5)); + y = (float)Math.Sin(2 * Math.PI * t) * depth + (depth / 100 * drift) + (normalizedDepth / 100 * pitchNoise); + } else { + y = (float)Math.Sin(2 * Math.PI * t) * depth + (depth / 100 * drift); + } if (nPos < nStart) { y = 0; } else if (nPos < nInPos) { @@ -367,7 +397,17 @@ public float EvaluateVolume(float nPos, float nPeriod) { volLink *= -1; } float t = (nPos - nStart) / nPeriod + shift / 100f; - float reduction = (-(float)Math.Sin(2 * Math.PI * t) / 2 + 0.3f) * volLink / 100; + float reduction; + if(variation > 0) { + Noise.Seed = variationSeed; // setting seed might need optimization + // * 0.33F so it has a reasonable base value, it's too much at unity + // TODO actually use pitch variation to influence volume link too?? + float variationPhaseShift = Noise.Generate(t * variationFreq) * 0.33F; + t += variationPhaseShift * variation; + reduction = (-(float)Math.Sin(2 * Math.PI * t) / 2 + 0.3f) * volLink / 100; + } else { + reduction = (-(float)Math.Sin(2 * Math.PI * t) / 2 + 0.3f) * volLink / 100; + } if (nPos < nStart) { reduction = 0; } else if (nPos < nInPos) { diff --git a/OpenUtau.Core/Ustx/UProject.cs b/OpenUtau.Core/Ustx/UProject.cs index 462f2a513..b63c27bbf 100644 --- a/OpenUtau.Core/Ustx/UProject.cs +++ b/OpenUtau.Core/Ustx/UProject.cs @@ -121,6 +121,7 @@ public UNote CreateNote() { int length = NotePresets.Default.DefaultPortamento.PortamentoLength; note.pitch.AddPoint(new PitchPoint(start, 0)); note.pitch.AddPoint(new PitchPoint(start + length, 0)); + note.vibrato.variationSeed = Random.Shared.Next(); return note; } diff --git a/OpenUtau.Core/Util/NotePresets.cs b/OpenUtau.Core/Util/NotePresets.cs index 7a46e715b..c0dbeff28 100644 --- a/OpenUtau.Core/Util/NotePresets.cs +++ b/OpenUtau.Core/Util/NotePresets.cs @@ -46,10 +46,10 @@ public static void Reset() { new PortamentoPreset("Snap", 2, -1), }); Default.VibratoPresets.AddRange(new List { - new VibratoPreset("Standard", 75, 175, 25, 10, 10, 0, 0, 0), - new VibratoPreset("UTAU Default", 65, 180, 35, 20, 20, 0, 0, 0), - new VibratoPreset("UTAU Strong", 65, 210, 55, 25, 25, 0, 0, 0), - new VibratoPreset("UTAU Weak", 65, 165, 20, 25, 25, 0, 0, 0) + new VibratoPreset("Standard", 75, 175, 25, 10, 10, 0, 0, 0, 0, 0, 1, 0), + new VibratoPreset("UTAU Default", 65, 180, 35, 20, 20, 0, 0, 0, 0, 0, 1, 0), + new VibratoPreset("UTAU Strong", 65, 210, 55, 25, 25, 0, 0, 0, 0, 0, 1, 0), + new VibratoPreset("UTAU Weak", 65, 165, 20, 25, 25, 0, 0, 0, 0, 0, 1, 0) }); Save(); @@ -61,7 +61,7 @@ public class SerializableNotePresets { public string SplittedLyric = "+"; public PortamentoPreset DefaultPortamento = new PortamentoPreset("Standard", 80, -40); public List PortamentoPresets = new List { }; - public VibratoPreset DefaultVibrato = new VibratoPreset("Standard", 75, 175, 25, 10, 10, 0, 0, 0); + public VibratoPreset DefaultVibrato = new VibratoPreset(); public List VibratoPresets = new List { }; public bool AutoVibratoToggle = false; public int AutoVibratoNoteDuration = 481; @@ -91,8 +91,12 @@ public class VibratoPreset { public float VibratoShift = 0; public float VibratoDrift = 0; public float VibratoVolLink = 0; + public float VibratoVariation = 0; + public float VibratoPitchVariation = 0; + public float VibratoVariationFreq = 1; + public int VibratoVariationSeed = 0; - public VibratoPreset(string name, float length, float period, float depth, float fadein, float fadeout, float shift, float drift, float volLink) { + public VibratoPreset(string name, float length, float period, float depth, float fadein, float fadeout, float shift, float drift, float volLink, float variation, float pitchVariation, float variationFrequency, int variationSeed) { Name = name; VibratoLength = length; VibratoPeriod = period; @@ -102,8 +106,14 @@ public VibratoPreset(string name, float length, float period, float depth, float VibratoShift = shift; VibratoDrift = drift; VibratoVolLink = volLink; + VibratoVariation = variation; + VibratoPitchVariation = pitchVariation; + VibratoVariationFreq = variationFrequency; + VibratoVariationSeed = variationSeed; } + public VibratoPreset() {} + public override string ToString() => Name; } diff --git a/OpenUtau.Test/Core/USTx/UstxYamlTest.cs b/OpenUtau.Test/Core/USTx/UstxYamlTest.cs index c7d500dce..69c003e20 100644 --- a/OpenUtau.Test/Core/USTx/UstxYamlTest.cs +++ b/OpenUtau.Test/Core/USTx/UstxYamlTest.cs @@ -22,6 +22,10 @@ public UstxYamlTest(ITestOutputHelper output) { index = 0, value = 123, }); + note.vibrato.variation = 0.9F; + note.vibrato.pitchVariation = 0.4F; + note.vibrato.variationFreq = 1; + note.vibrato.variationSeed = 12; } [Fact] @@ -38,7 +42,7 @@ public void UNoteSerializationTest() { - {x: -5, y: 0, shape: io} - {x: 5, y: 0, shape: io} snap_first: true -vibrato: {length: 0, period: 175, depth: 25, in: 10, out: 10, shift: 0, drift: 0, vol_link: 0} +vibrato: {length: 0, period: 175, depth: 25, in: 10, out: 10, shift: 0, drift: 0, vol_link: 0, variation: 0.9, pitch_variation: 0.4, variation_freq: 1, variation_seed: 12} tuning: 0 phoneme_expressions: - {index: 0, abbr: vel, value: 123} @@ -62,6 +66,10 @@ public void UNoteDeserializationTest() { Assert.NotNull(vel); Assert.Null(vel.descriptor); Assert.Equal(123, vel.value); + Assert.Equal(0.9F, actual.vibrato.variation); + Assert.Equal(0.4F, actual.vibrato.pitchVariation); + Assert.Equal(1.0F, actual.vibrato.variationFreq); + Assert.Equal(12, actual.vibrato.variationSeed); } [Fact] diff --git a/OpenUtau/Controls/NotePropertiesControl.axaml b/OpenUtau/Controls/NotePropertiesControl.axaml index 2d1921a3c..c80e8cf98 100644 --- a/OpenUtau/Controls/NotePropertiesControl.axaml +++ b/OpenUtau/Controls/NotePropertiesControl.axaml @@ -146,6 +146,35 @@ TickPlacement="BottomRight" TickFrequency="0.1" IsSnapToTickEnabled="true" Tag="VibratoVolLink"/> + + + + + + + +