Skip to content

Instantly share code, notes, and snippets.

@barncastle
Last active November 12, 2022 02:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barncastle/be026bbbab5c469f4e9e1317e95276e9 to your computer and use it in GitHub Desktop.
Save barncastle/be026bbbab5c469f4e9e1317e95276e9 to your computer and use it in GitHub Desktop.
Code for parsing TMNT: Shredder's Revenge's SoundSettings and SFXData files
// Paris.Engine.SoundSettings
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
public class SoundSettings
{
public GlobalSoundSettings Settings;
public List<SoundChannel> Channels = new List<SoundChannel>();
public List<SoundGroupInfo> SoundGroups = new List<SoundGroupInfo>();
public List<SoundInfo> Sounds = new List<SoundInfo>();
public List<SoundInfo> Music = new List<SoundInfo>();
/// <summary>
/// Loads settings from SoundsSettings.zpbn
/// </summary>
public void Load(string path)
{
using (var fs = File.OpenRead(path))
using (var ds = new DeflateStream(fs, CompressionMode.Decompress))
using (var br = new BinaryReader(ds))
{
var serializerType = br.ReadString();
int soundChannelCount = br.ReadInt32();
for (int i = 0; i < soundChannelCount; i++)
{
Channels.Add(new SoundChannel
{
MaxSounds = br.ReadInt32(),
Name = br.ReadString(),
PlayType = (PlayRule)br.ReadInt32(),
Volume = br.ReadSingle()
});
}
int musicSoundInfoCount = br.ReadInt32();
for (int j = 0; j < musicSoundInfoCount; j++)
{
SoundInfo soundInfo = new SoundInfo
{
Cooldown = br.ReadSingle(),
CutOffType = (SoundCutOffType)br.ReadInt32(),
IsVO = br.ReadBoolean(),
LoopType = (SoundLoopType)br.ReadInt32(),
PitchMax = br.ReadSingle(),
PitchMin = br.ReadSingle()
};
Music.Add(soundInfo);
int playbackChannelCount = br.ReadInt32();
for (int k = 0; k < playbackChannelCount; k++)
soundInfo.PlaybackChannels.Add(br.ReadString());
soundInfo.PoolSize = br.ReadInt32();
soundInfo.SoundID = br.ReadString();
soundInfo.Volume = br.ReadSingle();
}
Settings.MasterMusicVolume = br.ReadSingle();
Settings.MasterSoundVolume = br.ReadSingle();
Settings.MasterVolume = br.ReadSingle();
int soundGroupInfoCount = br.ReadInt32();
for (int l = 0; l < soundGroupInfoCount; l++)
{
var soundGroupInfo = new SoundGroupInfo
{
ChanceToPlay = br.ReadInt32(),
Cooldown = br.ReadSingle(),
ID = br.ReadString()
};
SoundGroups.Add(soundGroupInfo);
int soundIDCount = br.ReadInt32();
for (int m = 0; m < soundIDCount; m++)
{
br.ReadString(); // serializerName
br.ReadString(); // collectionID->ID
soundGroupInfo.SoundList.Add(br.ReadString());
}
}
int settingsSoundInfoCount = br.ReadInt32();
for (int n = 0; n < settingsSoundInfoCount; n++)
{
SoundInfo soundInfo = new SoundInfo
{
Cooldown = br.ReadSingle(),
CutOffType = (SoundCutOffType)br.ReadInt32(),
IsVO = br.ReadBoolean(),
LoopType = (SoundLoopType)br.ReadInt32(),
PitchMax = br.ReadSingle(),
PitchMin = br.ReadSingle()
};
Sounds.Add(soundInfo);
int num7 = br.ReadInt32();
for (int num8 = 0; num8 < num7; num8++)
soundInfo.PlaybackChannels.Add(br.ReadString());
soundInfo.PoolSize = br.ReadInt32();
soundInfo.SoundID = br.ReadString();
soundInfo.Volume = br.ReadSingle();
}
}
}
/// <summary>
/// Loads audio data from SFXPack.pbn
/// </summary>
public void LoadData(string path)
{
using (var fs = File.OpenRead(path))
using (var br = new BinaryReader(fs))
{
for (var i = 0; i < Sounds.Count; i++)
{
var fileSize = br.ReadInt32();
var data = br.ReadBytes(fileSize);
Sounds[i].RawData = data;
}
}
}
/// <summary>
/// Exports to WAV
/// </summary>
public void ExportToWAV(string path, SoundInfo soundInfo)
{
const ushort channels = 2;
const uint sampleRate = 48000;
var filename = Path.Combine(path, $"{soundInfo.SoundID}.wav");
for (var i = 0; File.Exists(filename); i++)
filename = Path.Combine(path, $"{soundInfo.SoundID}_{i:X2}.wav");
using (var fs = File.Create(filename))
using (var bw = new BinaryWriter(fs))
{
bw.Write("RIFF".ToCharArray());
bw.Write(soundInfo.RawData.Length + 36);
bw.Write("WAVE".ToCharArray());
bw.Write("fmt ".ToCharArray());
bw.Write(16);
bw.Write((ushort)1); // wFormatTag
bw.Write(channels);
bw.Write(sampleRate);
bw.Write(sampleRate * channels * 2); // nAvgBytesPerSec
bw.Write((ushort)(channels * 2)); // nBlockAlign
bw.Write(16); // wBitsPerSample
bw.Write("data".ToCharArray());
bw.Write(soundInfo.RawData.Length);
bw.Write(soundInfo.RawData);
}
}
public enum SoundCutOffType
{
CutOff,
NoCutOff,
MuteUntilChannelFree
}
public enum PlayRule
{
AlwaysPlay,
PlayOnlyIfFree
}
public enum SoundLoopType
{
NoLoop,
Loop,
LoopNoInterrupt
}
public class GlobalSoundSettings
{
public float MasterVolume = 100f;
public float MasterMusicVolume = 100f;
public float MasterSoundVolume = 100f;
}
public class SoundChannel
{
public string Name;
public float Volume = 100f;
public int MaxSounds;
public PlayRule PlayType;
}
public class SoundGroupInfo
{
public string ID;
public List<string> SoundList = new List<string>();
public float Cooldown;
public int ChanceToPlay = 100;
}
public class SoundInfo
{
public string SoundID;
public float Volume;
public SoundLoopType LoopType;
public int PoolSize;
public float Cooldown;
public bool IsVO;
public SoundCutOffType CutOffType;
public float PitchMin;
public float PitchMax;
public List<string> PlaybackChannels = new List<string>();
public byte[] RawData;
}
}
@BEARIlko
Copy link

Thank you for this code!

I am having some issues. In exporting the WAV files - they all are silent. The names are right and their total size do add up to the size of the full SFXPack.pbn. Did I miss something? Any ideas?

Best regards!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment