You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
4.4 KiB
183 lines
4.4 KiB
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 Output(string destinationFilename) |
|
{ |
|
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; |
|
} |
|
} |
|
} |
|
} |
|
}
|
|
|