Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 92 additions & 18 deletions OpenUtau/Controls/ExpressionCanvas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class ExpressionCanvas : Control {
o => o.ShowRealCurve,
(o, v) => o.ShowRealCurve = v);

public static readonly DirectProperty<ExpressionCanvas, ExpDisMode> DisplayModeProperty =
AvaloniaProperty.RegisterDirect<ExpressionCanvas, ExpDisMode>(
nameof(DisplayMode), o => o.DisplayMode, (o, v) => o.DisplayMode = v);

public double TickWidth {
get => tickWidth;
private set => SetAndRaise(TickWidthProperty, ref tickWidth, value);
Expand All @@ -60,12 +64,18 @@ public bool ShowRealCurve {
get => showRealCurve;
set => SetAndRaise(ShowRealCurveProperty, ref showRealCurve, value);
}

public ExpDisMode DisplayMode {
get => displayMode;
set => SetAndRaise(DisplayModeProperty, ref displayMode, value);
}

private double tickWidth;
private double tickOffset;
private UVoicePart? part;
private string key = string.Empty;
private bool showRealCurve = true;
private ExpDisMode displayMode = ExpDisMode.Visible;

private HashSet<UNote> selectedNotes = new HashSet<UNote>();
private CurveSelection curveSelection = new CurveSelection();
Expand Down Expand Up @@ -99,6 +109,11 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang

public override void Render(DrawingContext context) {
base.Render(context);

// Skip rendering if hidden
if (DisplayMode == ExpDisMode.Hidden) {
return;
}
if (Part == null) {
return;
}
Expand All @@ -114,7 +129,11 @@ public override void Render(DrawingContext context) {
if (descriptor.max <= descriptor.min) {
return;
}
DrawBackgroundForHitTest(context);

if (DisplayMode != ExpDisMode.Shadow) {
DrawBackgroundForHitTest(context);
}

double leftTick = TickOffset - 480;
double rightTick = TickOffset + Bounds.Width / TickWidth + 480;
double optionHeight = descriptor.type == UExpressionType.Options
Expand All @@ -123,12 +142,14 @@ public override void Render(DrawingContext context) {
if (descriptor.type == UExpressionType.Curve) {
var curve = Part.curves.FirstOrDefault(c => c.descriptor == descriptor);
double defaultHeight = Math.Round(Bounds.Height - Bounds.Height * (descriptor.defaultValue - descriptor.min) / (descriptor.max - descriptor.min));
var lPen = ThemeManager.AccentPen1;
var lPen2 = ThemeManager.AccentPen1Thickness2;
var lPenSelected = ThemeManager.AccentPen2;
var lPen2Selected = ThemeManager.AccentPen2Thickness2;

var lPen = DisplayMode == ExpDisMode.Shadow ? ThemeManager.NeutralAccentPen : ThemeManager.AccentPen1;
var lPen2 = DisplayMode == ExpDisMode.Shadow ? new Pen(ThemeManager.NeutralAccentBrush, 3) : ThemeManager.AccentPen1Thickness3;
var lPenSelected = DisplayMode == ExpDisMode.Shadow ? ThemeManager.NeutralAccentPen : ThemeManager.AccentPen2;
var lPen2Selected = DisplayMode == ExpDisMode.Shadow ? new Pen(ThemeManager.NeutralAccentBrush, 3) : ThemeManager.AccentPen2Thickness3;
var lPen3 = new Pen(ThemeManager.NeutralAccentBrush, 1, new DashStyle(new double[] { 4, 4 }, 0));
var brush = ThemeManager.AccentBrush1;
var brush = DisplayMode == ExpDisMode.Shadow ? ThemeManager.NeutralAccentBrush : ThemeManager.AccentBrush1;

double x3 = Math.Round(viewModel.TickToneToPoint(leftTick, 0).X);
double x4 = Math.Round(viewModel.TickToneToPoint(rightTick, 0).X);
context.DrawLine(lPen3, new Point(x3, defaultHeight), new Point(x4, defaultHeight));
Expand Down Expand Up @@ -161,6 +182,12 @@ public override void Render(DrawingContext context) {
index = -index - 1;
}
index = Math.Max(0, index) - 1;

// Create geometry elements for the custom curve fill
var fillGeometry = new PathGeometry();
var fillFigure = new PathFigure { IsClosed = true };
bool fillStarted = false;

while (index < xs.Count) {
float tick1 = index < 0 ? lTick : xs[index];
float value1 = index < 0 ? descriptor.defaultValue : ys[index];
Expand All @@ -170,6 +197,18 @@ public override void Render(DrawingContext context) {
float value2 = index == xs.Count - 1 ? descriptor.defaultValue : ys[index + 1];
double x2 = viewModel.TickToneToPoint(tick2, 0).X;
double y2 = defaultHeight - Bounds.Height * (value2 - descriptor.defaultValue) / (descriptor.max - descriptor.min);

if (!fillStarted) {
fillFigure.StartPoint = new Point(x1, defaultHeight);
fillFigure.Segments!.Add(new LineSegment { Point = new Point(x1, y1), IsStroked = false });
fillStarted = true;
}
fillFigure.Segments!.Add(new LineSegment { Point = new Point(x2, y2), IsStroked = false });

if (tick2 >= rTick || index == xs.Count - 1) {
fillFigure.Segments!.Add(new LineSegment { Point = new Point(x2, defaultHeight), IsStroked = false });
}

IPen pen;
if (curveSelection.HasValue(descriptor.abbr)) {
if (curveSelection.StartPoint.x <= tick1 && tick1 <= curveSelection.EndPoint.x
Expand All @@ -182,14 +221,19 @@ public override void Render(DrawingContext context) {
pen = value1 == descriptor.defaultValue && value2 == descriptor.defaultValue ? lPen : lPen2;
}
context.DrawLine(pen, new Point(x1, y1), new Point(x2, y2));
//using (var state = context.PushTransform(Matrix.CreateTranslation(x1, y1))) {
// context.DrawGeometry(brush, null, pointGeometry);
//}
index++;
if (tick2 >= rTick) {
break;
}
}

if (fillStarted) {
fillGeometry.Figures!.Add(fillFigure);
using (var state = context.PushOpacity(0.2)) {
context.DrawGeometry(brush, null, fillGeometry);
}
}

if (ShowRealCurve) {
int baseIndexL = curve.realXs.BinarySearch(lTick);
if (baseIndexL < 0) {
Expand All @@ -202,7 +246,6 @@ public override void Render(DrawingContext context) {
}
int offset = baseIndexL;
while (offset < baseIndexR) {
// negative values are breakpoints
int start = offset;
while (start < baseIndexR && curve.realYs[start] < 0) ++start;
int end = start;
Expand All @@ -213,23 +256,24 @@ public override void Render(DrawingContext context) {
}
var geometry = new PathGeometry();
var figure = new PathFigure {
IsClosed = false
IsClosed = true
};
for (int i = start; i < end; ++i) {
float tick = curve.realXs[i];
float value = curve.realYs[i];
double x = viewModel.TickToneToPoint(tick, 0).X;
double y = Bounds.Height * (1 - value / 1000.0);

if (i == start) {
figure.StartPoint = new Point(x, Bounds.Height);
figure.StartPoint = new Point(x, defaultHeight);
}
figure.Segments!.Add(new LineSegment {
Point = new Point(x, y),
IsStroked = i != start
});
if (i == end - 1) {
figure.Segments!.Add(new LineSegment {
Point = new Point(x, Bounds.Height),
Point = new Point(x, defaultHeight),
IsStroked = false
});
}
Expand All @@ -241,6 +285,16 @@ public override void Render(DrawingContext context) {
}
return;
}
if (descriptor.type == UExpressionType.Numerical) {
double p1 = Math.Round(viewModel.TickToneToPoint(leftTick, 0).X);
double p2 = Math.Round(viewModel.TickToneToPoint(rightTick, 0).X);
var dashedPen = new Pen(ThemeManager.NeutralAccentBrushSemi, 1, new DashStyle(new double[] { 4, 4 }, 0));
double defaultHeight = Math.Round(Bounds.Height - Bounds.Height * (descriptor.defaultValue - descriptor.min) / (descriptor.max - descriptor.min));
context.DrawLine(dashedPen, new Point(p1, defaultHeight), new Point(p2, defaultHeight));
}
var shadowHPen = new Pen(ThemeManager.NeutralAccentBrush, 3);
var shadowVPen = new Pen(ThemeManager.NeutralAccentBrush, 3);

foreach (var phoneme in Part.phonemes) {
if (phoneme.Error || phoneme.Parent == null) {
continue;
Expand All @@ -251,17 +305,36 @@ public override void Render(DrawingContext context) {
continue;
}
var note = phoneme.Parent;
var hPen = selectedNotes.Contains(note) ? ThemeManager.AccentPen2Thickness2 : ThemeManager.AccentPen1Thickness2;
var vPen = selectedNotes.Contains(note) ? ThemeManager.AccentPen2Thickness3 : ThemeManager.AccentPen1Thickness3;
var brush = selectedNotes.Contains(note) ? ThemeManager.AccentBrush2 : ThemeManager.AccentBrush1;

var hPen = DisplayMode == ExpDisMode.Shadow ? shadowHPen : (selectedNotes.Contains(note) ? ThemeManager.AccentPen2Thickness3 : ThemeManager.AccentPen1Thickness3);
var vPen = DisplayMode == ExpDisMode.Shadow ? shadowVPen : (selectedNotes.Contains(note) ? ThemeManager.AccentPen2Thickness3 : ThemeManager.AccentPen1Thickness3);
var brush = DisplayMode == ExpDisMode.Shadow ? ThemeManager.NeutralAccentBrush : (selectedNotes.Contains(note) ? ThemeManager.AccentBrush2 : ThemeManager.AccentBrush1);

var (value, overriden) = phoneme.GetExpression(project, track, Key);
double x1 = Math.Round(viewModel.TickToneToPoint(phoneme.position, 0).X);
double x2 = Math.Round(viewModel.TickToneToPoint(phoneme.End, 0).X);

if (descriptor.type == UExpressionType.Numerical) {
double valueHeight = Math.Round(Bounds.Height - Bounds.Height * (value - descriptor.min) / (descriptor.max - descriptor.min));
double zeroHeight = Math.Round(Bounds.Height - Bounds.Height * (0f - descriptor.min) / (descriptor.max - descriptor.min));

double rectX = x1;
double rectY = Math.Min(zeroHeight, valueHeight);
double rectHeight = Math.Abs(zeroHeight - valueHeight);
double rectWidth = Math.Max(0, Math.Max(x1, x2) - rectX);
var fillRect = new Rect(rectX, rectY, rectWidth, rectHeight);

// Use 45% opacity if edited, 15% opacity if default
double fillOpacity = overriden ? 0.45 : 0.15;

using (var state = context.PushOpacity(fillOpacity)) {
context.DrawRectangle(brush, null, fillRect);
}

// Vertical and horizontal lines
context.DrawLine(vPen, new Point(x1 + 0.5, zeroHeight + 0.5), new Point(x1 + 0.5, valueHeight + 3));
context.DrawLine(hPen, new Point(x1 + 3, valueHeight), new Point(Math.Max(x1 + 3, x2 - 3), valueHeight));
context.DrawLine(hPen, new Point(x1 + 3, valueHeight), new Point(Math.Max(x1 + 3, x2), valueHeight));

using (var state = context.PushTransform(Matrix.CreateTranslation(x1 + 0.5, valueHeight))) {
context.DrawGeometry(overriden ? brush : ThemeManager.BackgroundBrush, vPen, pointGeometry);
}
Expand All @@ -281,7 +354,8 @@ public override void Render(DrawingContext context) {
}
}
}
if (descriptor.type == UExpressionType.Options) {

if (descriptor.type == UExpressionType.Options && DisplayMode != ExpDisMode.Shadow) {
for (int i = 0; i < descriptor.options.Length; ++i) {
string option = descriptor.options[i];
if (string.IsNullOrEmpty(option)) {
Expand Down
35 changes: 29 additions & 6 deletions OpenUtau/Controls/NotesCanvas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,37 @@ private void RenderNoteBody(UNote note, NotesViewModel viewModel, DrawingContext
Size size = viewModel.TickToneToSize(note.duration, 1);
size = size.WithWidth(size.Width - 1).WithHeight(Math.Floor(size.Height - 2));
Point rightBottom = new Point(leftTop.X + size.Width, leftTop.Y + size.Height);
bool hasError = note.Error;

// Check for Phoneme Errors (mimicking PhonemeCanvas behavior)
if (!hasError && Part != null && Part.phonemes != null) {
int phonemeCount = 0;
foreach (var p in Part.phonemes) {
if (p.Parent == note) {
phonemeCount++;
// If any attached phoneme has an error, the whole note is flagged
if (p.Error) {
hasError = true;
break;
}
}
}
// Edge Case: If the note is not a continuation/rest but generated 0 phonemes,
// it means the phonemizer completely failed to process the lyric.
if (!hasError && phonemeCount == 0 && !note.lyric.StartsWith("+") && !note.lyric.StartsWith("-")) {
hasError = true;
}
}
// apply the transparent/greyed-out brush if an error was found
var brush = selectedNotes.Contains(note)
? (note.Error ? ThemeManager.AccentBrush2Semi : ThemeManager.AccentBrush2)
: (note.Error ? ThemeManager.AccentBrush1Semi : ThemeManager.AccentBrush1);
? (hasError ? ThemeManager.AccentBrush3Semi : ThemeManager.AccentBrush2)
: (hasError ? ThemeManager.NeutralAccentBrushSemi : ThemeManager.AccentBrush1);

context.DrawRectangle(brush, null, new Rect(leftTop, rightBottom), 2, 2);
if (TrackHeight < 10 || note.lyric.Length == 0) {
return;
}
// grey out the Phonemizer Transition Badges
if (ShowPhonemizerTags && TrackHeight >= 20) {
string currentOver = note.PhonemizerOverride ?? "";
bool isCurrentDefault = string.IsNullOrEmpty(currentOver) || currentOver.Equals("Default", StringComparison.OrdinalIgnoreCase);
Expand All @@ -240,13 +264,12 @@ private void RenderNoteBody(UNote note, NotesViewModel viewModel, DrawingContext
bool isTransition = !isContinuation && ((note.Prev == null && !isCurrentDefault) || (note.Prev != null && currentPh != prevPh));

if (isTransition) {
// Badge Background utilizes the same hasError flag
var badgeBrush = selectedNotes.Contains(note)
? (note.Error ? ThemeManager.AccentBrush2Semi : ThemeManager.AccentBrush2)
: (note.Error ? ThemeManager.AccentBrush1Semi : ThemeManager.AccentBrush1);
? (hasError ? ThemeManager.AccentBrush3Semi : ThemeManager.AccentBrush2)
: (hasError ? ThemeManager.NeutralAccentBrushSemi : ThemeManager.AccentBrush1);

if (isCurrentDefault) {
// Due to the limitation, we'll display a dot to inndicate
// the transition to default phonemizer instead of showing language tag
double boxWidth = 16;
double boxHeight = 16;
double dotRadius = 3;
Expand Down
7 changes: 5 additions & 2 deletions OpenUtau/Controls/PianoRoll.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@
</Border>
</ToggleButton>
<ToggleButton Classes="toolbar" Margin="0" Padding="1" Height="20" Width="20" IsChecked="{Binding ShowPhonemizerTags}"
ToolTip.Tip="Show Phonemizer Tags">
ToolTip.Tip="{DynamicResource pianoroll.toggle.phonemizer}">
<Grid Width="18" Height="18">
<Path Classes="filled" Data="M21.41 11.58l-9-9C12.04 2.21 11.54 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.41l9 9c.36.36.86.58 1.41.58.55 0 1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.41zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z">
<Path.RenderTransform>
Expand Down Expand Up @@ -651,7 +651,9 @@
TickOrigin="{Binding NotesViewModel.TickOrigin}"
TickOffset="{Binding NotesViewModel.TickOffset}"
SnapDiv="{Binding NotesViewModel.SnapDiv, Mode=OneWay}"/>
<c:ExpressionCanvas Grid.Row="5" Grid.Column="1" Opacity="{Binding NotesViewModel.ExpShadowOpacity}"
<c:ExpressionCanvas Grid.Row="5" Grid.Column="1"
DisplayMode="Shadow"
Opacity="0.2"
IsVisible="{Binding NotesViewModel.ShowExpressions}"
Margin="0,24,0,0"
TickWidth="{Binding NotesViewModel.TickWidth}"
Expand All @@ -660,6 +662,7 @@
Key="{Binding NotesViewModel.SecondaryKey}"
ShowRealCurve="False"/>
<c:ExpressionCanvas Grid.Row="5" Grid.Column="1"
DisplayMode="Visible"
IsVisible="{Binding NotesViewModel.ShowExpressions}"
Margin="0,24,0,0"
Bounds="{Binding NotesViewModel.ExpBounds, Mode=OneWayToSource}"
Expand Down
Loading
Loading