Optimise midi import block placement
This commit is contained in:
parent
742bcf25ef
commit
d2a2ce52f0
2 changed files with 195 additions and 34 deletions
|
@ -1,7 +1,106 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
using Melanchall.DryWetMidi.Common;
|
||||
|
||||
namespace Pixi.Audio
|
||||
{
|
||||
public static class AudioTools
|
||||
{
|
||||
private static Dictionary<byte, byte> programMap = null;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte TrackType(FourBitNumber channel)
|
||||
{
|
||||
return TrackType((byte) channel);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte TrackType(byte channel)
|
||||
{
|
||||
if (programMap.ContainsKey(channel)) return programMap[channel];
|
||||
#if DEBUG
|
||||
Logging.MetaLog($"Using default value (piano) for channel number {channel}");
|
||||
#endif
|
||||
return 5; // Piano
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float VelocityToVolume(SevenBitNumber velocity)
|
||||
{
|
||||
// faster key hit means louder note
|
||||
return 100f * velocity / ((float) SevenBitNumber.MaxValue + 1f);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void GenerateProgramMap()
|
||||
{
|
||||
programMap = new Dictionary<byte, byte>
|
||||
{
|
||||
{0, 5 /* Piano */},
|
||||
{1, 5},
|
||||
{2, 5},
|
||||
{3, 5},
|
||||
{4, 5},
|
||||
{5, 5},
|
||||
{6, 5},
|
||||
{7, 5},
|
||||
{8, 0 /* Kick Drum */},
|
||||
{9, 0},
|
||||
{10, 0},
|
||||
{11, 0},
|
||||
{12, 0},
|
||||
{13, 0},
|
||||
{14, 0},
|
||||
{15, 0},
|
||||
{24, 6 /* Guitar 1 (Acoustic) */},
|
||||
{25, 6},
|
||||
{26, 6},
|
||||
{27, 6},
|
||||
{28, 6},
|
||||
{29, 7 /* Guitar 2 (Dirty Electric) */},
|
||||
{30, 7},
|
||||
{32, 6},
|
||||
{33, 6},
|
||||
{34, 6},
|
||||
{35, 6},
|
||||
{36, 6},
|
||||
{37, 6},
|
||||
{38, 6},
|
||||
{39, 6},
|
||||
{56, 8 /* Trumpet */}, // basically all brass & reeds are trumpets... that's how music works right?
|
||||
{57, 8},
|
||||
{58, 8},
|
||||
{59, 8},
|
||||
{60, 8},
|
||||
{61, 8},
|
||||
{62, 8},
|
||||
{63, 8},
|
||||
{64, 8},
|
||||
{65, 8},
|
||||
{66, 8},
|
||||
{67, 8},
|
||||
{68, 8},
|
||||
{69, 8}, // Nice
|
||||
{70, 8},
|
||||
{71, 8},
|
||||
{72, 8},
|
||||
{73, 8},
|
||||
{74, 8},
|
||||
{75, 8},
|
||||
{76, 8},
|
||||
{77, 8},
|
||||
{78, 8},
|
||||
{79, 8},
|
||||
{112, 0},
|
||||
{113, 0},
|
||||
{114, 0},
|
||||
{115, 0},
|
||||
{116, 0},
|
||||
{117, 4 /* Tom Drum */},
|
||||
{118, 4},
|
||||
{119, 3 /* Open High Hat */},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using GamecraftModdingAPI;
|
|||
using GamecraftModdingAPI.Players;
|
||||
using GamecraftModdingAPI.Blocks;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
using Melanchall.DryWetMidi.Common;
|
||||
using Melanchall.DryWetMidi.Core;
|
||||
using Melanchall.DryWetMidi.Devices;
|
||||
using Melanchall.DryWetMidi.Interaction;
|
||||
|
@ -27,10 +28,18 @@ namespace Pixi.Audio
|
|||
public static bool ThreeDee = false;
|
||||
|
||||
public static float Spread = 1f;
|
||||
|
||||
public static byte Key = 0;
|
||||
|
||||
public MidiImporter()
|
||||
{
|
||||
AudioTools.GenerateProgramMap();
|
||||
}
|
||||
|
||||
public bool Qualifies(string name)
|
||||
{
|
||||
return name.EndsWith(".mid", StringComparison.InvariantCultureIgnoreCase);
|
||||
return name.EndsWith(".mid", StringComparison.InvariantCultureIgnoreCase)
|
||||
|| name.EndsWith(".midi", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public BlockJsonInfo[] Import(string name)
|
||||
|
@ -38,31 +47,54 @@ namespace Pixi.Audio
|
|||
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];
|
||||
BlockJsonInfo[] blocks = new BlockJsonInfo[(midi.GetNotes().Count() * 2) + 3];
|
||||
List<BlockJsonInfo> blocksToBuild = new List<BlockJsonInfo>();
|
||||
#if DEBUG
|
||||
// test (for faster, but incomplete, imports)
|
||||
if (blocks.Length > 102) blocks = new BlockJsonInfo[102];
|
||||
if (blocks.Length > 103) blocks = new BlockJsonInfo[103];
|
||||
#endif
|
||||
// convert Midi notes to sfx blocks
|
||||
Dictionary<long, uint> breadthCache = new Dictionary<long, uint>();
|
||||
uint count = 0;
|
||||
Dictionary<long, uint> depthCache = new Dictionary<long, uint>();
|
||||
HashSet<long> timerCache = new HashSet<long>();
|
||||
//uint count = 0;
|
||||
float zdepth = 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))
|
||||
float breadth = 0f;
|
||||
if (!timerCache.Contains(microTime))
|
||||
{
|
||||
breadth += breadthCache[microTime]++;
|
||||
depthCache[microTime] = (uint)++zdepth;
|
||||
breadthCache[microTime] = 1;
|
||||
timerCache.Add(microTime);
|
||||
blocksToBuild.Add(new BlockJsonInfo
|
||||
{
|
||||
name = GamecraftModdingAPI.Blocks.BlockIDs.Timer.ToString(),
|
||||
position = new float[] { breadth * 0.2f * Spread, 2 * 0.2f, zdepth * 0.2f * Spread},
|
||||
rotation = new float[] { 0, 0, 0},
|
||||
color = new float[] { -1, -1, -1},
|
||||
scale = new float[] { 1, 1, 1},
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
breadthCache[microTime] = 1;
|
||||
zdepth = depthCache[microTime]; // remember the z-position of notes played at the same moment (so they can be placed adjacent to each other)
|
||||
breadth += breadthCache[microTime]++; // if multiple notes exist for a given time, place them beside each other on the x-axis
|
||||
}
|
||||
blocksToBuild.Add(new BlockJsonInfo
|
||||
{
|
||||
name = GamecraftModdingAPI.Blocks.BlockIDs.SFXBlockInstrument.ToString(),
|
||||
position = new float[] { breadth * 0.2f * Spread, 1 * 0.2f, zdepth * 0.2f * Spread},
|
||||
rotation = new float[] { 0, 0, 0},
|
||||
color = new float[] { -1, -1, -1},
|
||||
scale = new float[] { 1, 1, 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},
|
||||
position = new float[] { breadth * 0.2f * Spread, 2 * 0.2f, zdepth * 0.2f * Spread},
|
||||
rotation = new float[] { 0, 0, 0},
|
||||
color = new float[] { -1, -1, -1},
|
||||
scale = new float[] { 1, 1, 1},
|
||||
|
@ -71,36 +103,39 @@ namespace Pixi.Audio
|
|||
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},
|
||||
position = new float[] { breadth * 0.2f * Spread, 1 * 0.2f, zdepth * 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
|
||||
count++;*/
|
||||
}
|
||||
// playback IO (reset & play)
|
||||
blocks[count] = new BlockJsonInfo
|
||||
blocksToBuild.Add(new BlockJsonInfo
|
||||
{
|
||||
name = GamecraftModdingAPI.Blocks.BlockIDs.SimpleConnector.ToString(),
|
||||
position = new float[] { -0.2f, 3 * 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 stop)
|
||||
blocksToBuild.Add(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
|
||||
}); // stop is middle (placed above reset)
|
||||
blocksToBuild.Add(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;
|
||||
}); // reset is last (placed below stop)
|
||||
return blocksToBuild.ToArray();
|
||||
}
|
||||
|
||||
public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks)
|
||||
|
@ -116,30 +151,57 @@ namespace Pixi.Audio
|
|||
public void PostProcess(string name, ref Block[] blocks)
|
||||
{
|
||||
// playback IO
|
||||
LogicGate startConnector = blocks[blocks.Length - 2].Specialise<LogicGate>();
|
||||
LogicGate startConnector = blocks[blocks.Length - 3].Specialise<LogicGate>();
|
||||
LogicGate stopConnector = blocks[blocks.Length - 2].Specialise<LogicGate>();
|
||||
LogicGate resetConnector = blocks[blocks.Length - 1].Specialise<LogicGate>();
|
||||
uint count = 0;
|
||||
// generate channel data
|
||||
byte[] channelPrograms = new byte[16];
|
||||
for (byte i = 0; i < channelPrograms.Length; i++) // init array
|
||||
{
|
||||
channelPrograms[i] = 5; // Piano
|
||||
}
|
||||
|
||||
foreach (TimedEvent e in openFiles[name].GetTimedEvents())
|
||||
{
|
||||
if (e.Event.EventType == MidiEventType.ProgramChange)
|
||||
{
|
||||
ProgramChangeEvent pce = (ProgramChangeEvent) e.Event;
|
||||
channelPrograms[pce.Channel] = AudioTools.TrackType(pce.ProgramNumber);
|
||||
#if DEBUG
|
||||
Logging.MetaLog($"Detected channel {pce.Channel} as program {pce.ProgramNumber} (index {channelPrograms[pce.Channel]})");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
Timer t = null;
|
||||
//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++;
|
||||
while (blocks[count].Type == BlockIDs.Timer)
|
||||
{
|
||||
// set timing info
|
||||
#if DEBUG
|
||||
Logging.Log($"Handling Timer for notes at {n.TimeAs<MetricTimeSpan>(openFiles[name].GetTempoMap()).TotalMicroseconds * 0.000001f}s");
|
||||
#endif
|
||||
t = blocks[count].Specialise<Timer>();
|
||||
t.Start = 0;
|
||||
t.End = 0.01f + 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.Pitch = n.NoteNumber - 60 + Key; // In MIDI, 60 is middle C, but GC uses 0 for middle C
|
||||
sfx.TrackIndex = channelPrograms[n.Channel];
|
||||
sfx.Is3D = ThreeDee;
|
||||
sfx.Volume = AudioTools.VelocityToVolume(n.Velocity);
|
||||
count++;
|
||||
// connect wires
|
||||
if (t == null) continue; // this should never happen
|
||||
t.Connect(0, sfx, 0);
|
||||
startConnector.Connect(0, t, 0);
|
||||
stopConnector.Connect(0, t, 1);
|
||||
resetConnector.Connect(0, t, 2);
|
||||
#if DEBUG
|
||||
// test (for faster, but incomplete, imports)
|
||||
if (count >= 100) break;
|
||||
#endif
|
||||
}
|
||||
openFiles.Remove(name);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue