Skip to content

Commit

Permalink
Organise TR4/5 SFX data hierarchically (LostArtefacts#622)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahm86 authored Apr 21, 2024
1 parent 5a3e7be commit 86d6eff
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 139 deletions.
126 changes: 104 additions & 22 deletions TRLevelControl/Control/TR4LevelControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ private void ReadLevelDataChunk(TRLevelReader mainReader)

ReadEntities(reader);

ReadDemoData(reader);

ReadSoundEffects(reader);

reader.ReadUInt16s(3); // Always 0s
Expand Down Expand Up @@ -144,6 +146,8 @@ private void WriteLevelDataChunk(TRLevelWriter mainWriter)

WriteEntities(writer);

WriteDemoData(writer);

WriteSoundEffects(writer);

writer.Write(Enumerable.Repeat((ushort)0, 3).ToArray());
Expand Down Expand Up @@ -383,59 +387,137 @@ private void WriteEntities(TRLevelWriter writer)
writer.Write(_level.AIEntities);
}

private void ReadSoundEffects(TRLevelReader reader)
private void ReadDemoData(TRLevelReader reader)
{
TR4FileReadUtilities.PopulateDemoSoundSampleIndices(reader, _level);
ushort numDemoData = reader.ReadUInt16();
_level.DemoData = reader.ReadBytes(numDemoData);
}

private void WriteSoundEffects(TRLevelWriter writer)
private void WriteDemoData(TRLevelWriter writer)
{
writer.Write((ushort)_level.DemoData.Length);
writer.Write(_level.DemoData);
}

private void ReadSoundEffects(TRLevelReader reader)
{
_level.SoundEffects = new();
short[] soundMap = reader.ReadInt16s(Enum.GetValues<TR4SFX>().Length);

uint numSoundDetails = reader.ReadUInt32();
List<TR4SoundEffect> sfx = new();

foreach (short sound in _level.SoundMap)
Dictionary<int, ushort> sampleMap = new();
for (int i = 0; i < numSoundDetails; i++)
{
writer.Write(sound);
sampleMap[i] = reader.ReadUInt16();
sfx.Add(new()
{
Volume = reader.ReadByte(),
Range = reader.ReadByte(),
Chance = reader.ReadByte(),
Pitch = reader.ReadByte(),
Samples = new()
});

sfx[i].SetFlags(reader.ReadUInt16());
}

writer.Write((uint)_level.SoundDetails.Count);
foreach (TR4SoundDetails snd in _level.SoundDetails)
// Sample indices are discarded in game. The details point to the samples
// directly per ReadWAVData. Observe the reads here only.
uint numSampleIndices = reader.ReadUInt32();
uint[] sampleIndices = reader.ReadUInt32s(numSampleIndices);
_observer?.OnSampleIndicesRead(sampleIndices);

for (int i = 0; i < soundMap.Length; i++)
{
writer.Write(snd.Serialize());
if (soundMap[i] < 0 || soundMap[i] >= sfx.Count)
{
continue;
}

_level.SoundEffects[(TR4SFX)i] = sfx[soundMap[i]];
}
}

writer.Write((uint)_level.SampleIndices.Count);
foreach (uint sampleindex in _level.SampleIndices)
private void WriteSoundEffects(TRLevelWriter writer)
{
short detailsIndex = 0;
List<uint> sampleIndices = new();
List<TR4Sample> samples = new();

foreach (TR4SFX id in Enum.GetValues<TR4SFX>())
{
writer.Write(_level.SoundEffects.ContainsKey(id) ? detailsIndex++ : (short)-1);
}

writer.Write((uint)_level.SoundEffects.Count);
foreach (TR4SoundEffect details in _level.SoundEffects.Values)
{
writer.Write(sampleindex);
TR4Sample firstSample = details.Samples.First();
int sampleIndex = samples.IndexOf(firstSample);
if (sampleIndex == -1)
{
sampleIndex = samples.Count;
samples.AddRange(details.Samples);
}

writer.Write((ushort)sampleIndex);
writer.Write(details.Volume);
writer.Write(details.Range);
writer.Write(details.Chance);
writer.Write(details.Pitch);
writer.Write(details.GetFlags());

sampleIndices.Add((uint)sampleIndex);
}

// Sample indices are not required, but write them anyway to match OG
IEnumerable<uint> outputIndices = _observer?.GetSampleIndices() ?? sampleIndices;
writer.Write((uint)outputIndices.Count());
writer.Write(outputIndices);
}

