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>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Version>1.0.1</Version>
|
||||
<Version>1.1.0</Version>
|
||||
<Authors>NGnius</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://git.exmods.org/NGnius/Pixi</PackageProjectUrl>
|
||||
|
@ -863,4 +863,13 @@
|
|||
<EmbeddedResource Include="cubes-id.json" />
|
||||
<EmbeddedResource Include="blueprints.json" />
|
||||
</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>
|
||||
|
|
|
@ -8,7 +8,7 @@ using Unity.Mathematics; // float3
|
|||
|
||||
using IllusionPlugin;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
using Pixi.Audio;
|
||||
using Pixi.Common;
|
||||
using Pixi.Images;
|
||||
using Pixi.Robots;
|
||||
|
@ -54,6 +54,8 @@ namespace Pixi
|
|||
// Development functionality
|
||||
RobotCommands.CreatePartDumpCommand();
|
||||
#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).
|
||||
|
||||
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 **Mr. Rotor** for all of the Robocraft blocks used in the PixiBot and PixiBotFile commands.
|
||||
|
|
Loading…
Reference in a new issue