Create prototype MIDI importer
This commit is contained in:
parent
d8064db54d
commit
7f9ef818ac
5 changed files with 169 additions and 2 deletions
7
Pixi/Audio/AudioTools.cs
Normal file
7
Pixi/Audio/AudioTools.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Pixi.Audio
|
||||||
|
{
|
||||||
|
public static class AudioTools
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
147
Pixi/Audio/MidiImporter.cs
Normal file
147
Pixi/Audio/MidiImporter.cs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
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<string, MidiFile> openFiles = new Dictionary<string, MidiFile>();
|
||||||
|
|
||||||
|
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<MidiTimeSpan>().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<long, uint> breadthCache = new Dictionary<long, uint>();
|
||||||
|
uint count = 0;
|
||||||
|
foreach (Note n in midi.GetNotes())
|
||||||
|
{
|
||||||
|
// even blocks are counters,
|
||||||
|
long microTime = n.TimeAs<MetricTimeSpan>(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>();
|
||||||
|
LogicGate resetConnector = blocks[blocks.Length - 1].Specialise<LogicGate>();
|
||||||
|
uint count = 0;
|
||||||
|
foreach (Note n in openFiles[name].GetNotes())
|
||||||
|
{
|
||||||
|
// set timing info
|
||||||
|
Timer t = blocks[count].Specialise<Timer>();
|
||||||
|
t.Start = 0;
|
||||||
|
t.End = n.TimeAs<MetricTimeSpan>(openFiles[name].GetTempoMap()).TotalMicroseconds * 0.000001f;
|
||||||
|
count++;
|
||||||
|
// set notes info
|
||||||
|
SfxBlock sfx = blocks[count].Specialise<SfxBlock>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net472</TargetFramework>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
<Version>1.0.1</Version>
|
<Version>1.1.0</Version>
|
||||||
<Authors>NGnius</Authors>
|
<Authors>NGnius</Authors>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageProjectUrl>https://git.exmods.org/NGnius/Pixi</PackageProjectUrl>
|
<PackageProjectUrl>https://git.exmods.org/NGnius/Pixi</PackageProjectUrl>
|
||||||
|
@ -863,4 +863,13 @@
|
||||||
<EmbeddedResource Include="cubes-id.json" />
|
<EmbeddedResource Include="cubes-id.json" />
|
||||||
<EmbeddedResource Include="blueprints.json" />
|
<EmbeddedResource Include="blueprints.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ILRepack" Version="2.0.18" />
|
||||||
|
<PackageReference Include="Melanchall.DryWetMidi" Version="5.1.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Target Name="StaticLinkMergeAfterBuild" AfterTargets="Build">
|
||||||
|
<Exec Command="$(PkgILRepack)\tools\ILRepack.exe /ndebug /out:bin\$(Configuration)\net472\Pixi.dll bin\$(Configuration)\net472\Pixi.dll bin\$(Configuration)\net472\Melanchall.DryWetMidi.dll" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -8,7 +8,7 @@ using Unity.Mathematics; // float3
|
||||||
|
|
||||||
using IllusionPlugin;
|
using IllusionPlugin;
|
||||||
using GamecraftModdingAPI.Utility;
|
using GamecraftModdingAPI.Utility;
|
||||||
|
using Pixi.Audio;
|
||||||
using Pixi.Common;
|
using Pixi.Common;
|
||||||
using Pixi.Images;
|
using Pixi.Images;
|
||||||
using Pixi.Robots;
|
using Pixi.Robots;
|
||||||
|
@ -54,6 +54,8 @@ namespace Pixi
|
||||||
// Development functionality
|
// Development functionality
|
||||||
RobotCommands.CreatePartDumpCommand();
|
RobotCommands.CreatePartDumpCommand();
|
||||||
#endif
|
#endif
|
||||||
|
// Audio functionality
|
||||||
|
root.Inject(new MidiImporter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -99,6 +99,8 @@ Robot parsing uses information from [RobocraftAssembler](https://github.com/dddo
|
||||||
|
|
||||||
Gamecraft interactions use the [GamecraftModdingAPI](https://git.exmods.org/modtainers/GamecraftModdingAPI).
|
Gamecraft interactions use the [GamecraftModdingAPI](https://git.exmods.org/modtainers/GamecraftModdingAPI).
|
||||||
|
|
||||||
|
MIDI file processing uses an integrated copy of melanchall's [DryWetMidi](https://github.com/melanchall/drywetmidi) library, licensed under the [MIT License](https://github.com/melanchall/drywetmidi/blob/develop/LICENSE).
|
||||||
|
|
||||||
Thanks to **TheGreenGoblin** and their Python app for converting images to coloured square characters, which inspired the PixiConsole and PixiText commands.
|
Thanks to **TheGreenGoblin** and their Python app for converting images to coloured square characters, which inspired the PixiConsole and PixiText commands.
|
||||||
|
|
||||||
Thanks to **Mr. Rotor** for all of the Robocraft blocks used in the PixiBot and PixiBotFile commands.
|
Thanks to **Mr. Rotor** for all of the Robocraft blocks used in the PixiBot and PixiBotFile commands.
|
||||||
|
|
Loading…
Reference in a new issue