private void ReadWAVData(TRLevelReader reader)
{
uint numSamples = reader.ReadUInt32();
_level.Samples = new();
List<TR4Sample> samples = new();

for (int i = 0; i < numSamples; i++)
{
_level.Samples.Add(new()
TR4Sample sample = new()
{
UncompSize = reader.ReadUInt32(),
CompSize = reader.ReadUInt32(),
});
InflatedLength = reader.ReadUInt32()
};
samples.Add(sample);

uint compressedSize = reader.ReadUInt32();
sample.Data = reader.ReadUInt8s(compressedSize);
}

_level.Samples[i].CompressedChunk = reader.ReadBytes((int)_level.Samples[i].CompSize);
int pos = 0;
foreach (TR4SoundEffect sfx in _level.SoundEffects.Values)
{
for (int i = 0; i < sfx.Samples.Capacity; i++)
{
sfx.Samples.Add(samples[pos++]);
}
}
}

private void WriteWAVData(TRLevelWriter writer)
{
writer.Write((uint)_level.Samples.Count);
foreach (TR4Sample sample in _level.Samples)
List<TR4Sample> samples = _level.SoundEffects.Values
.SelectMany(s => s.Samples)
.Distinct()
.ToList();

writer.Write((uint)samples.Count);
foreach (TR4Sample sample in samples)
{
writer.Write(sample.UncompSize);
writer.Write(sample.CompSize);
writer.Write(sample.CompressedChunk);
writer.Write(sample.InflatedLength);
writer.Write((uint)sample.Data.Length);
writer.Write(sample.Data);
}
}
}
132 changes: 102 additions & 30 deletions TRLevelControl/Control/TR5LevelControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,9 @@ private void ReadLevelDataChunk(TRLevelReader reader)

ReadEntities(reader);

//reader.ReadUInt16(); // Unused, always 0 //IF we eliminate demodata
ReadDemoData(reader);

ReadSoundEffects(reader);
ReadWAVData(reader);
}

private void WriteLevelDataChunk(TRLevelWriter mainWriter)
Expand Down Expand Up @@ -160,7 +159,7 @@ private void WriteLevelDataChunk(TRLevelWriter mainWriter)

WriteEntities(writer);

//writer.Write((ushort)0); //IF we eliminate demodata
WriteDemoData(writer);

WriteSoundEffects(writer);

Expand Down Expand Up @@ -407,62 +406,135 @@ private void WriteEntities(TRLevelWriter writer)
writer.Write(_level.AIEntities);
}

private void ReadSoundEffects(TRLevelReader reader)
private void ReadDemoData(TRLevelReader reader)
{
TR5FileReadUtilities.PopulateDemoSoundSampleIndices(reader, _level);
reader.ReadBytes(6); // Always 0xCD
ushort numDemoData = reader.ReadUInt16();
_level.DemoData = reader.ReadBytes(numDemoData);
}

private void WriteSoundEffects(TRLevelWriter writer)
private void WriteDemoData(TRLevelWriter writer)
{
writer.Write((ushort)_level.DemoData.Length);
writer.Write(_level.DemoData);
}

foreach (short sound in _level.SoundMap)
private void ReadSoundEffects(TRLevelReader reader)
{
_level.SoundEffects = new();
short[] soundMap = reader.ReadInt16s(Enum.GetValues<TR5SFX>().Length);

uint numSoundDetails = reader.ReadUInt32();
List<TR4SoundEffect> sfx = new();

Dictionary<int, ushort> sampleMap = new();
for (int i = 0; i < numSoundDetails; i++)
{
writer.Write(sound);
sampleMap[i] = reader.ReadUInt16();
sfx.Add(new()
{
Volume = reader.ReadByte(),
Range = reader.ReadByte(),
Chance = reader.ReadByte(),
Pitch = reader.ReadByte(),
Samples = new()
});

sfx[i].SetFlags(reader.ReadUInt16());
}

writer.Write((uint)_level.SoundDetails.Count);
foreach (TR4SoundDetails snd in _level.SoundDetails)
// Sample indices are discarded in game. The details point to the samples
// directly per ReadWAVData. Observe the reads here only.
uint numSampleIndices = reader.ReadUInt32();
uint[] sampleIndices = reader.ReadUInt32s(numSampleIndices);
_observer?.OnSampleIndicesRead(sampleIndices);

for (int i = 0; i < soundMap.Length; i++)
{
writer.Write(snd.Serialize());
if (soundMap[i] == -1)
continue;

_level.SoundEffects[(TR5SFX)i] = sfx[soundMap[i]];
}

writer.Write((uint)_level.SampleIndices.Count);
foreach (uint sampleindex in _level.SampleIndices)
reader.ReadBytes(6); // OxCD padding

uint numSamples = reader.ReadUInt32();
List<TR4Sample> samples = new();

for (int i = 0; i < numSamples; i++)
{
writer.Write(sampleindex);
TR4Sample sample = new()
{
InflatedLength = reader.ReadUInt32()
};
samples.Add(sample);

uint compressedSize = reader.ReadUInt32();
sample.Data = reader.ReadUInt8s(compressedSize);
}

writer.Write(Enumerable.Repeat((byte)0xCD, 6).ToArray());
for (int i = 0; i < sfx.Count; i++)
{
TR4SoundEffect effect = sfx[i];
for (int j = 0; j < effect.Samples.Capacity; j++)
{
effect.Samples.Add(samples[sampleMap[i] + j]);
}
}
}

