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
19 changes: 16 additions & 3 deletions OpenUtau.Core/Commands/ProjectCommands.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using OpenUtau.Core.Ustx;
using OpenUtau.Core.Util.MusicTheory;

namespace OpenUtau.Core {
public abstract class ProjectCommand : UCommand {
Expand Down Expand Up @@ -141,9 +142,9 @@ public override void Unexecute() {
}

public class KeyCommand : ProjectCommand{
public readonly int oldKey;
public readonly int newKey;
public KeyCommand(UProject project, int key) : base(project) {
public readonly Note oldKey;
public readonly Note newKey;
public KeyCommand(UProject project, Note key) : base(project) {
oldKey = project.key;
newKey = key;
}
Expand All @@ -152,6 +153,18 @@ public KeyCommand(UProject project, int key) : base(project) {
public override void Unexecute() => project.key = oldKey;
}

public class ModeCommand : ProjectCommand{
public readonly Mode oldMode;
public readonly Mode newMode;
public ModeCommand(UProject project, Mode key) : base(project) {
oldMode = project.mode;
newMode = key;
}
public override string ToString() => $"Change mode from {oldMode} to {newMode}";
public override void Execute() => project.mode = newMode;
public override void Unexecute() => project.mode = oldMode;
}

public class ConfigureExpressionsCommand : ProjectCommand {
readonly UExpressionDescriptor[] oldProjectDescriptors;
readonly UExpressionDescriptor[] newProjectDescriptors;
Expand Down
4 changes: 3 additions & 1 deletion OpenUtau.Core/Ustx/UProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using OpenUtau.Core.Util;
using OpenUtau.Core.Util.MusicTheory;
using SharpCompress;
using YamlDotNet.Serialization;

Expand Down Expand Up @@ -49,7 +50,8 @@ public class UProject {
public string[] expSelectors = new string[] { Format.Ustx.DYN, Format.Ustx.PITD, Format.Ustx.CLR, Format.Ustx.ENG, Format.Ustx.VEL, Format.Ustx.VOL, Format.Ustx.ATK, Format.Ustx.DEC, Format.Ustx.GEN, Format.Ustx.BRE };
public int expPrimary = 0;
public int expSecondary = 1;
public int key = 0;//Music key of the project, 0 = C, 1 = C#, 2 = D, ..., 11 = B
public Note key = Scale.Default().Tonic;
public Mode mode = Scale.Default().Mode;
public List<UTimeSignature> timeSignatures;
public List<UTempo> tempos;
public List<UTrack> tracks;
Expand Down
39 changes: 1 addition & 38 deletions OpenUtau.Core/Util/MusicMath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace OpenUtau.Core {
public static class MusicMath {
private static readonly double a = Math.Pow(2, 1.0 / 12);

// TODO move all the harmonics to MusicTheory namespace and refactor
public enum KeyColor { White, Black }

public static readonly Tuple<string, KeyColor>[] KeysInOctave = {
Expand Down Expand Up @@ -32,36 +33,6 @@ public enum KeyColor { White, Black }
{ "B", 11 },
};

public static readonly string[] Solfeges = {
"do",
"",
"re",
"",
"mi",
"fa",
"",
"sol",
"",
"la",
"",
"ti",
};

public static readonly string[] NumberedNotations = {
"1",
"",
"2",
"",
"3",
"4",
"",
"5",
"",
"6",
"",
"7",
};

public static string GetToneName(int noteNum) {
return noteNum < 0 ? string.Empty : KeysInOctave[noteNum % 12].Item1 + (noteNum / 12 - 1).ToString();
}
Expand All @@ -81,14 +52,6 @@ public static int NameToTone(string name) {
return 12 * (octave + 1) + inOctave;
}

public static bool IsBlackKey(int noteNum) {
return KeysInOctave[noteNum % 12].Item2 == KeyColor.Black;
}

public static bool IsCenterKey(int noteNum) {
return noteNum % 12 == 0;
}

public static double[] zoomRatios = { 4.0, 2.0, 1.0, 1.0 / 2, 1.0 / 4, 1.0 / 8, 1.0 / 16, 1.0 / 32, 1.0 / 64 };

public static double getZoomRatio(double quarterWidth, int beatPerBar, int beatUnit, double minWidth) {
Expand Down
13 changes: 13 additions & 0 deletions OpenUtau.Core/Util/MusicTheory/Mode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace OpenUtau.Core.Util.MusicTheory
{
public enum Mode
{
Lydian,
Ionian,
Mixolydian,
Dorian,
Aeolian,
Phrygian,
Locrian,
}
}
38 changes: 38 additions & 0 deletions OpenUtau.Core/Util/MusicTheory/Note.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;

namespace OpenUtau.Core.Util.MusicTheory;

public enum Note
{
C,
Csharp,
D,
Dsharp,
E,
F,
Fsharp,
G,
Gsharp,
A,
Asharp,
B,
}



public static class NoteHelper
{
public static Note CastNote(int note)
{
note %= Enum.GetValues<Note>().Length;
if (note < 0)
return Note.C;
else
return (Note)note;
}

public static string StringifyNote(Note note)
{
return note.ToString().Replace("sharp", "#");
}
}
84 changes: 84 additions & 0 deletions OpenUtau.Core/Util/MusicTheory/Scale.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace OpenUtau.Core.Util.MusicTheory;

public class Scale
{
public readonly Mode Mode;
private readonly List<Note> _scale;
public Note Tonic => _scale[0];

private Scale(List<Note> notes, Mode mode)
{
_scale = notes;
Mode = mode;
}

public bool IsOutOfScale(Note note)
{
return _scale.Contains(note) == false;
}

public bool IsTonic(Note note)
{
return Tonic.Equals(note);
}

public int? Interval(Note note)
{
int index = _scale.IndexOf(note);
return index >= 0 ? index + 1 : null;
}

public string? SolfegeIntervalName(Note note)
{
if (IsOutOfScale(note))
return null;

return note switch
{
Note.C => "do",
Note.Csharp => "do#",
Note.D => "re",
Note.Dsharp => "re#",
Note.E => "mi",
Note.F => "fa",
Note.Fsharp => "fa#",
Note.G => "sol",
Note.Gsharp => "sol#",
Note.A => "la",
Note.Asharp => "la#",
Note.B => "ti",
_ => null,
};
}

public static Scale Build(Note tonic, Mode mode)
{
int tonicValue = (int)tonic;
int[] intervals = mode switch
{
Mode.Lydian => [0, 2, 4, 6, 7, 9, 11],
Mode.Ionian => [0, 2, 4, 5, 7, 9, 11],
Mode.Mixolydian => [0, 2, 4, 5, 7, 9, 10],
Mode.Dorian => [0, 2, 3, 5, 7, 9, 10],
Mode.Aeolian => [0, 2, 3, 5, 7, 8, 10],
Mode.Phrygian => [0, 1, 3, 5, 7, 8, 10],
Mode.Locrian => [0, 1, 3, 5, 6, 8, 10],
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
};

var notes = intervals
.Select(interval => NoteHelper.CastNote(tonicValue + interval))
.ToList();

return new Scale(notes, mode);
}

public static Scale Default()
{
return Build(Note.C, Mode.Ionian);
}
}
32 changes: 32 additions & 0 deletions OpenUtau.Test/Core/Util/MusicTheory/NoteTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Xunit;

namespace OpenUtau.Core.Util.MusicTheory;

public class NoteTest
{
public class CastNote
{
[Fact]
public void BelowZeroReturnsC() => Assert.Equal(Note.C, NoteHelper.CastNote(-1));

[Fact]
public void ZeroReturnsC() => Assert.Equal(Note.C, NoteHelper.CastNote(0));

[Fact]
public void AboveTwelveReturnsModuloNoteIndex() =>
Assert.Equal(Note.Csharp, NoteHelper.CastNote(25));

[Fact]
public void ElseReturnsNoteIndex() => Assert.Equal(Note.G, NoteHelper.CastNote(7));
}

public class StringifyNote
{
[Fact]
public void PlainNoteDoesNotChange() => Assert.Equal("G", NoteHelper.StringifyNote(Note.G));

[Fact]
public void SharpIsStringifiedDoesNotChange() =>
Assert.Equal("G#", NoteHelper.StringifyNote(Note.Gsharp));
}
}
Loading