Added stereo playback option in AwcForm

This commit is contained in:
dexy 2020-03-07 07:00:35 +11:00
parent 1c9fc78f7f
commit b3857db929
3 changed files with 275 additions and 61 deletions

View File

@ -37,6 +37,9 @@ namespace CodeWalker.GameFiles
public AwcStream[] Streams { get; set; } public AwcStream[] Streams { get; set; }
public AwcStream MultiChannelSource { get; set; } public AwcStream MultiChannelSource { get; set; }
public Dictionary<uint, AwcStream> StreamDict { get; set; }
static public void Decrypt_RSXXTEA(byte[] data, uint[] key) static public void Decrypt_RSXXTEA(byte[] data, uint[] key)
{ {
// Rockstar's modified version of XXTEA // Rockstar's modified version of XXTEA
@ -212,6 +215,8 @@ namespace CodeWalker.GameFiles
Streams = streams.ToArray(); Streams = streams.ToArray();
MultiChannelSource?.AssignMultiChannelSources(Streams); MultiChannelSource?.AssignMultiChannelSources(Streams);
BuildStreamDict();
} }
private void Write(DataWriter w) private void Write(DataWriter w)
@ -343,6 +348,7 @@ namespace CodeWalker.GameFiles
BuildStreamInfos(); BuildStreamInfos();
BuildStreamDict();
} }
public static void WriteXmlNode(AwcFile f, StringBuilder sb, int indent, string wavfolder, string name = "AudioWaveContainer") public static void WriteXmlNode(AwcFile f, StringBuilder sb, int indent, string wavfolder, string name = "AudioWaveContainer")
{ {
@ -461,6 +467,15 @@ namespace CodeWalker.GameFiles
} }
public void BuildStreamDict()
{
StreamDict = new Dictionary<uint, AwcStream>();
if (Streams == null) return;
foreach (var stream in Streams)
{
StreamDict[stream.Hash] = stream;
}
}
} }
public enum AwcCodecType public enum AwcCodecType
@ -538,6 +553,7 @@ namespace CodeWalker.GameFiles
public AwcGranularLoopsChunk GranularLoopsChunk { get; set; } public AwcGranularLoopsChunk GranularLoopsChunk { get; set; }
public AwcStreamFormatChunk StreamFormatChunk { get; set; } public AwcStreamFormatChunk StreamFormatChunk { get; set; }
public AwcSeekTableChunk SeekTableChunk { get; set; } public AwcSeekTableChunk SeekTableChunk { get; set; }
public AwcStream[] ChannelStreams { get; set; }
public AwcStream StreamSource { get; set; } public AwcStream StreamSource { get; set; }
public AwcStreamFormat StreamFormat { get; set; } public AwcStreamFormat StreamFormat { get; set; }
public AwcStreamDataBlock[] StreamBlocks { get; set; } public AwcStreamDataBlock[] StreamBlocks { get; set; }
@ -646,6 +662,11 @@ namespace CodeWalker.GameFiles
{ {
if (FormatChunk != null) return (float)FormatChunk.Samples / FormatChunk.SamplesPerSecond; if (FormatChunk != null) return (float)FormatChunk.Samples / FormatChunk.SamplesPerSecond;
if (StreamFormat != null) return (float)StreamFormat.Samples / StreamFormat.SamplesPerSecond; if (StreamFormat != null) return (float)StreamFormat.Samples / StreamFormat.SamplesPerSecond;
if ((StreamFormatChunk != null) && (StreamFormatChunk.Channels?.Length > 0))
{
var chan = StreamFormatChunk.Channels[0];
return (float)chan.Samples / chan.SamplesPerSecond;
}
return 0; return 0;
} }
} }
@ -965,6 +986,7 @@ namespace CodeWalker.GameFiles
public void AssignMultiChannelSources(AwcStream[] streams) public void AssignMultiChannelSources(AwcStream[] streams)
{ {
var cstreams = new List<AwcStream>();
for (int i = 0; i < (streams?.Length ?? 0); i++) for (int i = 0; i < (streams?.Length ?? 0); i++)
{ {
var stream = streams[i]; var stream = streams[i];
@ -990,9 +1012,10 @@ namespace CodeWalker.GameFiles
stream.StreamSource = this; stream.StreamSource = this;
stream.StreamFormat = (srcind < chancnt) ? StreamFormatChunk.Channels[srcind] : null; stream.StreamFormat = (srcind < chancnt) ? StreamFormatChunk.Channels[srcind] : null;
stream.StreamChannelIndex = srcind; stream.StreamChannelIndex = srcind;
cstreams.Add(stream);
} }
} }
ChannelStreams = cstreams.ToArray();
} }
public void CompactMultiChannelSources(AwcStream[] streams) public void CompactMultiChannelSources(AwcStream[] streams)