private void ReadWAVData(TRLevelReader reader)
private void WriteSoundEffects(TRLevelWriter writer)
{
uint numSamples = reader.ReadUInt32();
_level.Samples = new();
short detailsIndex = 0;
List<uint> sampleIndices = new();
List<TR4Sample> samples = new();

for (int i = 0; i < numSamples; i++)
foreach (TR5SFX id in Enum.GetValues<TR5SFX>())
{
_level.Samples.Add(new()
writer.Write(_level.SoundEffects.ContainsKey(id) ? detailsIndex++ : (short)-1);
}

writer.Write((uint)_level.SoundEffects.Count);
foreach (TR4SoundEffect details in _level.SoundEffects.Values)
{
TR4Sample firstSample = details.Samples.First();
int sampleIndex = samples.IndexOf(firstSample);
if (sampleIndex == -1)
{
UncompSize = reader.ReadUInt32(),
CompSize = reader.ReadUInt32(),
});
sampleIndex = samples.Count;
samples.AddRange(details.Samples);
}

_level.Samples[i].CompressedChunk = reader.ReadBytes((int)_level.Samples[i].CompSize);
writer.Write((ushort)sampleIndex);
writer.Write(details.Volume);
writer.Write(details.Range);
writer.Write(details.Chance);
writer.Write(details.Pitch);
writer.Write(details.GetFlags());

sampleIndices.Add((uint)sampleIndex);
}

IEnumerable<uint> outputIndices = _observer?.GetSampleIndices() ?? sampleIndices;
writer.Write((uint)outputIndices.Count());
writer.Write(outputIndices);

writer.Write(Enumerable.Repeat((byte)0xCD, 6));
}

private void WriteWAVData(TRLevelWriter writer)
{
writer.Write((uint)_level.Samples.Count);
foreach (TR4Sample sample in _level.Samples)
List<TR4Sample> samples = _level.SoundEffects.Values
.SelectMany(s => s.Samples)
.Distinct()
.ToList();

writer.Write((uint)samples.Count);
foreach (TR4Sample sample in samples)
{
writer.Write(sample.UncompSize);
writer.Write(sample.CompSize);
writer.Write(sample.CompressedChunk);
writer.Write(sample.InflatedLength);
writer.Write((uint)sample.Data.Length);
writer.Write(sample.Data);
}
}
}
2 changes: 2 additions & 0 deletions TRLevelControl/ITRLevelObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ public interface ITRLevelObserver
void OnChunkWritten(long startPosition, long endPosition, TRChunkType chunkType, byte[] data);
void OnMeshPaddingRead(uint meshPointer, List<byte> values);
List<byte> GetMeshPadding(uint meshPointer);
void OnSampleIndicesRead(uint[] sampleIndices);
IEnumerable<uint> GetSampleIndices();
}
5 changes: 1 addition & 4 deletions TRLevelControl/Model/TR4/TR4Level.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,5 @@ public class TR4Level : TRLevelBase
public List<TR4Entity> Entities { get; set; }
public List<TR4AIEntity> AIEntities { get; set; }
public byte[] DemoData { get; set; }
public short[] SoundMap { get; set; }
public List<TR4SoundDetails> SoundDetails { get; set; }
public List<uint> SampleIndices { get; set; }
public List<TR4Sample> Samples { get; set; }
public SortedDictionary<TR4SFX, TR4SoundEffect> SoundEffects { get; set; }
}
Loading

0 comments on commit 86d6eff

Please sign in to comment.