mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-11-26 08:52:52 +08:00
Added stereo playback option in AwcForm
This commit is contained in:
parent
1c9fc78f7f
commit
b3857db929
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user