View File

@ -106,8 +106,11 @@ namespace CodeWalker.Forms
strlist.Sort((a, b) => a.Name.CompareTo(b.Name)); strlist.Sort((a, b) => a.Name.CompareTo(b.Name));
foreach (var audio in strlist) foreach (var audio in strlist)
{ {
if (audio.StreamBlocks != null) continue;//don't display multichannel source audios var stereo = (audio.ChannelStreams?.Length == 2);
var item = PlayListView.Items.Add(audio.Name); if ((audio.StreamBlocks != null) && (!stereo)) continue;//don't display multichannel source audios
var name = audio.Name;
if (stereo) name = "(Stereo Playback)";
var item = PlayListView.Items.Add(name);
item.SubItems.Add(audio.Type); item.SubItems.Add(audio.Type);
item.SubItems.Add(audio.LengthStr); item.SubItems.Add(audio.LengthStr);
item.SubItems.Add(TextUtil.GetBytesReadable(audio.ByteLength)); item.SubItems.Add(TextUtil.GetBytesReadable(audio.ByteLength));
@ -170,11 +173,23 @@ namespace CodeWalker.Forms
{ {
var item = PlayListView.SelectedItems[0]; var item = PlayListView.SelectedItems[0];
var audio = item.Tag as AwcStream; var audio = item.Tag as AwcStream;
var stereo = (audio?.ChannelStreams?.Length == 2);
if ((audio?.FormatChunk != null) || (audio?.StreamFormat != null)) if (stereo)
{ {
var name0 = audio.ChannelStreams[0].Name;
var left0 = name0.EndsWith("left") || name0.EndsWith(".l");
var f0 = left0 ? 1.0f : 0.0f;
var f1 = left0 ? 0.0f : 1.0f;
Player.LoadAudio(audio.ChannelStreams);
Player.SetVolume(VolumeTrackBar.Value / 100.0f); Player.SetVolume(VolumeTrackBar.Value / 100.0f);
Player.SetOutputMatrix(0, f0, f1);
Player.SetOutputMatrix(1, f1, f0);
Player.Play();
}
else if ((audio?.FormatChunk != null) || (audio?.StreamFormat != null))
{
Player.LoadAudio(audio); Player.LoadAudio(audio);
Player.SetVolume(VolumeTrackBar.Value / 100.0f);
Player.Play(); Player.Play();
} }
else if (audio.MidiChunk != null) else if (audio.MidiChunk != null)
@ -360,6 +375,13 @@ namespace CodeWalker.Forms
{ {
var item = PlayListView.SelectedItems[0]; var item = PlayListView.SelectedItems[0];
var audio = item.Tag as AwcStream; var audio = item.Tag as AwcStream;
var stereo = (audio?.ChannelStreams?.Length == 2);
if (stereo)
{
MessageBox.Show("Sorry, export for stereo output is not currently available.");
return;
}
var ext = ".wav"; var ext = ".wav";
if (audio?.MidiChunk != null) if (audio?.MidiChunk != null)

View File

@ -16,14 +16,19 @@ namespace CodeWalker.Utils
public class AudioPlayer public class AudioPlayer
{ {
private AwcStream currentAudio;
private Stream wavStream;
private SoundStream soundStream;
private XAudio2 xAudio2; private XAudio2 xAudio2;
private MasteringVoice masteringVoice; private MasteringVoice masteringVoice;
private AudioBuffer audioBuffer;
private SourceVoice sourceVoice; public class AudioVoice
{
public AwcStream audio;
public SoundStream soundStream;
public AudioBuffer audioBuffer;
public SourceVoice sourceVoice;
public float[] outputMatrix = new[] { 1.0f, 1.0f }; //left/right channel output levels
public float trackLength;
}
private AudioVoice[] voices = new AudioVoice[0];
public enum PlayerState { Stopped, Playing, Paused }; public enum PlayerState { Stopped, Playing, Paused };
public PlayerState State { get; private set; } = PlayerState.Stopped; public PlayerState State { get; private set; } = PlayerState.Stopped;
@ -35,7 +40,6 @@ namespace CodeWalker.Utils
private float volume = 1.0f; private float volume = 1.0f;
public int PlayTimeMS public int PlayTimeMS
{ {
get get
@ -52,32 +56,7 @@ namespace CodeWalker.Utils
} }
public void LoadAudio(AwcStream audio) public void LoadAudio(params AwcStream[] audios)
{
if (audio != currentAudio)
{
CloseStreams();
currentAudio = audio;
trackLength = audio.Length;
wavStream = audio.GetWavStream();
soundStream = new SoundStream(wavStream);
}
}
private void CloseStreams()
{
if (soundStream != null)
{
soundStream.Close();
}
if (wavStream != null)
{
wavStream.Close();//is this necessary?
}
}
private void InitializeAudio(float playBegin = 0)
{ {
if (xAudio2 == null) if (xAudio2 == null)
{ {
@ -85,17 +64,49 @@ namespace CodeWalker.Utils
masteringVoice = new MasteringVoice(xAudio2); masteringVoice = new MasteringVoice(xAudio2);
} }
wavStream.Position = 0; if ((voices == null) || (voices.Length != audios.Length))
soundStream.Position = 0;
audioBuffer = new AudioBuffer
{ {
Stream = soundStream.ToDataStream(), voices = new AudioVoice[audios.Length];
AudioBytes = (int)soundStream.Length, for (int i = 0; i < audios.Length; i++)
Flags = BufferFlags.EndOfStream {
}; voices[i] = new AudioVoice();
}
}
trackLength = 0;
for (int i = 0; i < audios.Length; i++)
{
var voice = voices[i];
var audio = audios[i];
if (audio != voice.audio)
{
voice.audio = audio;
voice.trackLength = audio.Length;
trackLength = Math.Max(trackLength, voice.trackLength);
var wavStream = audio.GetWavStream();
var soundStream = new SoundStream(wavStream);
voice.soundStream = soundStream;
voice.audioBuffer = new AudioBuffer
{
Stream = soundStream.ToDataStream(),
AudioBytes = (int)soundStream.Length,
Flags = BufferFlags.EndOfStream
};
soundStream.Close();
wavStream.Close();
}
}
}
private void CreateSourceVoices(float playBegin = 0)
{
if (playBegin > 0) if (playBegin > 0)
{ {
audioBuffer.PlayBegin = (int)(soundStream.Format.SampleRate * playBegin) / 128 * 128; foreach (var voice in voices)
{
voice.audioBuffer.PlayBegin = (int)(voice.soundStream.Format.SampleRate * playBegin) / 128 * 128;
}
if (playtimer.IsRunning) if (playtimer.IsRunning)
{ {
playtimer.Restart(); playtimer.Restart();
@ -112,10 +123,15 @@ namespace CodeWalker.Utils
} }
trackFinished = false; trackFinished = false;
sourceVoice = new SourceVoice(xAudio2, soundStream.Format, true); foreach (var voice in voices)
sourceVoice.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo); {
sourceVoice.BufferEnd += (context) => trackFinished = true; var sourceVoice = new SourceVoice(xAudio2, voice.soundStream.Format, true);
sourceVoice.SetVolume(volume); sourceVoice.SubmitSourceBuffer(voice.audioBuffer, voice.soundStream.DecodedPacketsInfo);
sourceVoice.BufferEnd += (context) => trackFinished = true;
sourceVoice.SetVolume(volume);
sourceVoice.SetOutputMatrix(1, 2, voice.outputMatrix);
voice.sourceVoice = sourceVoice;
}
} }
private void SetPlayerState(PlayerState newState) private void SetPlayerState(PlayerState newState)
@ -148,18 +164,35 @@ namespace CodeWalker.Utils
volume = v; volume = v;
if (State == PlayerState.Playing) if (State == PlayerState.Playing)
{ {
sourceVoice.SetVolume(v); foreach (var voice in voices)
{
voice.sourceVoice.SetVolume(v);
}
}
}
public void SetOutputMatrix(int v, float l, float r)
{
var voice = voices[v];
voice.outputMatrix[0] = l;
voice.outputMatrix[1] = r;
if (State == PlayerState.Playing)
{
voice.sourceVoice.SetOutputMatrix(1, 2, voice.outputMatrix);
} }
} }
public void DisposeAudio() public void DisposeAudio()
{ {
CloseStreams();
if (xAudio2 != null) if (xAudio2 != null)
{ {
masteringVoice.Dispose(); masteringVoice.Dispose();
xAudio2.Dispose(); xAudio2.Dispose();
} }
foreach (var voice in voices)
{
voice?.audioBuffer?.Stream?.Dispose();
}
} }
@ -167,9 +200,11 @@ namespace CodeWalker.Utils
{ {
if (State != PlayerState.Stopped) if (State != PlayerState.Stopped)
{ {
sourceVoice.DestroyVoice(); foreach (var voice in voices)
sourceVoice.Dispose(); {
audioBuffer.Stream.Dispose(); voice.sourceVoice.DestroyVoice();
voice.sourceVoice.Dispose();
}
SetPlayerState(PlayerState.Stopped); SetPlayerState(PlayerState.Stopped);
} }
} }
@ -177,8 +212,11 @@ namespace CodeWalker.Utils
public void Play(float playBegin = 0) public void Play(float playBegin = 0)
{ {
Stop(); Stop();
InitializeAudio(playBegin); CreateSourceVoices(playBegin);
sourceVoice.Start(); foreach (var voice in voices)
{
voice.sourceVoice.Start();
}
SetPlayerState(PlayerState.Playing); SetPlayerState(PlayerState.Playing);
} }
@ -192,7 +230,7 @@ namespace CodeWalker.Utils
{ {
var state = State; var state = State;
Stop(); Stop();
InitializeAudio(playBegin); CreateSourceVoices(playBegin);
State = state; State = state;
} }
} }
@ -201,7 +239,10 @@ namespace CodeWalker.Utils
{ {
if (State == PlayerState.Playing) if (State == PlayerState.Playing)
{ {
sourceVoice.Stop(); foreach (var voice in voices)
{
voice.sourceVoice.Stop();
}
SetPlayerState(PlayerState.Paused); SetPlayerState(PlayerState.Paused);
} }
} }
@ -210,14 +251,142 @@ namespace CodeWalker.Utils
{ {
if (State == PlayerState.Paused) if (State == PlayerState.Paused)
{ {
sourceVoice.Start(); foreach (var voice in voices)
{
voice.sourceVoice.Start();
}
SetPlayerState(PlayerState.Playing); SetPlayerState(PlayerState.Playing);
} }
} }
}
public class AudioDatabase
{
public bool IsInited { get; set; }
public Dictionary<uint, Dat54Sound> SoundsDB { get; set; }
public Dictionary<uint, Dat151RelData> GameDB { get; set; }
public Dictionary<uint, RpfFileEntry> ContainerDB { get; set; }
public void Init(GameFileCache gameFileCache, bool sounds = true, bool game = true)
{
var rpfman = gameFileCache.RpfMan;
var datrelentries = new Dictionary<uint, RpfFileEntry>();
var awcentries = new Dictionary<uint, RpfFileEntry>();
void addRpfDatRels(RpfFile rpffile)
{
if (rpffile.AllEntries == null) return;
foreach (var entry in rpffile.AllEntries)
{
if (entry is RpfFileEntry)
{
var fentry = entry as RpfFileEntry;
//if (entry.NameLower.EndsWith(".rel"))
//{
// datrels[entry.NameHash] = fentry;
//}
if (sounds && entry.NameLower.EndsWith(".dat54.rel"))
{
datrelentries[entry.NameHash] = fentry;
}
if (game && entry.NameLower.EndsWith(".dat151.rel"))
{
datrelentries[entry.NameHash] = fentry;
}
}
}
}
void addRpfAwcs(RpfFile rpffile)
{
if (rpffile.AllEntries == null) return;
foreach (var entry in rpffile.AllEntries)
{
if (entry is RpfFileEntry)
{
var fentry = entry as RpfFileEntry;
if (entry.NameLower.EndsWith(".awc"))
{
var shortname = entry.GetShortNameLower();
var parentname = entry.Parent?.GetShortNameLower() ?? "";
if (string.IsNullOrEmpty(parentname) && (entry.Parent?.File != null))
{
parentname = entry.Parent.File.NameLower;
int ind = parentname.LastIndexOf('.');
if (ind > 0)
{
parentname = parentname.Substring(0, ind);
}
}
var contname = parentname + "/" + shortname;
var hash = JenkHash.GenHash(contname);
awcentries[hash] = fentry;
}
}
}
}
var audrpf = rpfman.FindRpfFile("x64\\audio\\audio_rel.rpf");
if (audrpf != null)
{
addRpfDatRels(audrpf);
}
foreach (var baserpf in gameFileCache.BaseRpfs)
{
addRpfAwcs(baserpf);
}
if (gameFileCache.EnableDlc)
{
var updrpf = rpfman.FindRpfFile("update\\update.rpf");
if (updrpf != null)
{
addRpfDatRels(updrpf);
}
foreach (var dlcrpf in gameFileCache.DlcActiveRpfs) //load from current dlc rpfs
{
addRpfDatRels(dlcrpf);
addRpfAwcs(dlcrpf);
}
}
var soundsdb = new Dictionary<uint, Dat54Sound>();
var gamedb = new Dictionary<uint, Dat151RelData>();
foreach (var datentry in datrelentries.Values)
{
var relfile = rpfman.GetFile<RelFile>(datentry);
if (relfile?.RelDatas != null)
{
foreach (var rd in relfile.RelDatas)
{
if (rd is Dat54Sound sd)
{
soundsdb[sd.NameHash] = sd;
}
else if (rd is Dat151RelData gd)
{
gamedb[gd.NameHash] = gd;
}
}
}
}
ContainerDB = awcentries;
if (sounds) SoundsDB = soundsdb;
if (game) GameDB = gamedb;
IsInited = true;
}
} }
} }