using System; using System.Collections.Generic; using System.Linq; using GamecraftModdingAPI; using GamecraftModdingAPI.Players; using GamecraftModdingAPI.Blocks; using GamecraftModdingAPI.Utility; using Melanchall.DryWetMidi.Core; using Melanchall.DryWetMidi.Devices; using Melanchall.DryWetMidi.Interaction; using Pixi.Common; using Unity.Mathematics; namespace Pixi.Audio { public class MidiImporter : Importer { public int Priority { get; } = 1; public bool Optimisable { get; } = false; public string Name { get; } = "Midi~Spell"; public BlueprintProvider BlueprintProvider { get; } = null; private Dictionary openFiles = new Dictionary(); public static bool ThreeDee = false; public static float Spread = 1f; public bool Qualifies(string name) { return name.EndsWith(".mid", StringComparison.InvariantCultureIgnoreCase); } public BlockJsonInfo[] Import(string name) { MidiFile midi = MidiFile.Read(name); openFiles[name] = midi; Logging.MetaLog($"Found {midi.GetNotes().Count()} notes over {midi.GetDuration().TimeSpan} time units"); BlockJsonInfo[] blocks = new BlockJsonInfo[(midi.GetNotes().Count() * 2) + 2]; #if DEBUG // test (for faster, but incomplete, imports) if (blocks.Length > 102) blocks = new BlockJsonInfo[102]; #endif // convert Midi notes to sfx blocks Dictionary breadthCache = new Dictionary(); uint count = 0; foreach (Note n in midi.GetNotes()) { // even blocks are counters, long microTime = n.TimeAs(midi.GetTempoMap()).TotalMicroseconds; float breadth = 1f; if (breadthCache.ContainsKey(microTime)) { breadth += breadthCache[microTime]++; } else { breadthCache[microTime] = 1; } blocks[count] = new BlockJsonInfo { name = GamecraftModdingAPI.Blocks.BlockIDs.Timer.ToString(), position = new float[] { breadth * 0.2f * Spread, 2 * 0.2f, microTime * 0.00001f * 0.2f * Spread}, rotation = new float[] { 0, 0, 0}, color = new float[] { -1, -1, -1}, scale = new float[] { 1, 1, 1}, }; count++; blocks[count] = new BlockJsonInfo { name = GamecraftModdingAPI.Blocks.BlockIDs.SFXBlockInstrument.ToString(), position = new float[] { breadth * 0.2f * Spread, 1 * 0.2f, microTime * 0.00001f * 0.2f * Spread}, rotation = new float[] { 0, 0, 0}, color = new float[] { -1, -1, -1}, scale = new float[] { 1, 1, 1}, }; count++; #if DEBUG // test (for faster, but incomplete, imports) if (count >= 100) break; #endif } // playback IO (reset & play) blocks[count] = new BlockJsonInfo { name = GamecraftModdingAPI.Blocks.BlockIDs.SimpleConnector.ToString(), position = new float[] { -0.2f, 2 * 0.2f, 0}, rotation = new float[] { 0, 0, 0}, color = new float[] { -1, -1, -1}, scale = new float[] { 1, 1, 1}, }; // play is second last (placed above reset) count++; blocks[count] = new BlockJsonInfo { name = GamecraftModdingAPI.Blocks.BlockIDs.SimpleConnector.ToString(), position = new float[] { -0.2f, 1 * 0.2f, 0}, rotation = new float[] { 0, 0, 0}, color = new float[] { -1, -1, -1}, scale = new float[] { 1, 1, 1}, }; // reset is last (placed below play) return blocks; } public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks) { Player p = new Player(PlayerType.Local); float3 pos = p.Position; for (int i = 0; i < blocks.Length; i++) { blocks[i].position += pos; } } public void PostProcess(string name, ref Block[] blocks) { // playback IO LogicGate startConnector = blocks[blocks.Length - 2].Specialise(); LogicGate resetConnector = blocks[blocks.Length - 1].Specialise(); uint count = 0; foreach (Note n in openFiles[name].GetNotes()) { // set timing info Timer t = blocks[count].Specialise(); t.Start = 0; t.End = n.TimeAs(openFiles[name].GetTempoMap()).TotalMicroseconds * 0.000001f; count++; // set notes info SfxBlock sfx = blocks[count].Specialise(); sfx.Pitch = n.NoteNumber - 60; // In MIDI, 60 is middle C, but GC uses 0 for middle C sfx.TrackIndex = 5; // Piano sfx.Is3D = ThreeDee; count++; // connect wires t.Connect(0, sfx, 0); startConnector.Connect(0, t, 0); resetConnector.Connect(0, t, 2); #if DEBUG // test (for faster, but incomplete, imports) if (count >= 100) break; #endif } openFiles.Remove(name); } } }