Merge pull request #10069 from penev92/soundLoader
Add plumbing for mod-defined sound loaders
This commit is contained in:
@@ -36,16 +36,18 @@ namespace OpenRA.FileFormats
|
||||
Chunk c;
|
||||
c.CompressedSize = s.ReadUInt16();
|
||||
c.OutputSize = s.ReadUInt16();
|
||||
|
||||
if (s.ReadUInt32() != 0xdeaf)
|
||||
throw new InvalidDataException("Chunk header is bogus");
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AudLoader
|
||||
public class AudLoader : ISoundLoader
|
||||
{
|
||||
static int[] indexAdjust = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
static int[] stepTable =
|
||||
static readonly int ExpectedSampleRate = 22050;
|
||||
static readonly int[] IndexAdjust = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
static readonly int[] StepTable =
|
||||
{
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 16,
|
||||
17, 19, 21, 23, 25, 28, 31, 34, 37,
|
||||
@@ -64,14 +66,14 @@ namespace OpenRA.FileFormats
|
||||
var sb = (b & 8) != 0;
|
||||
b &= 7;
|
||||
|
||||
var delta = (stepTable[index] * b) / 4 + stepTable[index] / 8;
|
||||
var delta = (StepTable[index] * b) / 4 + StepTable[index] / 8;
|
||||
if (sb) delta = -delta;
|
||||
|
||||
current += delta;
|
||||
if (current > short.MaxValue) current = short.MaxValue;
|
||||
if (current < short.MinValue) current = short.MinValue;
|
||||
|
||||
index += indexAdjust[b];
|
||||
index += IndexAdjust[b];
|
||||
if (index < 0) index = 0;
|
||||
if (index > 88) index = 88;
|
||||
|
||||
@@ -117,13 +119,24 @@ namespace OpenRA.FileFormats
|
||||
return samples / sampleRate;
|
||||
}
|
||||
|
||||
public static byte[] LoadSound(Stream s)
|
||||
public static bool LoadSound(Stream s, out byte[] rawData)
|
||||
{
|
||||
/*var sampleRate =*/ s.ReadUInt16();
|
||||
rawData = null;
|
||||
|
||||
var sampleRate = s.ReadUInt16();
|
||||
var dataSize = s.ReadInt32();
|
||||
var outputSize = s.ReadInt32();
|
||||
/*var flags = (SoundFlags)*/ s.ReadByte();
|
||||
/*var format = (SoundFormat)*/ s.ReadByte();
|
||||
var readFlag = s.ReadByte();
|
||||
var readFormat = s.ReadByte();
|
||||
|
||||
if (sampleRate != ExpectedSampleRate)
|
||||
return false;
|
||||
|
||||
if (!Enum.IsDefined(typeof(SoundFlags), readFlag))
|
||||
return false;
|
||||
|
||||
if (!Enum.IsDefined(typeof(SoundFormat), readFormat))
|
||||
return false;
|
||||
|
||||
var output = new byte[outputSize];
|
||||
var offset = 0;
|
||||
@@ -153,7 +166,37 @@ namespace OpenRA.FileFormats
|
||||
dataSize -= 8 + chunk.CompressedSize;
|
||||
}
|
||||
|
||||
return output;
|
||||
rawData = output;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels, out int sampleBits,
|
||||
out int sampleRate)
|
||||
{
|
||||
channels = sampleBits = sampleRate = 0;
|
||||
|
||||
try
|
||||
{
|
||||
if (!LoadSound(stream, out rawData))
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// LoadSound() will check if the stream is in a format that this parser supports.
|
||||
// If not, it will simply return false so we know we can't use it. If it is, it will start
|
||||
// parsing the data without any further failsafes, which means that it will crash on corrupted files
|
||||
// (that end prematurely or otherwise don't conform to the specifications despite the headers being OK).
|
||||
Log.Write("debug", "Failed to parse AUD file {0}. Error message:".F(fileName));
|
||||
Log.Write("debug", e.ToString());
|
||||
rawData = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
channels = 1;
|
||||
sampleBits = 16;
|
||||
sampleRate = ExpectedSampleRate;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,48 +13,53 @@ using System.IO;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class WavLoader
|
||||
public class WavLoader : ISoundLoader
|
||||
{
|
||||
public readonly int FileSize;
|
||||
public readonly string Format;
|
||||
public int FileSize;
|
||||
public string Format;
|
||||
|
||||
public readonly int FmtChunkSize;
|
||||
public readonly int AudioFormat;
|
||||
public readonly int Channels;
|
||||
public readonly int SampleRate;
|
||||
public readonly int ByteRate;
|
||||
public readonly int BlockAlign;
|
||||
public readonly int BitsPerSample;
|
||||
public int FmtChunkSize;
|
||||
public int AudioFormat;
|
||||
public int Channels;
|
||||
public int SampleRate;
|
||||
public int ByteRate;
|
||||
public int BlockAlign;
|
||||
public int BitsPerSample;
|
||||
|
||||
public readonly int UncompressedSize;
|
||||
public readonly int DataSize;
|
||||
public readonly byte[] RawOutput;
|
||||
public int UncompressedSize;
|
||||
public int DataSize;
|
||||
public byte[] RawOutput;
|
||||
|
||||
public enum WaveType { Pcm = 0x1, ImaAdpcm = 0x11 }
|
||||
public static WaveType Type { get; private set; }
|
||||
|
||||
public WavLoader(Stream s)
|
||||
bool LoadSound(Stream s)
|
||||
{
|
||||
var type = s.ReadASCII(4);
|
||||
if (type != "RIFF")
|
||||
return false;
|
||||
|
||||
FileSize = s.ReadInt32();
|
||||
Format = s.ReadASCII(4);
|
||||
if (Format != "WAVE")
|
||||
return false;
|
||||
|
||||
while (s.Position < s.Length)
|
||||
{
|
||||
if ((s.Position & 1) == 1)
|
||||
s.ReadByte(); // Alignment
|
||||
|
||||
var type = s.ReadASCII(4);
|
||||
type = s.ReadASCII(4);
|
||||
switch (type)
|
||||
{
|
||||
case "RIFF":
|
||||
FileSize = s.ReadInt32();
|
||||
Format = s.ReadASCII(4);
|
||||
if (Format != "WAVE")
|
||||
throw new NotSupportedException("Not a canonical WAVE file.");
|
||||
break;
|
||||
case "fmt ":
|
||||
FmtChunkSize = s.ReadInt32();
|
||||
AudioFormat = s.ReadInt16();
|
||||
Type = (WaveType)AudioFormat;
|
||||
|
||||
if (Type != WaveType.Pcm && Type != WaveType.ImaAdpcm)
|
||||
throw new NotSupportedException("Compression type is not supported.");
|
||||
|
||||
Channels = s.ReadInt16();
|
||||
SampleRate = s.ReadInt32();
|
||||
ByteRate = s.ReadInt32();
|
||||
@@ -91,6 +96,8 @@ namespace OpenRA.FileFormats
|
||||
RawOutput = DecodeImaAdpcmData();
|
||||
BitsPerSample = 16;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static float WaveLength(Stream s)
|
||||
@@ -176,5 +183,35 @@ namespace OpenRA.FileFormats
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public bool TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels,
|
||||
out int sampleBits, out int sampleRate)
|
||||
{
|
||||
rawData = null;
|
||||
channels = sampleBits = sampleRate = 0;
|
||||
|
||||
try
|
||||
{
|
||||
if (!LoadSound(stream))
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// LoadSound() will check if the stream is in a format that this parser supports.
|
||||
// If not, it will simply return false so we know we can't use it. If it is, it will start
|
||||
// parsing the data without any further failsafes, which means that it will crash on corrupted files
|
||||
// (that end prematurely or otherwise don't conform to the specifications despite the headers being OK).
|
||||
Log.Write("debug", "Failed to parse WAV file {0}. Error message:".F(fileName));
|
||||
Log.Write("debug", e.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
rawData = RawOutput;
|
||||
channels = Channels;
|
||||
sampleBits = BitsPerSample;
|
||||
sampleRate = SampleRate;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,10 +293,11 @@ namespace OpenRA
|
||||
Settings.Game.Mod = mod;
|
||||
|
||||
Sound.StopVideo();
|
||||
Sound.Initialize();
|
||||
|
||||
ModData = new ModData(mod, !Settings.Server.Dedicated);
|
||||
|
||||
Sound.Initialize();
|
||||
|
||||
using (new PerfTimer("LoadMaps"))
|
||||
ModData.MapCache.LoadMaps();
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace OpenRA.Graphics
|
||||
if (loader.TryParseSprite(stream, out frames))
|
||||
return frames;
|
||||
|
||||
throw new InvalidDataException(filename + " is not a valid sprite file");
|
||||
throw new InvalidDataException(filename + " is not a valid sprite file!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,12 +49,14 @@ namespace OpenRA
|
||||
public readonly Dictionary<string, string> RequiresMods;
|
||||
public readonly Dictionary<string, Pair<string, int>> Fonts;
|
||||
|
||||
public readonly string[] SoundFormats = { };
|
||||
public readonly string[] SpriteFormats = { };
|
||||
|
||||
readonly string[] reservedModuleNames = { "Metadata", "Folders", "MapFolders", "Packages", "Rules",
|
||||
"Sequences", "VoxelSequences", "Cursors", "Chrome", "Assemblies", "ChromeLayout", "Weapons",
|
||||
"Voices", "Notifications", "Music", "Translations", "TileSets", "ChromeMetrics", "Missions",
|
||||
"ServerTraits", "LoadScreen", "LobbyDefaults", "Fonts", "SupportsMapsFrom", "SpriteFormats", "RequiresMods" };
|
||||
"ServerTraits", "LoadScreen", "LobbyDefaults", "Fonts", "SupportsMapsFrom", "SoundFormats", "SpriteFormats",
|
||||
"RequiresMods" };
|
||||
|
||||
readonly TypeDictionary modules = new TypeDictionary();
|
||||
readonly Dictionary<string, MiniYaml> yaml;
|
||||
@@ -113,6 +115,9 @@ namespace OpenRA
|
||||
|
||||
MapCompatibility = compat.ToArray();
|
||||
|
||||
if (yaml.ContainsKey("SoundFormats"))
|
||||
SoundFormats = FieldLoader.GetValue<string[]>("SoundFormats", yaml["SoundFormats"].Value);
|
||||
|
||||
if (yaml.ContainsKey("SpriteFormats"))
|
||||
SpriteFormats = FieldLoader.GetValue<string[]>("SpriteFormats", yaml["SpriteFormats"].Value);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace OpenRA
|
||||
public readonly ObjectCreator ObjectCreator;
|
||||
public readonly WidgetLoader WidgetLoader;
|
||||
public readonly MapCache MapCache;
|
||||
public readonly ISoundLoader[] SoundLoaders;
|
||||
public readonly ISpriteLoader[] SpriteLoaders;
|
||||
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
|
||||
public readonly RulesetCache RulesetCache;
|
||||
@@ -60,17 +61,8 @@ namespace OpenRA
|
||||
RulesetCache.LoadingProgress += HandleLoadingProgress;
|
||||
MapCache = new MapCache(this);
|
||||
|
||||
var spriteLoaders = new List<ISpriteLoader>();
|
||||
foreach (var format in Manifest.SpriteFormats)
|
||||
{
|
||||
var loader = ObjectCreator.FindType(format + "Loader");
|
||||
if (loader == null || !loader.GetInterfaces().Contains(typeof(ISpriteLoader)))
|
||||
throw new InvalidOperationException("Unable to find a sprite loader for type '{0}'.".F(format));
|
||||
|
||||
spriteLoaders.Add((ISpriteLoader)ObjectCreator.CreateBasic(loader));
|
||||
}
|
||||
|
||||
SpriteLoaders = spriteLoaders.ToArray();
|
||||
SoundLoaders = GetLoaders<ISoundLoader>(Manifest.SoundFormats, "sound");
|
||||
SpriteLoaders = GetLoaders<ISpriteLoader>(Manifest.SpriteFormats, "sprite");
|
||||
|
||||
var sequenceFormat = Manifest.Get<SpriteSequenceFormat>();
|
||||
var sequenceLoader = ObjectCreator.FindType(sequenceFormat.Type + "Loader");
|
||||
@@ -113,6 +105,21 @@ namespace OpenRA
|
||||
CursorProvider = new CursorProvider(this);
|
||||
}
|
||||
|
||||
TLoader[] GetLoaders<TLoader>(IEnumerable<string> formats, string name)
|
||||
{
|
||||
var loaders = new List<TLoader>();
|
||||
foreach (var format in formats)
|
||||
{
|
||||
var loader = ObjectCreator.FindType(format + "Loader");
|
||||
if (loader == null || !loader.GetInterfaces().Contains(typeof(TLoader)))
|
||||
throw new InvalidOperationException("Unable to find a {0} loader for type '{1}'.".F(name, format));
|
||||
|
||||
loaders.Add((TLoader)ObjectCreator.CreateBasic(loader));
|
||||
}
|
||||
|
||||
return loaders.ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<string> Languages { get; private set; }
|
||||
|
||||
void LoadTranslations(Map map)
|
||||
|
||||
@@ -9,14 +9,19 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public interface ISoundLoader
|
||||
{
|
||||
bool TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels, out int sampleBits, out int sampleRate);
|
||||
}
|
||||
|
||||
public sealed class Sound : IDisposable
|
||||
{
|
||||
readonly ISoundEngine soundEngine;
|
||||
@@ -51,22 +56,21 @@ namespace OpenRA
|
||||
return null;
|
||||
}
|
||||
|
||||
if (filename.ToLowerInvariant().EndsWith("wav"))
|
||||
using (var s = Game.ModData.ModFiles.Open(filename))
|
||||
return LoadWave(new WavLoader(s));
|
||||
using (var stream = Game.ModData.ModFiles.Open(filename))
|
||||
{
|
||||
byte[] rawData;
|
||||
int channels;
|
||||
int sampleBits;
|
||||
int sampleRate;
|
||||
foreach (var loader in Game.ModData.SoundLoaders)
|
||||
{
|
||||
stream.Position = 0;
|
||||
if (loader.TryParseSound(stream, filename, out rawData, out channels, out sampleBits, out sampleRate))
|
||||
return soundEngine.AddSoundSourceFromMemory(rawData, channels, sampleBits, sampleRate);
|
||||
}
|
||||
|
||||
using (var s = Game.ModData.ModFiles.Open(filename))
|
||||
return LoadSoundRaw(AudLoader.LoadSound(s), 1, 16, 22050);
|
||||
}
|
||||
|
||||
ISoundSource LoadWave(WavLoader wave)
|
||||
{
|
||||
return soundEngine.AddSoundSourceFromMemory(wave.RawOutput, wave.Channels, wave.BitsPerSample, wave.SampleRate);
|
||||
}
|
||||
|
||||
ISoundSource LoadSoundRaw(byte[] rawData, int channels, int sampleBits, int sampleRate)
|
||||
{
|
||||
return soundEngine.AddSoundSourceFromMemory(rawData, channels, sampleBits, sampleRate);
|
||||
throw new InvalidDataException(filename + " is not a valid sound file!");
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
@@ -121,7 +125,7 @@ namespace OpenRA
|
||||
|
||||
public void PlayVideo(byte[] raw, int channels, int sampleBits, int sampleRate)
|
||||
{
|
||||
rawSource = LoadSoundRaw(raw, channels, sampleBits, sampleRate);
|
||||
rawSource = soundEngine.AddSoundSourceFromMemory(raw, channels, sampleBits, sampleRate);
|
||||
video = soundEngine.Play2D(rawSource, false, true, WPos.Zero, InternalSoundVolume, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ Fonts:
|
||||
|
||||
LobbyDefaults:
|
||||
|
||||
SoundFormats:
|
||||
|
||||
SpriteFormats:
|
||||
|
||||
SpriteSequenceFormat: DefaultSpriteSequence
|
||||
|
||||
@@ -214,6 +214,8 @@ MapGrid:
|
||||
|
||||
SupportsMapsFrom: cnc
|
||||
|
||||
SoundFormats: Aud, Wav
|
||||
|
||||
SpriteFormats: ShpTD, TmpTD, ShpTS, TmpRA
|
||||
|
||||
SpriteSequenceFormat: TilesetSpecificSpriteSequence
|
||||
|
||||
@@ -195,6 +195,8 @@ Missions:
|
||||
|
||||
SupportsMapsFrom: d2k
|
||||
|
||||
SoundFormats: Aud, Wav
|
||||
|
||||
SpriteFormats: R8, ShpTD, TmpRA
|
||||
|
||||
SpriteSequenceFormat: DefaultSpriteSequence
|
||||
|
||||
@@ -54,6 +54,8 @@ Fonts:
|
||||
|
||||
LobbyDefaults:
|
||||
|
||||
SoundFormats:
|
||||
|
||||
SpriteFormats: ShpTD
|
||||
|
||||
SpriteSequenceFormat: DefaultSpriteSequence
|
||||
|
||||
@@ -214,6 +214,8 @@ MapGrid:
|
||||
|
||||
SupportsMapsFrom: ra
|
||||
|
||||
SoundFormats: Aud, Wav
|
||||
|
||||
SpriteFormats: ShpD2, ShpTD, TmpRA, TmpTD, ShpTS
|
||||
|
||||
SpriteSequenceFormat: TilesetSpecificSpriteSequence
|
||||
|
||||
@@ -251,6 +251,8 @@ Fonts:
|
||||
|
||||
SupportsMapsFrom: ts
|
||||
|
||||
SoundFormats: Aud, Wav
|
||||
|
||||
SpriteFormats: ShpTS, TmpTS, ShpTD
|
||||
|
||||
SpriteSequenceFormat: TilesetSpecificSpriteSequence
|
||||
|
||||
Reference in New Issue
Block a user