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 MultiChannelSource { get; set; }
public Dictionary<uint, AwcStream> StreamDict { get; set; }
static public void Decrypt_RSXXTEA(byte[] data, uint[] key)
{
// Rockstar's modified version of XXTEA
@ -212,6 +215,8 @@ namespace CodeWalker.GameFiles
Streams = streams.ToArray();
MultiChannelSource?.AssignMultiChannelSources(Streams);
BuildStreamDict();
}
private void Write(DataWriter w)
@ -343,6 +348,7 @@ namespace CodeWalker.GameFiles
BuildStreamInfos();
BuildStreamDict();
}
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
@ -538,6 +553,7 @@ namespace CodeWalker.GameFiles
public AwcGranularLoopsChunk GranularLoopsChunk { get; set; }
public AwcStreamFormatChunk StreamFormatChunk { get; set; }
public AwcSeekTableChunk SeekTableChunk { get; set; }
public AwcStream[] ChannelStreams { get; set; }
public AwcStream StreamSource { get; set; }
public AwcStreamFormat StreamFormat { get; set; }
public AwcStreamDataBlock[] StreamBlocks { get; set; }
@ -646,6 +662,11 @@ namespace CodeWalker.GameFiles
{
if (FormatChunk != null) return (float)FormatChunk.Samples / FormatChunk.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;
}
}
@ -965,6 +986,7 @@ namespace CodeWalker.GameFiles
public void AssignMultiChannelSources(AwcStream[] streams)
{
var cstreams = new List<AwcStream>();
for (int i = 0; i < (streams?.Length ?? 0); i++)
{
var stream = streams[i];
@ -990,9 +1012,10 @@ namespace CodeWalker.GameFiles
stream.StreamSource = this;
stream.StreamFormat = (srcind < chancnt) ? StreamFormatChunk.Channels[srcind] : null;
stream.StreamChannelIndex = srcind;
cstreams.Add(stream);
}
}
ChannelStreams = cstreams.ToArray();
}
public void CompactMultiChannelSources(AwcStream[] streams)

View File

@ -106,8 +106,11 @@ namespace CodeWalker.Forms
strlist.Sort((a, b) => a.Name.CompareTo(b.Name));
foreach (var audio in strlist)
{
if (audio.StreamBlocks != null) continue;//don't display multichannel source audios
var item = PlayListView.Items.Add(audio.Name);
var stereo = (audio.ChannelStreams?.Length == 2);
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.LengthStr);
item.SubItems.Add(TextUtil.GetBytesReadable(audio.ByteLength));
@ -170,11 +173,23 @@ namespace CodeWalker.Forms
{
var item = PlayListView.SelectedItems[0];
var audio = item.Tag as AwcStream;
if ((audio?.FormatChunk != null) || (audio?.StreamFormat != null))
var stereo = (audio?.ChannelStreams?.Length == 2);
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.SetOutputMatrix(0, f0, f1);
Player.SetOutputMatrix(1, f1, f0);
Player.Play();
}
else if ((audio?.FormatChunk != null) || (audio?.StreamFormat != null))
{
Player.LoadAudio(audio);
Player.SetVolume(VolumeTrackBar.Value / 100.0f);
Player.Play();
}
else if (audio.MidiChunk != null)
@ -360,6 +375,13 @@ namespace CodeWalker.Forms
{
var item = PlayListView.SelectedItems[0];
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";
if (audio?.MidiChunk != null)

View File

@ -16,14 +16,19 @@ namespace CodeWalker.Utils
public class AudioPlayer
{
private AwcStream currentAudio;
private Stream wavStream;
private SoundStream soundStream;
private XAudio2 xAudio2;
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 PlayerState State { get; private set; } = PlayerState.Stopped;
@ -35,7 +40,6 @@ namespace CodeWalker.Utils
private float volume = 1.0f;
public int PlayTimeMS
{
get
@ -52,32 +56,7 @@ namespace CodeWalker.Utils
}
public void LoadAudio(AwcStream audio)
{
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)
public void LoadAudio(params AwcStream[] audios)
{
if (xAudio2 == null)
{
@ -85,17 +64,49 @@ namespace CodeWalker.Utils
masteringVoice = new MasteringVoice(xAudio2);
}
wavStream.Position = 0;
soundStream.Position = 0;
audioBuffer = new AudioBuffer
if ((voices == null) || (voices.Length != audios.Length))
{
voices = new AudioVoice[audios.Length];
for (int i = 0; i < audios.Length; i++)
{
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)
{
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)
{
playtimer.Restart();
@ -112,10 +123,15 @@ namespace CodeWalker.Utils
}
trackFinished = false;
sourceVoice = new SourceVoice(xAudio2, soundStream.Format, true);
sourceVoice.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo);
foreach (var voice in voices)
{
var sourceVoice = new SourceVoice(xAudio2, voice.soundStream.Format, true);
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)
@ -148,18 +164,35 @@ namespace CodeWalker.Utils
volume = v;
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()
{
CloseStreams();
if (xAudio2 != null)
{
masteringVoice.Dispose();
xAudio2.Dispose();
}
foreach (var voice in voices)
{
voice?.audioBuffer?.Stream?.Dispose();
}
}
@ -167,9 +200,11 @@ namespace CodeWalker.Utils
{
if (State != PlayerState.Stopped)
{
sourceVoice.DestroyVoice();
sourceVoice.Dispose();
audioBuffer.Stream.Dispose();
foreach (var voice in voices)
{
voice.sourceVoice.DestroyVoice();
voice.sourceVoice.Dispose();
}
SetPlayerState(PlayerState.Stopped);
}
}
@ -177,8 +212,11 @@ namespace CodeWalker.Utils
public void Play(float playBegin = 0)
{
Stop();
InitializeAudio(playBegin);
sourceVoice.Start();
CreateSourceVoices(playBegin);
foreach (var voice in voices)
{
voice.sourceVoice.Start();
}
SetPlayerState(PlayerState.Playing);
}
@ -192,7 +230,7 @@ namespace CodeWalker.Utils
{
var state = State;
Stop();
InitializeAudio(playBegin);
CreateSourceVoices(playBegin);
State = state;
}
}
@ -201,7 +239,10 @@ namespace CodeWalker.Utils
{
if (State == PlayerState.Playing)
{
sourceVoice.Stop();
foreach (var voice in voices)
{
voice.sourceVoice.Stop();
}
SetPlayerState(PlayerState.Paused);
}
}
@ -210,14 +251,142 @@ namespace CodeWalker.Utils
{
if (State == PlayerState.Paused)
{
sourceVoice.Start();
foreach (var voice in voices)
{
voice.sourceVoice.Start();
}
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;
}
}
}