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
|
namespace Pixi.Audio
|
||||||
{
|
{
|
||||||
public static class AudioTools
|
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.Players;
|
||||||
using GamecraftModdingAPI.Blocks;
|
using GamecraftModdingAPI.Blocks;
|
||||||
using GamecraftModdingAPI.Utility;
|
using GamecraftModdingAPI.Utility;
|
||||||
|
using Melanchall.DryWetMidi.Common;
|
||||||
using Melanchall.DryWetMidi.Core;
|
using Melanchall.DryWetMidi.Core;
|
||||||
using Melanchall.DryWetMidi.Devices;
|
using Melanchall.DryWetMidi.Devices;
|
||||||
using Melanchall.DryWetMidi.Interaction;
|
using Melanchall.DryWetMidi.Interaction;
|
||||||
|
@ -28,9 +29,17 @@ namespace Pixi.Audio
|
||||||
|
|
||||||
public static float Spread = 1f;
|
public static float Spread = 1f;
|
||||||
|
|
||||||
|
public static byte Key = 0;
|
||||||
|
|
||||||
|
public MidiImporter()
|
||||||
|
{
|
||||||
|
AudioTools.GenerateProgramMap();
|
||||||
|
}
|
||||||
|
|
||||||
public bool Qualifies(string name)
|
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)
|
public BlockJsonInfo[] Import(string name)
|
||||||
|
@ -38,31 +47,54 @@ namespace Pixi.Audio
|
||||||
MidiFile midi = MidiFile.Read(name);
|
MidiFile midi = MidiFile.Read(name);
|
||||||
openFiles[name] = midi;
|
openFiles[name] = midi;
|
||||||
Logging.MetaLog($"Found {midi.GetNotes().Count()} notes over {midi.GetDuration<MidiTimeSpan>().TimeSpan} time units");
|
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
|
#if DEBUG
|
||||||
// test (for faster, but incomplete, imports)
|
// test (for faster, but incomplete, imports)
|
||||||
if (blocks.Length > 102) blocks = new BlockJsonInfo[102];
|
if (blocks.Length > 103) blocks = new BlockJsonInfo[103];
|
||||||
#endif
|
#endif
|
||||||
// convert Midi notes to sfx blocks
|
// convert Midi notes to sfx blocks
|
||||||
Dictionary<long, uint> breadthCache = new Dictionary<long, uint>();
|
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())
|
foreach (Note n in midi.GetNotes())
|
||||||
{
|
{
|
||||||
// even blocks are counters,
|
|
||||||
long microTime = n.TimeAs<MetricTimeSpan>(midi.GetTempoMap()).TotalMicroseconds;
|
long microTime = n.TimeAs<MetricTimeSpan>(midi.GetTempoMap()).TotalMicroseconds;
|
||||||
float breadth = 1f;
|
float breadth = 0f;
|
||||||
if (breadthCache.ContainsKey(microTime))
|
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
|
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
|
blocks[count] = new BlockJsonInfo
|
||||||
{
|
{
|
||||||
name = GamecraftModdingAPI.Blocks.BlockIDs.Timer.ToString(),
|
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},
|
rotation = new float[] { 0, 0, 0},
|
||||||
color = new float[] { -1, -1, -1},
|
color = new float[] { -1, -1, -1},
|
||||||
scale = new float[] { 1, 1, 1},
|
scale = new float[] { 1, 1, 1},
|
||||||
|
@ -71,36 +103,39 @@ namespace Pixi.Audio
|
||||||
blocks[count] = new BlockJsonInfo
|
blocks[count] = new BlockJsonInfo
|
||||||
{
|
{
|
||||||
name = GamecraftModdingAPI.Blocks.BlockIDs.SFXBlockInstrument.ToString(),
|
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},
|
rotation = new float[] { 0, 0, 0},
|
||||||
color = new float[] { -1, -1, -1},
|
color = new float[] { -1, -1, -1},
|
||||||
scale = new float[] { 1, 1, 1},
|
scale = new float[] { 1, 1, 1},
|
||||||
};
|
};
|
||||||
count++;
|
count++;*/
|
||||||
#if DEBUG
|
|
||||||
// test (for faster, but incomplete, imports)
|
|
||||||
if (count >= 100) break;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
// playback IO (reset & play)
|
// 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(),
|
name = GamecraftModdingAPI.Blocks.BlockIDs.SimpleConnector.ToString(),
|
||||||
position = new float[] { -0.2f, 2 * 0.2f, 0},
|
position = new float[] { -0.2f, 2 * 0.2f, 0},
|
||||||
rotation = new float[] { 0, 0, 0},
|
rotation = new float[] { 0, 0, 0},
|
||||||
color = new float[] { -1, -1, -1},
|
color = new float[] { -1, -1, -1},
|
||||||
scale = new float[] { 1, 1, 1},
|
scale = new float[] { 1, 1, 1},
|
||||||
}; // play is second last (placed above reset)
|
}); // stop is middle (placed above reset)
|
||||||
count++;
|
blocksToBuild.Add(new BlockJsonInfo
|
||||||
blocks[count] = new BlockJsonInfo
|
|
||||||
{
|
{
|
||||||
name = GamecraftModdingAPI.Blocks.BlockIDs.SimpleConnector.ToString(),
|
name = GamecraftModdingAPI.Blocks.BlockIDs.SimpleConnector.ToString(),
|
||||||
position = new float[] { -0.2f, 1 * 0.2f, 0},
|
position = new float[] { -0.2f, 1 * 0.2f, 0},
|
||||||
rotation = new float[] { 0, 0, 0},
|
rotation = new float[] { 0, 0, 0},
|
||||||
color = new float[] { -1, -1, -1},
|
color = new float[] { -1, -1, -1},
|
||||||
scale = new float[] { 1, 1, 1},
|
scale = new float[] { 1, 1, 1},
|
||||||
}; // reset is last (placed below play)
|
}); // reset is last (placed below stop)
|
||||||
return blocks;
|
return blocksToBuild.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks)
|
public void PreProcess(string name, ref ProcessedVoxelObjectNotation[] blocks)
|
||||||
|
@ -116,30 +151,57 @@ namespace Pixi.Audio
|
||||||
public void PostProcess(string name, ref Block[] blocks)
|
public void PostProcess(string name, ref Block[] blocks)
|
||||||
{
|
{
|
||||||
// playback IO
|
// 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>();
|
LogicGate resetConnector = blocks[blocks.Length - 1].Specialise<LogicGate>();
|
||||||
uint count = 0;
|
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())
|
foreach (Note n in openFiles[name].GetNotes())
|
||||||
|
{
|
||||||
|
while (blocks[count].Type == BlockIDs.Timer)
|
||||||
{
|
{
|
||||||
// set timing info
|
// set timing info
|
||||||
Timer t = blocks[count].Specialise<Timer>();
|
#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.Start = 0;
|
||||||
t.End = n.TimeAs<MetricTimeSpan>(openFiles[name].GetTempoMap()).TotalMicroseconds * 0.000001f;
|
t.End = 0.01f + n.TimeAs<MetricTimeSpan>(openFiles[name].GetTempoMap()).TotalMicroseconds * 0.000001f;
|
||||||
count++;
|
count++;
|
||||||
|
}
|
||||||
// set notes info
|
// set notes info
|
||||||
SfxBlock sfx = blocks[count].Specialise<SfxBlock>();
|
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.Pitch = n.NoteNumber - 60 + Key; // In MIDI, 60 is middle C, but GC uses 0 for middle C
|
||||||
sfx.TrackIndex = 5; // Piano
|
sfx.TrackIndex = channelPrograms[n.Channel];
|
||||||
sfx.Is3D = ThreeDee;
|
sfx.Is3D = ThreeDee;
|
||||||
|
sfx.Volume = AudioTools.VelocityToVolume(n.Velocity);
|
||||||
count++;
|
count++;
|
||||||
// connect wires
|
// connect wires
|
||||||
|
if (t == null) continue; // this should never happen
|
||||||
t.Connect(0, sfx, 0);
|
t.Connect(0, sfx, 0);
|
||||||
startConnector.Connect(0, t, 0);
|
startConnector.Connect(0, t, 0);
|
||||||
|
stopConnector.Connect(0, t, 1);
|
||||||
resetConnector.Connect(0, t, 2);
|
resetConnector.Connect(0, t, 2);
|
||||||
#if DEBUG
|
|
||||||
// test (for faster, but incomplete, imports)
|
|
||||||
if (count >= 100) break;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
openFiles.Remove(name);
|
openFiles.Remove(name);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue