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 AllNotes() { foreach(TrackChunk chunk in midiFile.Chunks.OfType()) { using(NotesManager notesManager = new NotesManager(chunk.Events)) { foreach(Note note in notesManager.Notes) yield return note; } } } } }