196 lines
4.8 KiB
C#
196 lines
4.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Melanchall.DryWetMidi.Smf;
|
|
using Melanchall.DryWetMidi.Smf.Interaction;
|
|
|
|
using SBRL.Utilities;
|
|
|
|
namespace MusicBoxConverter
|
|
{
|
|
public class MusicBoxScoreGenerator
|
|
{
|
|
public bool Debug { get; set; } = false;
|
|
|
|
public Vector2 Offset { get; set; } = new Vector2(10, 10);
|
|
public Vector2 ScaleFactor { get; set; } = new Vector2(0.03f, 4f);
|
|
|
|
public float HoleSize { get; set; } = 1f;
|
|
|
|
public Vector2 ArrowSize { get; set; } = new Vector2(25, 25);
|
|
public float ArrowSpacing { get; set; } = 50f;
|
|
public float ArrowStrokeWidth { get; set; } = 8;
|
|
public string ArrowColour { get; set; } = "#dacef3";
|
|
|
|
private int trackLength;
|
|
public int TrackLength
|
|
{
|
|
get
|
|
{
|
|
if (trackLength == 0)
|
|
trackLength = (int)AllNotes().Max((Note arg) => arg.Time);
|
|
return trackLength;
|
|
}
|
|
}
|
|
|
|
public int MaxNoteNumber {
|
|
get {
|
|
return SelectedMusicBox.HighestNote.NoteNumber;
|
|
}
|
|
}
|
|
public int MinNoteNumber {
|
|
get
|
|
{
|
|
return SelectedMusicBox.LowestNote.NoteNumber;
|
|
}
|
|
}
|
|
|
|
public MusicBox SelectedMusicBox { get; private set; }
|
|
|
|
private MidiFile midiFile;
|
|
|
|
public MusicBoxScoreGenerator(string filename, MusicBox inMusicBox)
|
|
{
|
|
SelectedMusicBox = inMusicBox;
|
|
midiFile = MidiFile.Read(filename);
|
|
|
|
// Set the scale factor based on the strip height of the music box
|
|
ScaleFactor = new Vector2(
|
|
ScaleFactor.X,
|
|
SelectedMusicBox.StripHeightMm / (SelectedMusicBox.NoteCount-1)
|
|
);
|
|
|
|
foreach(Note note in AllNotes()) {
|
|
if (!SelectedMusicBox.IsValidNote(note))
|
|
Console.Error.WriteLine($"Warning: The note {note} at {note.Time} can't be played by the {SelectedMusicBox}.");
|
|
}
|
|
}
|
|
|
|
public void SetScaleFactorX(float scaleFactorX) {
|
|
Vector2 newScaleFactor = ScaleFactor;
|
|
newScaleFactor.X = scaleFactorX;
|
|
ScaleFactor = newScaleFactor;
|
|
}
|
|
|
|
public void SetScaleFactorY(float scaleFactorY) {
|
|
Vector2 newScaleFactor = ScaleFactor;
|
|
newScaleFactor.Y = scaleFactorY;
|
|
ScaleFactor = newScaleFactor;
|
|
}
|
|
|
|
public void Output(string destinationFilename)
|
|
{
|
|
Console.WriteLine($"DEBUG SF {ScaleFactor}");
|
|
Vector2 area = new Vector2(TrackLength, SelectedMusicBox.NoteCount-1).Multiply(ScaleFactor);
|
|
Vector2 size = area.Add(Offset.Multiply(2));
|
|
|
|
SvgWriter svg = new SvgWriter(
|
|
destinationFilename,
|
|
$"{size.X}mm", $"{size.Y}mm"
|
|
) {
|
|
UnitSuffix = "mm"
|
|
};
|
|
|
|
// Draw directional arrows down the score
|
|
for (float i = 0; i < area.X; i += ArrowSpacing)
|
|
{
|
|
Vector2 arrowPos = new Vector2(i, area.Y / 2 + Offset.Y);
|
|
|
|
svg.WriteLine(
|
|
arrowPos.Subtract(ArrowSize),
|
|
arrowPos,
|
|
ArrowColour,
|
|
ArrowStrokeWidth
|
|
);
|
|
svg.WriteLine(
|
|
arrowPos,
|
|
arrowPos.Subtract(new Vector2(ArrowSize.X, -ArrowSize.Y)),
|
|
ArrowColour,
|
|
ArrowStrokeWidth
|
|
);
|
|
}
|
|
|
|
|
|
// Draw a marker at the beginning of the score
|
|
svg.WriteRectangle(
|
|
new Vector2(Offset.X / 2, Offset.Y),
|
|
new Vector2(Offset.X / 2, area.Y),
|
|
"#ffa500", -1,
|
|
true
|
|
);
|
|
|
|
svg.WriteCircle(
|
|
new Vector2((Offset.X * 3) / 4, Offset.Y * 1.25f),
|
|
HoleSize,
|
|
"#ff0077"
|
|
);
|
|
svg.WriteCircle(
|
|
new Vector2((Offset.X * 3) / 4, Offset.Y + area.Y - Offset.Y * 0.25f),
|
|
HoleSize * 2,
|
|
"#0077ff"
|
|
);
|
|
|
|
|
|
|
|
|
|
// Draw the note lines
|
|
for (float i = 0; i < area.Y; i += ScaleFactor.Y) {
|
|
Vector2 start = Offset.Add(new Vector2(0, i));
|
|
svg.WriteLine(start, start.Add(new Vector2(area.X, 0)), "darkgreen", 0.75f);
|
|
}
|
|
|
|
// Draw a red box around everything
|
|
svg.WriteRectangle(Offset, area, "red", 0.75f);
|
|
|
|
|
|
int noteIndex = 0;
|
|
foreach (Note note in AllNotes())
|
|
{
|
|
if(Debug) {
|
|
Console.WriteLine(
|
|
$"[Note] {note.Time}: " +
|
|
$"{note.NoteName}{note.Octave}/{note.NoteNumber} " +
|
|
$"-> {SelectedMusicBox.NoteToBoxNumber(note)}"
|
|
);
|
|
}
|
|
|
|
Vector2 holePosition = new Vector2(
|
|
Offset.X + note.Time * ScaleFactor.X,
|
|
Offset.Y + ((SelectedMusicBox.NoteCount - 1) - SelectedMusicBox.NoteToBoxNumber(note)) * ScaleFactor.Y
|
|
);
|
|
|
|
svg.WriteCircle(
|
|
holePosition,
|
|
HoleSize // radius
|
|
);
|
|
|
|
svg.WritePlus(
|
|
holePosition,
|
|
new Vector2(HoleSize, HoleSize).Multiply(1.5f)
|
|
);
|
|
|
|
svg.WriteText(
|
|
holePosition.Subtract(new Vector2(-HoleSize * 2, HoleSize/2 + -svg.FontSizeRegular/2)),
|
|
$"{noteIndex}",
|
|
"regular"
|
|
);
|
|
|
|
noteIndex++;
|
|
}
|
|
|
|
svg.Complete();
|
|
}
|
|
|
|
public IEnumerable<Note> AllNotes()
|
|
{
|
|
foreach(TrackChunk chunk in midiFile.Chunks.OfType<TrackChunk>())
|
|
{
|
|
using(NotesManager notesManager = new NotesManager(chunk.Events))
|
|
{
|
|
foreach(Note note in notesManager.Notes)
|
|
yield return note;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|