diff --git a/Forms/AwcForm.Designer.cs b/Forms/AwcForm.Designer.cs index cfee778..f78d5fa 100644 --- a/Forms/AwcForm.Designer.cs +++ b/Forms/AwcForm.Designer.cs @@ -28,12 +28,14 @@ /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AwcForm)); this.MainTabControl = new System.Windows.Forms.TabControl(); this.PlayerTabPage = new System.Windows.Forms.TabPage(); + this.VolumeLabel = new System.Windows.Forms.Label(); + this.chbAutoJump = new System.Windows.Forms.CheckBox(); this.PrevButton = new System.Windows.Forms.Button(); this.NextButton = new System.Windows.Forms.Button(); - this.VolumeButton = new System.Windows.Forms.Button(); this.PlayButton = new System.Windows.Forms.Button(); this.PlayListView = new System.Windows.Forms.ListView(); this.PlaylistNameHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); @@ -43,7 +45,7 @@ this.PositionTrackBar = new System.Windows.Forms.TrackBar(); this.DetailsTabPage = new System.Windows.Forms.TabPage(); this.DetailsPropertyGrid = new CodeWalker.WinForms.PropertyGridFix(); - this.label1 = new System.Windows.Forms.Label(); + this.Timer = new System.Windows.Forms.Timer(this.components); this.MainTabControl.SuspendLayout(); this.PlayerTabPage.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.VolumeTrackBar)).BeginInit(); @@ -64,10 +66,10 @@ // // PlayerTabPage // - this.PlayerTabPage.Controls.Add(this.label1); + this.PlayerTabPage.Controls.Add(this.VolumeLabel); + this.PlayerTabPage.Controls.Add(this.chbAutoJump); this.PlayerTabPage.Controls.Add(this.PrevButton); this.PlayerTabPage.Controls.Add(this.NextButton); - this.PlayerTabPage.Controls.Add(this.VolumeButton); this.PlayerTabPage.Controls.Add(this.PlayButton); this.PlayerTabPage.Controls.Add(this.PlayListView); this.PlayerTabPage.Controls.Add(this.VolumeTrackBar); @@ -80,6 +82,26 @@ this.PlayerTabPage.Text = "Player"; this.PlayerTabPage.UseVisualStyleBackColor = true; // + // VolumeLabel + // + this.VolumeLabel.AutoSize = true; + this.VolumeLabel.Location = new System.Drawing.Point(414, 305); + this.VolumeLabel.Name = "VolumeLabel"; + this.VolumeLabel.Size = new System.Drawing.Size(42, 13); + this.VolumeLabel.TabIndex = 9; + this.VolumeLabel.Text = "Volume"; + // + // chbAutoJump + // + this.chbAutoJump.AutoSize = true; + this.chbAutoJump.Enabled = false; + this.chbAutoJump.Location = new System.Drawing.Point(17, 305); + this.chbAutoJump.Name = "chbAutoJump"; + this.chbAutoJump.Size = new System.Drawing.Size(108, 17); + this.chbAutoJump.TabIndex = 8; + this.chbAutoJump.Text = "Auto-jump to next"; + this.chbAutoJump.UseVisualStyleBackColor = true; + // // PrevButton // this.PrevButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; @@ -102,17 +124,6 @@ this.NextButton.UseVisualStyleBackColor = true; this.NextButton.Click += new System.EventHandler(this.NextButton_Click); // - // VolumeButton - // - this.VolumeButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.VolumeButton.Location = new System.Drawing.Point(426, 301); - this.VolumeButton.Name = "VolumeButton"; - this.VolumeButton.Size = new System.Drawing.Size(33, 23); - this.VolumeButton.TabIndex = 5; - this.VolumeButton.Text = "Vol"; - this.VolumeButton.UseVisualStyleBackColor = true; - this.VolumeButton.Click += new System.EventHandler(this.VolumeButton_Click); - // // PlayButton // this.PlayButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; @@ -136,11 +147,13 @@ this.PlayListView.FullRowSelect = true; this.PlayListView.HideSelection = false; this.PlayListView.Location = new System.Drawing.Point(6, 6); + this.PlayListView.MultiSelect = false; this.PlayListView.Name = "PlayListView"; - this.PlayListView.Size = new System.Drawing.Size(556, 216); + this.PlayListView.Size = new System.Drawing.Size(556, 235); this.PlayListView.TabIndex = 0; this.PlayListView.UseCompatibleStateImageBehavior = false; this.PlayListView.View = System.Windows.Forms.View.Details; + this.PlayListView.DoubleClick += new System.EventHandler(this.PlayListView_DoubleClick); // // PlaylistNameHeader // @@ -176,7 +189,7 @@ this.PositionTrackBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.PositionTrackBar.BackColor = System.Drawing.SystemColors.ControlLightLight; - this.PositionTrackBar.LargeChange = 50; + this.PositionTrackBar.LargeChange = 1000; this.PositionTrackBar.Location = new System.Drawing.Point(6, 263); this.PositionTrackBar.Maximum = 1000; this.PositionTrackBar.Name = "PositionTrackBar"; @@ -205,14 +218,10 @@ this.DetailsPropertyGrid.Size = new System.Drawing.Size(562, 326); this.DetailsPropertyGrid.TabIndex = 0; // - // label1 + // Timer // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(44, 238); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(248, 13); - this.label1.TabIndex = 7; - this.label1.Text = "NOTE: Work in progress... Audio does not play yet!"; + this.Timer.Enabled = true; + this.Timer.Tick += new System.EventHandler(this.Timer_Tick); // // AwcForm // @@ -223,6 +232,7 @@ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Name = "AwcForm"; this.Text = "AWC Player - CodeWalker by dexyfex"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.AwcForm_FormClosing); this.MainTabControl.ResumeLayout(false); this.PlayerTabPage.ResumeLayout(false); this.PlayerTabPage.PerformLayout(); @@ -245,10 +255,11 @@ private System.Windows.Forms.ColumnHeader PlaylistLengthHeader; private System.Windows.Forms.Button PrevButton; private System.Windows.Forms.Button NextButton; - private System.Windows.Forms.Button VolumeButton; private System.Windows.Forms.Button PlayButton; private System.Windows.Forms.TrackBar VolumeTrackBar; private System.Windows.Forms.TrackBar PositionTrackBar; - private System.Windows.Forms.Label label1; + private System.Windows.Forms.Timer Timer; + private System.Windows.Forms.CheckBox chbAutoJump; + private System.Windows.Forms.Label VolumeLabel; } } \ No newline at end of file diff --git a/Forms/AwcForm.cs b/Forms/AwcForm.cs index 3b754bf..6f144f5 100644 --- a/Forms/AwcForm.cs +++ b/Forms/AwcForm.cs @@ -2,15 +2,8 @@ using SharpDX.Multimedia; using SharpDX.XAudio2; using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; +using System.Diagnostics; using System.Windows.Forms; namespace CodeWalker.Forms @@ -19,6 +12,11 @@ namespace CodeWalker.Forms { public AwcFile Awc { get; set; } + private XAudio2 xAudio2; + private MasteringVoice masteringVoice; + private AudioBuffer audioBuffer; + private SourceVoice sourceVoice; + private string fileName; public string FileName { @@ -31,36 +29,35 @@ namespace CodeWalker.Forms } public string FilePath { get; set; } - private bool Playing = false; + private enum PlayerState { Stopped, Playing, Paused }; + private PlayerState playerState = PlayerState.Stopped; + private Stopwatch playtime; + private float trackLength; public AwcForm() { InitializeComponent(); + + playtime = new Stopwatch(); } - - private void UpdateFormTitle() { Text = fileName + " - AWC Player - CodeWalker by dexyfex"; } - public void LoadAwc(AwcFile awc) { Awc = awc; DetailsPropertyGrid.SelectedObject = awc; - //MainTabControl.SelectedTab = DetailsTabPage;//remove this - fileName = awc?.Name; if (string.IsNullOrEmpty(fileName)) { fileName = awc?.FileEntry?.Name; } - PlayListView.Items.Clear(); if (awc.Audios != null) { @@ -76,79 +73,129 @@ namespace CodeWalker.Forms UpdateFormTitle(); } + private void Stop() + { + if (playerState != PlayerState.Stopped) + { + sourceVoice.DestroyVoice(); + sourceVoice.Dispose(); + audioBuffer.Stream.Dispose(); + SetPlayerState(PlayerState.Stopped); + } + } + + private void SetPlayerState(PlayerState newState) + { + if (playerState != newState) + { + switch (newState) + { + case PlayerState.Playing: + if (playerState == PlayerState.Stopped) + playtime.Reset(); + playtime.Start(); + break; + default: + playtime.Stop(); + break; + } + + playerState = newState; + } + } + + private void InitializeAudio(AwcAudio audio) + { + trackLength = audio.Length; + + if (xAudio2 == null) + { + xAudio2 = new XAudio2(); + masteringVoice = new MasteringVoice(xAudio2); + } + + Stream wavStream = audio.GetWavStream(); + SoundStream soundStream = new SoundStream(wavStream); + audioBuffer = new AudioBuffer + { + Stream = soundStream.ToDataStream(), + AudioBytes = (int)soundStream.Length, + Flags = BufferFlags.EndOfStream + }; + soundStream.Close(); + wavStream.Close(); + + sourceVoice = new SourceVoice(xAudio2, soundStream.Format, true); + sourceVoice.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo); + sourceVoice.SetVolume((float)VolumeTrackBar.Value / 100); + } private void Play() { - if (PlayListView.SelectedItems.Count != 1) return; + Stop(); - var item = PlayListView.SelectedItems[0]; - var audio = item.Tag as AwcAudio; + if (PlayListView.SelectedItems.Count == 1) + { + var item = PlayListView.SelectedItems[0]; + var audio = item.Tag as AwcAudio; - if (audio == null) return; - - - - //see https://github.com/sharpdx/SharpDX-Samples/blob/master/Desktop/XAudio2/PlaySound/Program.cs - //see https://github.com/sharpdx/SharpDX-Samples/blob/master/Desktop/XAudio2/AudioPlayerApp/AudioPlayer.cs - - //var mstrm = new MemoryStream(audio.Data); - //var sstrm = new SoundStream(mstrm); - //SourceVoice sv=new SourceVoice() - var mstrm = audio.GetWavStream(); - - ////var mdata = ((MemoryStream)mstrm).GetBuffer(); - ////File.WriteAllBytes("C:\\test2.wav", mdata); - ////return; - - //var sstrm = new SoundStream(mstrm); - //var waveFormat = sstrm.Format; - //var buffer = new AudioBuffer - //{ - // Stream = sstrm.ToDataStream(), - // AudioBytes = (int)sstrm.Length, - // Flags = BufferFlags.EndOfStream - //}; - //sstrm.Close(); - - - //var xaudio2 = new XAudio2();//cache this... - //var masteringVoice = new MasteringVoice(xaudio2);//cache this... - //var sourceVoice = new SourceVoice(xaudio2, waveFormat, true); - ////sourceVoice.BufferEnd += (context) => Console.WriteLine(" => event received: end of buffer"); - //sourceVoice.SubmitSourceBuffer(buffer, sstrm.DecodedPacketsInfo); - //sourceVoice.Start(); - //while (sourceVoice.State.BuffersQueued > 0) // && !IsKeyPressed(ConsoleKey.Escape)) - //{ - // Thread.Sleep(10); - //} - //sourceVoice.DestroyVoice(); - //sourceVoice.Dispose(); - //buffer.Stream.Dispose(); - - //masteringVoice.Dispose();//on form exit? - //xaudio2.Dispose();//on form exit? - - - - Playing = true; + if (audio != null) + { + InitializeAudio(audio); + sourceVoice.Start(); + SetPlayerState(PlayerState.Playing); + } + } } + private void PlayPrevious() + { + Stop(); + if (PlayListView.SelectedIndices.Count > 0) + { + var nextIndex = PlayListView.SelectedIndices[0] - 1; + if (nextIndex >= 0) + { + PlayListView.Items[nextIndex].Selected = true; + PlayListView.Items[nextIndex].Focused = true; + Play(); + } + } + } + + private void PlayNext() + { + Stop(); + if (PlayListView.SelectedIndices.Count > 0) + { + var nextIndex = PlayListView.SelectedIndices[0] + 1; + if (nextIndex < PlayListView.Items.Count) + { + PlayListView.Items[nextIndex].Selected = true; + PlayListView.Items[nextIndex].Focused = true; + Play(); + } + } + } + private void Pause() { - - Playing = false; + if (playerState == PlayerState.Playing) + { + sourceVoice.Stop(); + SetPlayerState(PlayerState.Paused); + } } - private void Prev() - { + private void Resume() + { + if (playerState == PlayerState.Paused) + { + sourceVoice.Start(); + SetPlayerState(PlayerState.Playing); + } } - private void Next() - { - } - - - private void PositionTrackBar_Scroll(object sender, EventArgs e) { @@ -156,41 +203,66 @@ namespace CodeWalker.Forms private void PlayButton_Click(object sender, EventArgs e) { - if (Playing) Pause(); - else Play(); + switch (playerState) + { + case PlayerState.Stopped: + Play(); + break; + case PlayerState.Playing: + Pause(); + break; + case PlayerState.Paused: + Resume(); + break; + } } private void PrevButton_Click(object sender, EventArgs e) { - Prev(); + PlayPrevious(); } private void NextButton_Click(object sender, EventArgs e) { - Next(); - } - - private void VolumeButton_Click(object sender, EventArgs e) - { - + PlayNext(); } private void VolumeTrackBar_Scroll(object sender, EventArgs e) { - - } + if (playerState == PlayerState.Playing) + sourceVoice.SetVolume((float)VolumeTrackBar.Value / 100); + } + + private void Timer_Tick(object sender, EventArgs e) + { + if (playerState != PlayerState.Stopped) + { + int playedMs = (int)playtime.Elapsed.TotalMilliseconds; + int totalMs = (int)(trackLength * 1000); + PositionTrackBar.Maximum = totalMs; + PositionTrackBar.Value = playedMs < totalMs ? playedMs : totalMs; + } + else + { + PositionTrackBar.Value = 0; + } + } + + private void PlayListView_DoubleClick(object sender, EventArgs e) + { + if (playerState == PlayerState.Playing) + Stop(); + Play(); + } + + private void AwcForm_FormClosing(object sender, FormClosingEventArgs e) + { + Stop(); + if (xAudio2 != null) + { + masteringVoice.Dispose(); + xAudio2.Dispose(); + } + } } - - - - - - - public class AudioPlayer - { - - } - - - } diff --git a/Forms/AwcForm.resx b/Forms/AwcForm.resx index 1431f6b..3badf1f 100644 --- a/Forms/AwcForm.resx +++ b/Forms/AwcForm.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + diff --git a/GameFiles/FileTypes/AwcFile.cs b/GameFiles/FileTypes/AwcFile.cs index c39649b..aeb64d6 100644 --- a/GameFiles/FileTypes/AwcFile.cs +++ b/GameFiles/FileTypes/AwcFile.cs @@ -414,9 +414,9 @@ namespace CodeWalker.GameFiles [TC(typeof(EXP))] public class AwcFormatChunk { public uint Samples { get; set; } - public int UnkMinusOne { get; set; } + public int LoopPoint { get; set; } public ushort SamplesPerSecond { get; set; } - public ushort Unk1 { get; set; } + public short Headroom { get; set; } public ushort Unk2 { get; set; } public ushort Unk3 { get; set; } public ushort Unk4 { get; set; } @@ -426,9 +426,9 @@ namespace CodeWalker.GameFiles public AwcFormatChunk(DataReader r) { Samples = r.ReadUInt32(); - UnkMinusOne = r.ReadInt32(); + LoopPoint = r.ReadInt32(); SamplesPerSecond = r.ReadUInt16(); - Unk1 = r.ReadUInt16(); + Headroom = r.ReadInt16(); Unk2 = r.ReadUInt16(); Unk3 = r.ReadUInt16(); Unk4 = r.ReadUInt16(); @@ -443,7 +443,7 @@ namespace CodeWalker.GameFiles public override string ToString() { - return Unk1.ToString() + ", " + Unk6.ToString() + ": " + Samples.ToString() + " samples, " + SamplesPerSecond.ToString() + " samples/sec"; + return Headroom.ToString() + ", " + Unk6.ToString() + ": " + Samples.ToString() + " samples, " + SamplesPerSecond.ToString() + " samples/sec"; } } @@ -503,14 +503,20 @@ namespace CodeWalker.GameFiles return fmt + ((hz > 0) ? (", " + hz.ToString() + " Hz") : ""); } } + + public float Length + { + get + { + return Format == null ? 0 : (float)Format.Samples / Format.SamplesPerSecond; + } + } + public string LengthStr { get { - if (Format == null) return "0:00"; - float sec = (float)Format.Samples / Format.SamplesPerSecond; - TimeSpan ts = TimeSpan.FromSeconds(sec); - return ts.ToString("m\\:ss"); + return TimeSpan.FromSeconds(Length).ToString("m\\:ss"); } } @@ -534,8 +540,8 @@ namespace CodeWalker.GameFiles public Stream GetWavStream() { - MemoryStream ms = new MemoryStream(); - BinaryWriter w = new BinaryWriter(ms); + MemoryStream stream = new MemoryStream(); + BinaryWriter w = new BinaryWriter(stream); //see http://icculus.org/SDL_sound/downloads/external_documentation/wavecomp.htm @@ -543,70 +549,38 @@ namespace CodeWalker.GameFiles //see https://msdn.microsoft.com/en-us/library/windows/desktop/ff538799(v=vs.85).aspx - int sampleCount = SampleCount; int samplesPerSec = SamplesPerSecond; - //short sampleSize = (short)((BitsPerSample / 8) * Channels);//2 - //int avgBytesPerSec = sampleSize * samplesPerSec; - short blockAlign = 512; - short samplesPerBlock = (short)((((blockAlign - (7 * Channels)) * 8) / (BitsPerSample * Channels)) + 2); - int avgBytesPerSec = ((samplesPerSec / samplesPerBlock) * blockAlign); - - w.Write("RIFF".ToCharArray()); - w.Write(0); //file size written later... - w.Write("WAVE".ToCharArray()); - w.Write("fmt ".ToCharArray()); - w.Write(50); //(PCM:16) //header size - w.Write((short)2); //pcm format tag 1=PCM, 2=ADPCM - w.Write(Channels); - w.Write(samplesPerSec); - w.Write(avgBytesPerSec); - w.Write(blockAlign);// sampleSize); - w.Write(BitsPerSample); - w.Write((short)32);//extra byte count for WAVEFORMATEX - - w.Write(samplesPerBlock); - w.Write((short)7);//num coefficients - w.Write((short)256); //coeff 0 - w.Write((short)0); - w.Write((short)512); //coeff 1 - w.Write((short)-256); - w.Write((short)0); //coeff 2 - w.Write((short)0); - w.Write((short)192); //coeff 3 - w.Write((short)64); - w.Write((short)240); //coeff 4 - w.Write((short)0); - w.Write((short)460); //coeff 5 - w.Write((short)-208); - w.Write((short)392); //coeff 6 - w.Write((short)-232); + Channels = 1; + BitsPerSample = 16; + int byteRate = samplesPerSec * Channels * BitsPerSample / 8; + short blockAlign = (short)(Channels * BitsPerSample / 8); + + // RIFF chunk + var fileSize = 4 + 24 + 8 + Data.Length; + w.Write("RIFF".ToCharArray()); // 0x00 - "RIFF" magic + w.Write(fileSize); // 0x04 - file size + w.Write("WAVE".ToCharArray()); // 0x08 - "WAVE" magic + // fmt sub-chunk + w.Write("fmt ".ToCharArray()); // 0x0C - "fmt " magic + w.Write(16); // 0x10 - header size (16 bytes) + w.Write((short)1); // 0x14 - audio format (1=PCM) + w.Write(Channels); // 0x16 - number of channels + w.Write(samplesPerSec); // 0x18 + w.Write(byteRate); // 0x1C + w.Write(blockAlign);// sampleSize); // 0x20 + w.Write(BitsPerSample); // 0x22 + + // data sub-chunk w.Write("data".ToCharArray()); - w.Write(0); //data size written later... - - if (sampleCount != 0) - { - - //var sc = sampleCount * sampleSize; - var datalen = Data.Length; - w.Write(Data); - } - else - { - w.Write(Data); - } - - ms.Position = 4; - w.Write((int)ms.Length - 8); - - ms.Position = 74;// 40; - w.Write((int)ms.Length - 78);// 44); + w.Write(Data.Length); + w.Write(Data); w.Flush(); - ms.Position = 0; - return ms; + stream.Position = 0; + return stream; }