Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MusicXML Import #1152

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
8 changes: 7 additions & 1 deletion OpenUtau.Core/Format/Formats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using OpenUtau.Core.Ustx;

namespace OpenUtau.Core.Format {
public enum ProjectFormats { Unknown, Vsq3, Vsq4, Ust, Ustx, Midi, Ufdata };
public enum ProjectFormats { Unknown, Vsq3, Vsq4, Ust, Ustx, Midi, Ufdata, Musicxml };

public static class Formats {
const string ustMatch = "[#SETTING]";
Expand All @@ -15,6 +15,7 @@ public static class Formats {
const string vsq4Match = VSQx.vsq4NameSpace;
const string midiMatch = "MThd";
const string ufdataMatch = "\"formatVersion\":";
const string musicxmlMatch = "score-partwise";

public static ProjectFormats DetectProjectFormat(string file) {
var lines = new List<string>();
Expand All @@ -36,6 +37,8 @@ public static ProjectFormats DetectProjectFormat(string file) {
return ProjectFormats.Midi;
} else if (contents.Contains(ufdataMatch)) {
return ProjectFormats.Ufdata;
} else if (contents.Contains(musicxmlMatch)) {
return ProjectFormats.Musicxml;
} else {
return ProjectFormats.Unknown;
}
Expand Down Expand Up @@ -68,6 +71,9 @@ public static ProjectFormats DetectProjectFormat(string file) {
case ProjectFormats.Ufdata:
project = Ufdata.Load(files[0]);
break;
case ProjectFormats.Musicxml:
project = MusicXML.LoadProject(files[0]);
break;
default:
throw new FileFormatException("Unknown file format");
}
Expand Down
129 changes: 129 additions & 0 deletions OpenUtau.Core/Format/MusicXML.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Collections.Generic;
using OpenUtau.Core;
using OpenUtau.Core.Ustx;
using Serilog;
using UtfUnknown;

namespace OpenUtau.Core.Format
{
public static class MusicXML
{
static public UProject LoadProject(string file)
{
UProject uproject = new UProject();
Ustx.AddDefaultExpressions(uproject);

uproject.tracks.Clear();
uproject.parts.Clear();
uproject.tempos.Clear();
uproject.timeSignatures.Clear();

var score = ReadXMLScore(file);

foreach (var part in score.Part)
{
var utrack = new UTrack(uproject);
utrack.TrackNo = uproject.tracks.Count;
uproject.tracks.Add(utrack);

var upart = new UVoicePart();
upart.trackNo = utrack.TrackNo;
uproject.parts.Add(upart);

int divisions = (int)part.Measure[0].Attributes[0].Divisions;
int currPosTick = 0;

foreach (var measure in part.Measure)
{
// BPM
double? bpm;
if ((bpm = MeasureBPM(measure)).HasValue) {
uproject.tempos.Add(new UTempo(currPosTick, bpm.Value));
Log.Information($"Measure {measure.Number} BPM: {bpm.ToString()}");
}

// Time Signature
foreach (var attributes in measure.Attributes) {
foreach (var time in attributes.Time) {
if (time.Beats.Count > 0 && time.BeatType.Count > 0) {
uproject.timeSignatures.Add(new UTimeSignature {
barPosition = currPosTick,
beatPerBar = Int32.Parse(time.Beats[0]),
beatUnit = Int32.Parse(time.BeatType[0])
});
Log.Information($"Measure {measure.Number} Time Signature: {time.Beats[0]}/{time.BeatType[0]}");
}
}
}

// Note
foreach(var note in measure.Note) {
int durTick = (int)note.Duration * uproject.resolution / divisions;

if (note.Rest != null) {
// pass
}
else {
var pitch = note.Pitch.Step.ToString() + note.Pitch.Octave.ToString();
int tone = MusicMath.NameToTone(pitch) + (int)note.Pitch.Alter;
UNote unote = uproject.CreateNote(tone, currPosTick, durTick);
if (note.Lyric.Count > 0) {
unote.lyric = note.Lyric[0].Text[0].Value;
}
upart.notes.Add(unote);
}

currPosTick += durTick;
}
}
upart.Duration = upart.GetMinDurTick(uproject);
}
uproject.AfterLoad();
uproject.ValidateFull();
return uproject;
}

static public Encoding DetectXMLEncoding(string file)
{
Encoding xmlEncoding = Encoding.UTF8;
var detectionResult = CharsetDetector.DetectFromFile(file);

if (detectionResult.Detected != null && detectionResult.Detected.Confidence > 0.5)
{
xmlEncoding = detectionResult.Detected.Encoding;
}
return xmlEncoding;
}

static public double? MeasureBPM(MusicXMLSchema.ScorePartwisePartMeasure measure)
{
foreach (var direction in measure.Direction) {
if (direction.Sound != null) { return (double)direction.Sound.Tempo; }
}
return null;
}

static public MusicXMLSchema.ScorePartwise ReadXMLScore(string xmlFile)
{
Log.Information($"MusicXML Character Encoding: {DetectXMLEncoding(xmlFile)}");

XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Parse;
settings.MaxCharactersFromEntities = 1024;

using (var fs = new FileStream(xmlFile, FileMode.Open))
using (var xmlReader = XmlReader.Create(fs, settings))
{
XmlSerializer s = new XmlSerializer(typeof(MusicXMLSchema.ScorePartwise));

var score = s.Deserialize(xmlReader) as MusicXMLSchema.ScorePartwise;
return score;
}
}
}
}
Loading
Loading