diff --git a/ClassicalSharp.sln b/ClassicalSharp.sln index 7dffc9c28..728f84106 100644 --- a/ClassicalSharp.sln +++ b/ClassicalSharp.sln @@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropPatcher", "InteropPa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Launcher2", "Launcher2\Launcher2.csproj", "{3E84ACC1-27B4-401B-A359-6AAE4DF6C9B5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpWave", "SharpWave\SharpWave.csproj", "{77EA9D1E-4995-4D05-A9C7-29173CB5DC72}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -40,6 +42,14 @@ Global {3E84ACC1-27B4-401B-A359-6AAE4DF6C9B5}.Debug_DX|Any CPU.ActiveCfg = Debug|Any CPU {3E84ACC1-27B4-401B-A359-6AAE4DF6C9B5}.Release_DX|Any CPU.Build.0 = Release|Any CPU {3E84ACC1-27B4-401B-A359-6AAE4DF6C9B5}.Release_DX|Any CPU.ActiveCfg = Release|Any CPU + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72}.Release|Any CPU.Build.0 = Release|Any CPU + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72}.Debug_DX|Any CPU.Build.0 = Debug|Any CPU + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72}.Debug_DX|Any CPU.ActiveCfg = Debug|Any CPU + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72}.Release_DX|Any CPU.Build.0 = Debug|Any CPU + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72}.Release_DX|Any CPU.ActiveCfg = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ClassicalSharp/Audio/AudioPlayer.Sounds.cs b/ClassicalSharp/Audio/AudioPlayer.Sounds.cs index 5bf8c160d..08cd25a09 100644 --- a/ClassicalSharp/Audio/AudioPlayer.Sounds.cs +++ b/ClassicalSharp/Audio/AudioPlayer.Sounds.cs @@ -1,156 +1,153 @@ -// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -using System; -using System.Threading; -using ClassicalSharp.Events; -using SharpWave; -using SharpWave.Codecs; - -namespace ClassicalSharp.Audio { - - public sealed partial class AudioPlayer { - - Soundboard digBoard, stepBoard; - const int maxSounds = 6; - - public void SetSounds(int volume) { - if (volume > 0) InitSound(); - else DisposeSound(); - } - - void InitSound() { - if (digBoard == null) InitSoundboards(); - monoOutputs = new IAudioOutput[maxSounds]; - stereoOutputs = new IAudioOutput[maxSounds]; - } - - void InitSoundboards() { - digBoard = new Soundboard(); - digBoard.Init("dig_", files); - stepBoard = new Soundboard(); - stepBoard.Init("step_", files); - } - - void PlayBlockSound(object sender, BlockChangedEventArgs e) { - if (e.Block == 0) { - PlayDigSound(BlockInfo.DigSounds[e.OldBlock]); - } else if (!game.ClassicMode) { - PlayDigSound(BlockInfo.StepSounds[e.Block]); - } - } - - public void PlayDigSound(byte type) { PlaySound(type, digBoard); } - - public void PlayStepSound(byte type) { PlaySound(type, stepBoard); } - - AudioChunk chunk = new AudioChunk(); - void PlaySound(byte type, Soundboard board) { - if (type == SoundType.None || monoOutputs == null) return; - Sound snd = board.PickRandomSound(type); - if (snd == null) return; - - chunk.Channels = snd.Channels; - chunk.BitsPerSample = snd.BitsPerSample; - chunk.BytesOffset = 0; - chunk.BytesUsed = snd.Data.Length; - chunk.Data = snd.Data; - - float volume = game.SoundsVolume / 100.0f; - if (board == digBoard) { - if (type == SoundType.Metal) chunk.SampleRate = (snd.SampleRate * 6) / 5; - else chunk.SampleRate = (snd.SampleRate * 4) / 5; - } else { - volume *= 0.50f; - - if (type == SoundType.Metal) chunk.SampleRate = (snd.SampleRate * 7) / 5; - else chunk.SampleRate = snd.SampleRate; - } - - if (snd.Channels == 1) { - PlayCurrentSound(monoOutputs, volume); - } else if (snd.Channels == 2) { - PlayCurrentSound(stereoOutputs, volume); - } - } - - IAudioOutput firstSoundOut; - void PlayCurrentSound(IAudioOutput[] outputs, float volume) { - for (int i = 0; i < monoOutputs.Length; i++) { - IAudioOutput output = outputs[i]; - if (output == null) output = MakeSoundOutput(outputs, i); - if (!output.DoneRawAsync()) continue; - - LastChunk l = output.Last; - if (l.Channels == 0 || (l.Channels == chunk.Channels && l.BitsPerSample == chunk.BitsPerSample - && l.SampleRate == chunk.SampleRate)) { - PlaySound(output, volume); return; - } - } - - // This time we try to play the sound on all possible devices, - // even if it requires the expensive case of recreating a device - for (int i = 0; i < monoOutputs.Length; i++) { - IAudioOutput output = outputs[i]; - if (!output.DoneRawAsync()) continue; - - PlaySound(output, volume); return; - } - } - - - IAudioOutput MakeSoundOutput(IAudioOutput[] outputs, int i) { - IAudioOutput output = GetPlatformOut(); - output.Create(1, firstSoundOut); - if (firstSoundOut == null) - firstSoundOut = output; - - outputs[i] = output; - return output; - } - - void PlaySound(IAudioOutput output, float volume) { - try { - output.SetVolume(volume); - output.PlayRawAsync(chunk); - } catch (InvalidOperationException ex) { - ErrorHandler.LogError("AudioPlayer.PlayCurrentSound()", ex); - if (ex.Message == "No audio devices found") - game.Chat.Add("&cNo audio devices found, disabling sounds."); - else - game.Chat.Add("&cAn error occured when trying to play sounds, disabling sounds."); - - SetSounds(0); - game.SoundsVolume = 0; - } - } - - void DisposeSound() { - DisposeOutputs(ref monoOutputs); - DisposeOutputs(ref stereoOutputs); - if (firstSoundOut != null) { - firstSoundOut.Dispose(); - firstSoundOut = null; - } - } - - void DisposeOutputs(ref IAudioOutput[] outputs) { - if (outputs == null) return; - bool soundPlaying = true; - - while (soundPlaying) { - soundPlaying = false; - for (int i = 0; i < outputs.Length; i++) { - if (outputs[i] == null) continue; - soundPlaying |= !outputs[i].DoneRawAsync(); - } - if (soundPlaying) - Thread.Sleep(1); - } - - for (int i = 0; i < outputs.Length; i++) { - if (outputs[i] == null || outputs[i] == firstSoundOut) continue; - outputs[i].Dispose(); - } - outputs = null; - } - } -} +// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 +using System; +using System.Threading; +using ClassicalSharp.Events; +using SharpWave; +using SharpWave.Codecs; + +namespace ClassicalSharp.Audio { + + public sealed partial class AudioPlayer { + + Soundboard digBoard, stepBoard; + const int maxSounds = 6; + + public void SetSounds(int volume) { + if (volume > 0) InitSound(); + else DisposeSound(); + } + + void InitSound() { + if (digBoard == null) InitSoundboards(); + monoOutputs = new IAudioOutput[maxSounds]; + stereoOutputs = new IAudioOutput[maxSounds]; + } + + void InitSoundboards() { + digBoard = new Soundboard(); + digBoard.Init("dig_", files); + stepBoard = new Soundboard(); + stepBoard.Init("step_", files); + } + + void PlayBlockSound(object sender, BlockChangedEventArgs e) { + if (e.Block == 0) { + PlayDigSound(BlockInfo.DigSounds[e.OldBlock]); + } else if (!game.ClassicMode) { + PlayDigSound(BlockInfo.StepSounds[e.Block]); + } + } + + public void PlayDigSound(byte type) { PlaySound(type, digBoard); } + + public void PlayStepSound(byte type) { PlaySound(type, stepBoard); } + + AudioFormat format; + AudioChunk chunk = new AudioChunk(); + void PlaySound(byte type, Soundboard board) { + if (type == SoundType.None || monoOutputs == null) return; + Sound snd = board.PickRandomSound(type); + if (snd == null) return; + + format = snd.Format; + chunk.Data = snd.Data; + chunk.Length = snd.Data.Length; + + float volume = game.SoundsVolume / 100.0f; + if (board == digBoard) { + if (type == SoundType.Metal) format.SampleRate = (format.SampleRate * 6) / 5; + else format.SampleRate = (format.SampleRate * 4) / 5; + } else { + volume *= 0.50f; + if (type == SoundType.Metal) format.SampleRate = (format.SampleRate * 7) / 5; + } + + if (format.Channels == 1) { + PlayCurrentSound(monoOutputs, volume); + } else if (format.Channels == 2) { + PlayCurrentSound(stereoOutputs, volume); + } + } + + IAudioOutput firstSoundOut; + void PlayCurrentSound(IAudioOutput[] outputs, float volume) { + for (int i = 0; i < monoOutputs.Length; i++) { + IAudioOutput output = outputs[i]; + if (output == null) { + outputs[i] = GetPlatformOut(); + output = outputs[i]; + } + + if (!output.IsFinished()) continue; + AudioFormat fmt = output.Format; + if (fmt.Channels == 0 || fmt.Equals(format)) { + PlaySound(output, volume); return; + } + } + + // This time we try to play the sound on all possible devices, + // even if it requires the expensive case of recreating a device + for (int i = 0; i < monoOutputs.Length; i++) { + IAudioOutput output = outputs[i]; + if (!output.IsFinished()) continue; + + PlaySound(output, volume); return; + } + } + + + IAudioOutput MakeSoundOutput(IAudioOutput[] outputs, int i) { + IAudioOutput output = GetPlatformOut(); + output.Create(1); + outputs[i] = output; + return output; + } + + void PlaySound(IAudioOutput output, float volume) { + try { + output.SetVolume(volume); + output.SetFormat(format); + output.PlayData(0, chunk); + } catch (InvalidOperationException ex) { + ErrorHandler.LogError("AudioPlayer.PlayCurrentSound()", ex); + if (ex.Message == "No audio devices found") + game.Chat.Add("&cNo audio devices found, disabling sounds."); + else + game.Chat.Add("&cAn error occured when trying to play sounds, disabling sounds."); + + SetSounds(0); + game.SoundsVolume = 0; + } + } + + void DisposeSound() { + DisposeOutputs(ref monoOutputs); + DisposeOutputs(ref stereoOutputs); + if (firstSoundOut != null) { + firstSoundOut.Dispose(); + firstSoundOut = null; + } + } + + void DisposeOutputs(ref IAudioOutput[] outputs) { + if (outputs == null) return; + bool soundPlaying = true; + + while (soundPlaying) { + soundPlaying = false; + for (int i = 0; i < outputs.Length; i++) { + if (outputs[i] == null) continue; + soundPlaying |= !outputs[i].IsFinished(); + } + if (soundPlaying) + Thread.Sleep(1); + } + + for (int i = 0; i < outputs.Length; i++) { + if (outputs[i] == null) continue; + outputs[i].Dispose(); + } + outputs = null; + } + } +} diff --git a/ClassicalSharp/Audio/AudioPlayer.cs b/ClassicalSharp/Audio/AudioPlayer.cs index e9573b84b..488bd0a14 100644 --- a/ClassicalSharp/Audio/AudioPlayer.cs +++ b/ClassicalSharp/Audio/AudioPlayer.cs @@ -139,7 +139,7 @@ namespace ClassicalSharp.Audio { void DisposeOf(ref IAudioOutput output, ref Thread thread) { if (output == null) return; - output.Stop(); + output.pendingStop = true; thread.Join(); output.Dispose(); diff --git a/ClassicalSharp/Audio/Soundboard.cs b/ClassicalSharp/Audio/Soundboard.cs index 6655ab002..7745facd0 100644 --- a/ClassicalSharp/Audio/Soundboard.cs +++ b/ClassicalSharp/Audio/Soundboard.cs @@ -2,11 +2,12 @@ using System; using System.Collections.Generic; using System.IO; +using SharpWave; namespace ClassicalSharp.Audio { public class Sound { - public int SampleRate, BitsPerSample, Channels; + public AudioFormat Format; public byte[] Data; } @@ -97,12 +98,12 @@ namespace ClassicalSharp.Audio { static void HandleFormat(BinaryReader r, ref int size, Sound snd) { if (r.ReadUInt16() != 1) throw new InvalidDataException("Only PCM audio is supported."); - size -= 2; - snd.Channels = r.ReadUInt16(); size -= 2; - snd.SampleRate = r.ReadInt32(); size -= 4; - r.ReadInt32(); r.ReadUInt16(); size -= 6; - snd.BitsPerSample = r.ReadUInt16(); size -= 2; + snd.Format.Channels = r.ReadUInt16(); + snd.Format.SampleRate = r.ReadInt32(); + r.ReadInt32(); r.ReadUInt16(); + snd.Format.BitsPerSample = r.ReadUInt16(); + size -= 16; } unsafe string GetFourCC(BinaryReader r) { diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index 41247d31b..29fcb6b44 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -69,9 +69,6 @@ - - SharpWave.dll - @@ -450,6 +447,10 @@ {4A4110EE-21CA-4715-AF67-0C8B7CE0642F} InteropPatcher + + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72} + SharpWave + diff --git a/ClassicalSharp/SharpWave.dll b/ClassicalSharp/SharpWave.dll index db9479d36..8ee223677 100644 Binary files a/ClassicalSharp/SharpWave.dll and b/ClassicalSharp/SharpWave.dll differ diff --git a/Launcher2/Launcher2.csproj b/Launcher2/Launcher2.csproj index 6e895e1a2..d6dc99c66 100644 --- a/Launcher2/Launcher2.csproj +++ b/Launcher2/Launcher2.csproj @@ -48,9 +48,6 @@ Project - - ..\ClassicalSharp\SharpWave.dll - @@ -265,5 +262,11 @@ + + + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72} + SharpWave + + \ No newline at end of file diff --git a/Launcher2/Patcher/SoundPatcher.cs b/Launcher2/Patcher/SoundPatcher.cs index 136ad5bf1..02007534c 100644 --- a/Launcher2/Patcher/SoundPatcher.cs +++ b/Launcher2/Patcher/SoundPatcher.cs @@ -77,15 +77,16 @@ namespace Launcher.Patcher { WriteFourCC(w, "RIFF"); w.Write((int)(stream.Length - 8)); WriteFourCC(w, "WAVE"); + AudioFormat format = data.Format; WriteFourCC(w, "fmt "); w.Write(16); w.Write((ushort)1); // audio format, PCM - w.Write((ushort)data.Last.Channels); - w.Write(data.Last.SampleRate); - w.Write((data.Last.SampleRate * data.Last.Channels * data.Last.BitsPerSample) / 8); // byte rate - w.Write((ushort)((data.Last.Channels * data.Last.BitsPerSample) / 8)); // block align - w.Write((ushort)data.Last.BitsPerSample); + w.Write((ushort)format.Channels); + w.Write(format.SampleRate); + w.Write((format.SampleRate * format.Channels * format.BitsPerSample) / 8); // byte rate + w.Write((ushort)((format.Channels * format.BitsPerSample) / 8)); // block align + w.Write((ushort)format.BitsPerSample); WriteFourCC(w, "data"); w.Write((int)(stream.Length - 44)); diff --git a/SharpWave/ICodec.cs b/SharpWave/ICodec.cs new file mode 100644 index 000000000..3eb89e284 --- /dev/null +++ b/SharpWave/ICodec.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace SharpWave.Codecs { + + public interface ICodec { + AudioFormat ReadHeader(Stream source); + IEnumerable StreamData(Stream source); + string Name { get; } + } + + public sealed class AudioChunk { + public byte[] Data; + public int Length; + } +} diff --git a/SharpWave/IMediaContainer.cs b/SharpWave/IMediaContainer.cs new file mode 100644 index 000000000..e0b20483d --- /dev/null +++ b/SharpWave/IMediaContainer.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using SharpWave.Codecs; + +namespace SharpWave.Containers { + + public abstract class IMediaContainer : Stream { + protected Stream stream; + public IMediaContainer(Stream source) { stream = source; } + + public abstract void ReadMetadata(); + + public abstract ICodec GetAudioCodec(); + + #region Stream implementation + + public override bool CanRead { get { return true; } } + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return false; } } + public override void Flush() { stream.Flush(); } + public override long Length { get { return stream.Length; } } + + public override long Position { + get { return stream.Position; } + set { stream.Position = value; } + } + + public override long Seek( long offset, SeekOrigin origin ) { + return stream.Seek( offset, origin ); + } + + public override void SetLength( long value ) { + throw new NotImplementedException( "SharpWave is only a decoder" ); + } + + public override int Read(byte[] buffer, int offset, int count) { + return stream.Read(buffer, offset, count); + } + + public override int ReadByte() { return stream.ReadByte(); } + + public override void Write( byte[] buffer, int offset, int count ) { + throw new NotImplementedException( "SharpWave is only a decoder" ); + } + + #endregion + } +} diff --git a/SharpWave/Properties/AssemblyInfo.cs b/SharpWave/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..f37b6f27c --- /dev/null +++ b/SharpWave/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +#region Using directives + +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +#endregion + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SharpWave")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SharpWave")] +[assembly: AssemblyCopyright("Copyright 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// This sets the default COM visibility of types in the assembly to invisible. +// If you need to expose a type to COM, use [ComVisible(true)] on that type. +[assembly: ComVisible(false)] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all the values or you can use the default the Revision and +// Build Numbers by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.*")] diff --git a/SharpWave/SharpWave.csproj b/SharpWave/SharpWave.csproj new file mode 100644 index 000000000..a87ad73e6 --- /dev/null +++ b/SharpWave/SharpWave.csproj @@ -0,0 +1,87 @@ + + + + {77EA9D1E-4995-4D05-A9C7-29173CB5DC72} + Debug + AnyCPU + Library + SharpWave + SharpWave + v2.0 + Properties + True + False + 4 + false + + + + + AnyCPU + False + Auto + 4194304 + 4096 + + + bin\Debug\ + true + Full + False + False + DEBUG;TRACE + + + bin\Release\ + False + None + True + False + TRACE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + \ No newline at end of file diff --git a/SharpWave/SharpWave.dll.config b/SharpWave/SharpWave.dll.config new file mode 100644 index 000000000..b2d99e8d2 --- /dev/null +++ b/SharpWave/SharpWave.dll.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/SharpWave/Utils/MemUtils.cs b/SharpWave/Utils/MemUtils.cs new file mode 100644 index 000000000..962e147b5 --- /dev/null +++ b/SharpWave/Utils/MemUtils.cs @@ -0,0 +1,35 @@ +using System; + +namespace SharpWave { + + public static class MemUtils { + + static MemUtils() { + use64Bit = IntPtr.Size == 8; + } + static bool use64Bit; + + public static unsafe void memcpy( IntPtr srcPtr, IntPtr dstPtr, int bytes ) { + byte* srcByte, dstByte; + if( use64Bit ) { + ulong* srcLong = (ulong*)srcPtr, dstLong = (ulong*)dstPtr; + while( bytes >= 8 ) { + *dstLong++ = *srcLong++; + bytes -= 8; + } + srcByte = (byte*)srcLong; dstByte = (byte*)dstLong; + } else { + uint* srcInt = (uint*)srcPtr, dstInt = (uint*)dstPtr; + while( bytes >= 4 ) { + *dstInt++ = *srcInt++; + bytes -= 4; + } + srcByte = (byte*)srcInt; dstByte = (byte*)dstInt; + } + + for( int i = 0; i < bytes; i++ ) { + *dstByte++ = *srcByte++; + } + } + } +} \ No newline at end of file diff --git a/SharpWave/VolumeMixer.cs b/SharpWave/VolumeMixer.cs new file mode 100644 index 000000000..ec980917c --- /dev/null +++ b/SharpWave/VolumeMixer.cs @@ -0,0 +1,39 @@ +using System; + +namespace SharpWave { + + public unsafe static class VolumeMixer { + + public static void Mix16( short* samples, int numSamples, int volumePercent ) { + int numBulkSamples = numSamples & ~0x07; + + // Unrolled loop, do 8 samples per iteration + for( int i = 0; i < numBulkSamples; i += 8 ) { + samples[0] = (short)(samples[0] * volumePercent / 100); + samples[1] = (short)(samples[1] * volumePercent / 100); + samples[2] = (short)(samples[2] * volumePercent / 100); + samples[3] = (short)(samples[3] * volumePercent / 100); + + samples[4] = (short)(samples[4] * volumePercent / 100); + samples[5] = (short)(samples[5] * volumePercent / 100); + samples[6] = (short)(samples[6] * volumePercent / 100); + samples[7] = (short)(samples[7] * volumePercent / 100); + + samples += 8; + } + + // Fixup the few last samples + for( int i = numBulkSamples; i < numSamples; i++ ) { + samples[0] = (short)(samples[0] * volumePercent / 100); + samples++; + } + } + + public static void Mix8( byte* samples, int numSamples, int volumePercent ) { + for( int i = 0; i < numSamples; i++ ) { + samples[0] = (byte)(127 + (samples[0] - 127) * volumePercent / 100); + samples++; + } + } + } +} diff --git a/SharpWave/csvorbis/Block.cs b/SharpWave/csvorbis/Block.cs new file mode 100644 index 000000000..db6f0ee1b --- /dev/null +++ b/SharpWave/csvorbis/Block.cs @@ -0,0 +1,90 @@ +/* csvorbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +using System; +using csogg; + +namespace csvorbis +{ + public class Block + { + //necessary stream state for linking to the framing abstraction + internal float[][] pcm = new float[0][]; // this is a pointer into local storage + internal csBuffer opb = new csBuffer(); + + internal int lW; + internal int W; + internal int nW; + internal int pcmend; + internal int mode; + + internal int eofflag; + internal long granulepos; + internal long sequence; + internal DspState vd; // For read-only access of configuration + + public void init(DspState vd) { + this.vd = vd; + } + + public int synthesis(Packet op) + { + Info vi = vd.vi; + opb.readinit(op.packet_base, op.packet, op.bytes); + opb.read(1); + // read our mode and pre/post windowsize + mode = opb.read(vd.modebits); + W = vi.mode_param[mode].blockflag; + if(W != 0) { + lW = opb.read(1); nW = opb.read(1); + } else { + lW = 0; nW = 0; + } + + // more setup + granulepos = op.granulepos; + sequence = op.packetno-3; // first block is third packet + eofflag = op.e_o_s; + + // alloc pcm passback storage + pcmend = vi.blocksizes[W]; + if(pcm.Length + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +using System; +using System.Runtime.CompilerServices; +using csogg; + +namespace csvorbis +{ + class CodeBook + { + internal int dim; // codebook dimensions (elements per vector) + internal int entries; // codebook entries + internal StaticCodeBook c = new StaticCodeBook(); + + internal float[] valuelist; // list of dim*entries actual entry values + internal DecodeAux decode_tree; + + internal int[] t = new int[15]; // decodevs_add is synchronized for re-using t. + + internal int decodevs_add(float[]a, int offset, csBuffer b, int n) + { + int step = n/dim; + int entry; + int i,j,o; + + if(t.Length8) + { + for(i = 0;i + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +using System; +using csogg; +using csvorbis; + +namespace csvorbis +{ + public class DspState + { + static float M_PI = 3.1415926539f; + + internal Info vi; + internal int modebits; + + float[][] pcm; + //float[][] pcmret; + int pcm_storage; + int pcm_current; + int pcm_returned; + + int lW; + int W; + int centerW; + + long granulepos; + public long sequence; + + // local lookup storage + //!! Envelope ve = new Envelope(); // envelope + internal float[][][][] wnd; // block, leadin, leadout + //vorbis_look_transform **transform[2]; // block, type + internal Object[] transform; + internal CodeBook[] fullbooks; + // backend lookups are tied to the mode, not the backend or naked mapping + internal Object[] mode; + + public DspState() + { + transform = new Object[2]; + wnd = new float[2][][][]; + wnd[0] = new float[2][][]; + wnd[0][0] = new float[2][]; + wnd[0][1] = new float[2][]; + wnd[0][0][0] = new float[2]; + wnd[0][0][1] = new float[2]; + wnd[0][1][0] = new float[2]; + wnd[0][1][1] = new float[2]; + wnd[1] = new float[2][][]; + wnd[1][0] = new float[2][]; + wnd[1][1] = new float[2][]; + wnd[1][0][0] = new float[2]; + wnd[1][0][1] = new float[2]; + wnd[1][1][0] = new float[2]; + wnd[1][1][1] = new float[2]; + } + + internal static float[] window(int wnd, int left, int right) + { + float[] ret = new float[wnd]; + // The 'vorbis window' (window 0) is sin(sin(x)*sin(x)*2pi) + + int leftbegin = wnd/4-left/2; + int rightbegin = wnd-wnd/4-right/2; + + for(int i = 0;ivi.blocksizes[1]/2 && pcm_returned>8192) + { + // don't shift too much; we need to have a minimum PCM buffer of + // 1/2 long block + + int shiftPCM = centerW-vi.blocksizes[1]/2; + shiftPCM = (pcm_returnedpcm_storage) + { + // expand the storage + pcm_storage = endW+vi.blocksizes[1]; + for(int i = 0;igranulepos. + // + // This is not foolproof! It will be confused if we begin + // decoding at the last page after a seek or hole. In that case, + // we don't have a starting point to judge where the last frame + // is. For this reason, vorbisfile will always try to make sure + // it reads the last two marked pages in proper sequence + + if(granulepos == -1) + { + granulepos = vb.granulepos; + } + else + { + granulepos += (_centerW-centerW); + if(vb.granulepos != -1 && granulepos != vb.granulepos) + { + if(granulepos>vb.granulepos && vb.eofflag != 0) + { + // partial last frame. Strip the padding off + _centerW = _centerW - (int)(granulepos-vb.granulepos); + }// else{ Shouldn't happen *unless* the bitstream is out of + // spec. Either way, believe the bitstream } + granulepos = vb.granulepos; + } + } + + // Update, cleanup + centerW = _centerW; + pcm_current = endW; + } + + public int synthesis_pcmout(ref float[][] _pcm, int[] index) + { + if(pcm_returnedcenterW) + return; + pcm_returned += bytes; + } + } +} \ No newline at end of file diff --git a/SharpWave/csvorbis/FuncFloor.cs b/SharpWave/csvorbis/FuncFloor.cs new file mode 100644 index 000000000..94c991095 --- /dev/null +++ b/SharpWave/csvorbis/FuncFloor.cs @@ -0,0 +1,560 @@ +/* csvorbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +using System; +using csogg; + +namespace csvorbis +{ + abstract class FuncFloor + { + public static FuncFloor[] floor_P = {null,new Floor1()}; + + public abstract Object unpack(Info vi, csBuffer opb); + public abstract Object look(DspState vd, InfoMode mi, Object i); + public abstract Object inverse1(Block vb, Object i, Object memo); + public abstract int inverse2(Block vb, Object i, Object memo, float[] fout); + } + + class Floor1 : FuncFloor + { + static int VIF_POSIT = 63; + + public override Object unpack(Info vi , csBuffer opb) + { + int count = 0,maxclass = -1,rangebits; + InfoFloor1 info = new InfoFloor1(); + + /* read partitions */ + info.partitions = opb.read(5); /* only 0 to 31 legal */ + for(int j = 0;j= vi.books) + { + //goto err_out; + info.free(); + return(null); + } + for(int k = 0;k<(1<= vi.books) + { + //goto err_out; + info.free(); + return(null); + } + } + } + + /* read the post list */ + info.mult = opb.read(2)+1; /* only 1,2,3,4 legal now */ + rangebits = opb.read(4); + + for(int j = 0,k = 0;j= (1<info.postlist[sortpointer[k]]) + { + foo = sortpointer[k]; + sortpointer[k] = sortpointer[j]; + sortpointer[j] = foo; + } + } + } + + /* points from sort order back to range number */ + for(int j = 0;j<_n;j++) + { + look.forward_index[j] = sortpointer[j]; + } + /* points from range order to sorted position */ + for(int j = 0;j<_n;j++) + { + look.reverse_index[look.forward_index[j]] = j; + } + /* we actually need the post values too */ + for(int j = 0;j<_n;j++) + { + look.sorted_index[j] = info.postlist[look.forward_index[j]]; + } + + + /* quantize values to multiplier spec */ + switch(info.mult) + { + case 1: /* 1024 -> 256 */ + look.quant_q = 256; + break; + case 2: /* 1024 -> 128 */ + look.quant_q = 128; + break; + case 3: /* 1024 -> 86 */ + look.quant_q = 86; + break; + case 4: /* 1024 -> 64 */ + look.quant_q = 64; + break; + default: + look.quant_q = -1; + break; + } + + /* discover our neighbors for decode where we don't use fit flags + (that would push the neighbors outward) */ + for(int j = 0;j<_n-2;j++) + { + int lo = 0; + int hi = 1; + int lx = 0; + int hx = look.n; + int currentx = info.postlist[j+2]; + for(int k = 0;klx && xcurrentx) + { + hi = k; + hx = x; + } + } + look.loneighbor[j] = lo; + look.hineighbor[j] = hi; + } + + return look; + } + + public override Object inverse1(Block vb, Object ii, Object memo) + { + LookFloor1 look = (LookFloor1)ii; + InfoFloor1 info = look.vi; + CodeBook[] books = vb.vd.fullbooks; + + /* unpack wrapped/predicted values from stream */ + if(vb.opb.read(1) == 1) + { + int[] fit_value = null; + if(memo is int[]) + { + fit_value = (int[])memo; + } + if(fit_value == null || fit_value.Length> csubbits); + if(book >= 0) + { + if((fit_value[j+k] = books[book].decode(vb.opb)) == -1) + { + return(null); + } + } + else + { + fit_value[j+k] = 0; + } + } + j += cdim; + } + + /* unwrap positive values and reconsitute via linear interpolation */ + for(int i = 2;i= room) + { + if(hiroom>loroom) + { + val = val-loroom; + } + else + { + val = -1-(val-hiroom); + } + } + else + { + if((val&1) != 0) + { + val = (int)(-((uint)(val+1) >> 1)); + } + else + { + val >>= 1; + } + } + + fit_value[i] = val+predicted; + fit_value[look.loneighbor[i-2]] &= 0x7fff; + fit_value[look.hineighbor[i-2]] &= 0x7fff; + } + else + { + fit_value[i] = predicted|0x8000; + } + } + return(fit_value); + } + + // eop: + // return(NULL); + return(null); + } + + private static int render_point(int x0,int x1,int y0,int y1,int x) + { + y0 &= 0x7fff; /* mask off flag */ + y1 &= 0x7fff; + + int dy = y1-y0; + int adx = x1-x0; + int ady = Math.Abs(dy); + int err = ady*(x-x0); + + int off = (int)(err/adx); + if(dy<0)return(y0-off); + return(y0+off); + } + + public override int inverse2(Block vb, Object i, Object memo, float[] fout) + { + LookFloor1 look = (LookFloor1)i; + InfoFloor1 info = look.vi; + int n = vb.vd.vi.blocksizes[vb.mode]/2; + + if(memo != null) + { + /* render the lines */ + int[] fit_value = (int[] )memo; + int hx = 0, lx = 0; + int ly = fit_value[0]*info.mult; + for(int j = 1;j= adx) + { + err -= adx; + y += sy; + } + else + { + y += bbase; + } + d[x] *= FLOOR_fromdB_LOOKUP[y]; + } + } + } + + class InfoFloor1 + { + const int VIF_POSIT = 63; + const int VIF_CLASS = 16; + const int VIF_PARTS = 31; + + internal int partitions; /* 0 to 31 */ + internal int[] partitionclass = new int[VIF_PARTS]; /* 0 to 15 */ + + internal int[] class_dim = new int[VIF_CLASS]; /* 1 to 8 */ + internal int[] class_subs = new int[VIF_CLASS]; /* 0,1,2,3 (bits: 1< + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +using System; +using csogg; + +namespace csvorbis +{ + abstract class FuncMapping + { + public static FuncMapping[] mapping_P = {new Mapping0()}; + + public abstract Object unpack(Info info , csBuffer buffer); + public abstract Object look(DspState vd, InfoMode vm, Object m); + public abstract int inverse(Block vd, Object lm); + } + + class Mapping0 : FuncMapping + { + public override Object look(DspState vd, InfoMode vm, Object m) + { + Info vi = vd.vi; + LookMapping0 looks = new LookMapping0(); + InfoMapping0 info = looks.map = (InfoMapping0)m; + looks.mode = vm; + + looks.floor_look = new Object[info.submaps]; + looks.residue_look = new Object[info.submaps]; + + looks.floor_func = new FuncFloor[info.submaps]; + looks.residue_func = new FuncResidue[info.submaps]; + + for(int i = 0;i1) { + for(int i = 0;i= 0; i-- ) { + float[] pcmM = vb.pcm[info.coupling_mag[i]]; + float[] pcmA = vb.pcm[info.coupling_ang[i]]; + + for( int j = 0; j < n / 2; j++ ) { + float mag = pcmM[j]; + float ang = pcmA[j]; + + if( mag > 0 ) { + if( ang > 0 ) { + pcmM[j] = mag; + pcmA[j] = mag-ang; + } else { + pcmA[j] = mag; + pcmM[j] = mag+ang; + } + } else { + if( ang > 0 ) { + pcmM[j] = mag; + pcmA[j] = mag+ang; + } else { + pcmA[j] = mag; + pcmM[j] = mag-ang; + } + } + } + } + } + } + + class InfoMapping0 + { + internal int submaps; // <= 16 + internal int[] chmuxlist = new int[256]; // up to 256 channels in a Vorbis stream + + internal int[] timesubmap = new int[16]; // [mux] + internal int[] floorsubmap = new int[16]; // [mux] submap to floors + internal int[] residuesubmap = new int[16];// [mux] submap to residue + internal int[] psysubmap = new int[16]; // [mux]; encode only + + internal int coupling_steps; + internal int[] coupling_mag = new int[256]; + internal int[] coupling_ang = new int[256]; + + internal void free() + { + chmuxlist = null; + timesubmap = null; + floorsubmap = null; + residuesubmap = null; + psysubmap = null; + + coupling_mag = null; + coupling_ang = null; + } + } + + class LookMapping0 + { + internal InfoMode mode; + internal InfoMapping0 map; + internal Object[] floor_look; + internal Object[] residue_look; + + internal FuncFloor[] floor_func; + internal FuncResidue[] residue_func; + + internal int ch; + } +} \ No newline at end of file diff --git a/SharpWave/csvorbis/FuncResidue.cs b/SharpWave/csvorbis/FuncResidue.cs new file mode 100644 index 000000000..985b0ed00 --- /dev/null +++ b/SharpWave/csvorbis/FuncResidue.cs @@ -0,0 +1,312 @@ +/* csvorbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +using System; +using csogg; + +namespace csvorbis +{ + abstract class FuncResidue + { + public static FuncResidue[] residue_P = {new Residue0(), new Residue1(), new Residue2()}; + + public abstract Object unpack(Info vi, csBuffer opb); + public abstract Object look(DspState vd, InfoMode vm, Object vr); + + public abstract int inverse(Block vb, Object vl, float[][] fin, int[] nonzero,int ch); + } + + class Residue0 : FuncResidue + { + public override Object unpack(Info vi, csBuffer opb) + { + int acc = 0; + InfoResidue0 info = new InfoResidue0(); + + info.begin = opb.read(24); + info.end = opb.read(24); + info.grouping = opb.read(24)+1; + info.partitions = opb.read(6)+1; + info.groupbook = opb.read(8); + + for(int j = 0;jmaxstage)maxstage = stages; + look.partbooks[j] = new int[stages]; + for(int k = 0; k + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +using System; +using System.Text; +using csogg; + +namespace csvorbis +{ + class InfoMode + { + internal int blockflag; + internal int windowtype; + internal int transformtype; + internal int mapping; + } + + public class Info + { + private static int OV_ENOTAUDIO = -135; + + public int version, channels, rate; + + // Vorbis supports only short and long blocks, but allows the + // encoder to choose the sizes + internal int[] blocksizes = new int[2]; + + // modes are the primary means of supporting on-the-fly different + // blocksizes, different channel mappings (LR or mid-side), + // different residue backends, etc. Each mode consists of a + // blocksize flag and a mapping (along with the mapping setup + + internal int modes, maps, times, floors, residues, books; + + internal InfoMode[] mode_param = null; + + internal int[] map_type = null; + internal Object[] map_param = null; + + internal int[] floor_type = null; + internal Object[] floor_param = null; + + internal int[] residue_type = null; + internal Object[] residue_param = null; + + internal StaticCodeBook[] book_param = null; + + // used by synthesis, which has a full, alloced vi + public void init() { + rate = 0; + } + + public void clear() { + mode_param = null; + map_param = null; + floor_param = null; + residue_param = null; + book_param = null; + } + + // Header packing/unpacking + void unpack_info(csBuffer opb) { + version = opb.read(32); + channels = opb.read(8); + rate = opb.read(32); + + opb.read(32); // bitrate_upper + opb.read(32); // bitrate_nominal + opb.read(32); // bitrate_lower + + blocksizes[0] = 1<(ref T[] array, int count) { + if(array == null || array.Length != count) { + array = new T[count]; + } + } + + void CheckEntries(ref T[] array, ref int[] types, int count) { + if(array == null || array.Length != count) { + array = new T[count]; + types = new int[count]; + } + } + + public void synthesis_headerin(Comment vc, Packet op) { + if(op == null) return; + + csBuffer opb = new csBuffer(); + opb.readinit(op.packet_base, op.packet, op.bytes); + byte[] buffer = new byte[6]; + int packtype = opb.read(8); + opb.read(buffer, 6); + + if(buffer[0] != 'v' || buffer[1] != 'o' || buffer[2] != 'r' || + buffer[3] != 'b' || buffer[4] != 'i' || buffer[5] != 's') { + throw new InvalidOperationException("Expected vorbis header"); + } + + switch(packtype) { + case 0x01: // least significant *bit* is read first + unpack_info(opb); + break; + case 0x03: // least significant *bit* is read first + vc.unpack(opb); + break; + case 0x05: // least significant *bit* is read first + unpack_books(opb); + break; + default: + // Not a valid vorbis header type + break; + } + } + + public int blocksize(Packet op) { + csBuffer opb = new csBuffer(); + opb.readinit(op.packet_base, op.packet, op.bytes); + + /* Check the packet type */ + if(opb.read(1) != 0) + return(OV_ENOTAUDIO); + + int modebits = VUtils.ilog2(modes); + int mode = opb.read(modebits); + return blocksizes[mode_param[mode].blockflag]; + } + + public String toString() + { + return "version:" + version + ", channels:" + channels + + ", rate:" + rate; + } + } +} diff --git a/SharpWave/csvorbis/Mdct.cs b/SharpWave/csvorbis/Mdct.cs new file mode 100644 index 000000000..304a41e03 --- /dev/null +++ b/SharpWave/csvorbis/Mdct.cs @@ -0,0 +1,249 @@ +/* csvorbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +using System; +using System.Runtime.CompilerServices; +using csogg; + +namespace csvorbis +{ + class Mdct + { + + //static private float cPI3_8 = 0.38268343236508977175f; + //static private float cPI2_8 = 0.70710678118654752441f; + //static private float cPI1_8 = 0.92387953251128675613f; + + int n; + int log2n; + + float[] trig; + int[] bitrev; + + float scale; + + internal void init(int n) + { + bitrev = new int[n/4]; + trig = new float[n+n/4]; + + int n2 = (int)((uint)n >> 1); + log2n = (int)Math.Round(Math.Log(n)/Math.Log(2)); + this.n = n; + + + int AE = 0; + int AO = 1; + int BE = AE+n/2; + int BO = BE+1; + int CE = BE+n/2; + int CO = CE+1; + // trig lookups... + for(int i = 0;i> j) != 0; j++) + if(((((uint)msb>>j))&i) != 0) + acc |= 1 << j; + bitrev[i*2] = ((~acc)&mask); + // bitrev[i*2] = ((~acc)&mask)-1; + bitrev[i*2+1] = acc; + } + } + scale = 4.0f/n; + } + + float[] _x = new float[1024]; + float[] _w = new float[1024]; + + internal void backward(float[] fin, float[] fout) + { + if(_x.Length < n/2){_x = new float[n/2];} + if(_w.Length < n/2){_w = new float[n/2];} + float[] x = _x; + float[] w = _w; + int n2 = (int)((uint)n >> 1); + int n4 = (int)((uint)n >> 2); + int n8 = (int)((uint)n >> 3); + + // rotate + step 1 + int inO = 1; + int xO = 0; + int A = n2; + + for(int i = 0;i> (i+2)); + int k1 = 1 << (i+3); + int wbase = n2-2; + + A = 0; + float[] temp; + + for(int r = 0; r<((uint)k0>>2); r++) + { + int w1 = wbase; + w2 = w1-(k0>>1); + float AEv = trig[A],wA; + float AOv = trig[A+1],wB; + wbase -= 2; + + k0++; + for(int s = 0;s<(2< + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +using System; + +namespace csogg +{ + public class csBuffer + { + private static uint[] mask={ + 0x00000000,0x00000001,0x00000003,0x00000007,0x0000000f, + 0x0000001f,0x0000003f,0x0000007f,0x000000ff,0x000001ff, + 0x000003ff,0x000007ff,0x00000fff,0x00001fff,0x00003fff, + 0x00007fff,0x0000ffff,0x0001ffff,0x0003ffff,0x0007ffff, + 0x000fffff,0x001fffff,0x003fffff,0x007fffff,0x00ffffff, + 0x01ffffff,0x03ffffff,0x07ffffff,0x0fffffff,0x1fffffff, + 0x3fffffff,0x7fffffff,0xffffffff + }; + int ptr = 0; + byte[] buffer = null; + int endbit = 0; + int endbyte = 0; + int storage = 0; + + public void read (byte[] s, int bytes) + { + int i = 0; + while(bytes--!=0) + { + s[i++]=(byte)(read(8)); + } + } + + void reset() + { + ptr = 0; + buffer[0] = (byte)'\0'; + endbit = endbyte = 0; + } + + public void readinit(byte[] buf, int start, int bytes) + { + ptr = start; + buffer = buf; + endbit = endbyte = 0; + storage = bytes; + } + + public int look(int bits) + { + int ret; + uint m = mask[bits]; + + bits += endbit; + + if(endbyte + 4 >= storage) + { + if(endbyte+(bits-1)/8 >= storage) + return (-1); + } + + ret = ((buffer[ptr]) & 0xff) >> endbit; + + if(bits > 8) + { + ret |= ((buffer[ptr+1]) & 0xff) << (8 - endbit); + if(bits > 16) + { + ret |= ((buffer[ptr+2])&0xff) << (16-endbit); + if(bits > 24) + { + ret |= ((buffer[ptr+3])&0xff) << (24-endbit); + if((bits > 32) && (endbit != 0)) + { + ret |= ((buffer[ptr+4])&0xff) << (32-endbit); + } + } + } + } + ret = (int)(m & ret); + return (ret); + } + + public int look1() + { + if(endbyte >= storage) + return(-1); + return((buffer[ptr] >> endbit) & 1); + } + + public void adv(int bits) + { + bits += endbit; + ptr += bits / 8; + endbyte += bits / 8; + endbit = bits & 7; + } + + public void adv1() + { + ++endbit; + if(endbit > 7) + { + endbit = 0; + ptr++; + endbyte++; + } + } + + public int read(int bits) + { + int ret; + uint m=mask[bits]; + + bits += endbit; + + if(endbyte+4 >= storage) + { + ret = -1; + if(endbyte + (bits-1)/8 >= storage) + { + ptr += bits/8; + endbyte += bits/8; + endbit = bits&7; + return(ret); + } + } + + ret = ((buffer[ptr]) & 0xff) >> endbit; + if(bits > 8) + { + ret|=((buffer[ptr+1])&0xff)<<(8-endbit); + if(bits > 16) + { + ret|=((buffer[ptr+2])&0xff)<<(16-endbit); + if(bits > 24) + { + ret|=((buffer[ptr+3])&0xff)<<(24-endbit); + + if((bits > 32) && (endbit != 0)) + { + ret|=((buffer[ptr+4])&0xff)<<(32-endbit); + } + } + } + } + + ret &= (int)m; + + ptr += bits/8; + endbyte += bits/8; + endbit = bits&7; + return(ret); + } + + public int read1() + { + int ret; + if(endbyte>=storage) + { + ret = -1; + endbit++; + if(endbit > 7) + { + endbit = 0; + ptr++; + endbyte++; + } + return(ret); + } + + ret=(buffer[ptr] >> endbit) & 1; + + endbit++; + if(endbit > 7) + { + endbit = 0; + ptr++; + endbyte++; + } + return(ret); + } + + public int bytes() + { + return(endbyte+(endbit+7)/8); + } + + public int bits() + { + return(endbyte*8+endbit); + } + + public byte[] buf() + { + return(buffer); + } + } +} diff --git a/SharpWave/csvorbis/Ogg/Packet.cs b/SharpWave/csvorbis/Ogg/Packet.cs new file mode 100644 index 000000000..47da039b9 --- /dev/null +++ b/SharpWave/csvorbis/Ogg/Packet.cs @@ -0,0 +1,42 @@ +/* csogg + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +using System; + +namespace csogg +{ + public class Packet + { + public byte[] packet_base; + public int packet, bytes; + public int b_o_s, e_o_s; + + public long granulepos; + public long packetno; // sequence number for decode; the framing + // knows where there's a hole in the data, + // but we need coupling so that the codec + // (which is in a seperate abstraction + // layer) also knows about the gap + } +} diff --git a/SharpWave/csvorbis/Ogg/Page.cs b/SharpWave/csvorbis/Ogg/Page.cs new file mode 100644 index 000000000..2b4b27ae2 --- /dev/null +++ b/SharpWave/csvorbis/Ogg/Page.cs @@ -0,0 +1,137 @@ +/* csogg + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +using System; + +namespace csogg +{ + public class Page + { + private static uint[] crc_lookup=new uint[256]; + + private static uint crc_entry(uint index) + { + uint r = index << 24; + for(int i=0; i<8; i++) + { + if((r& 0x80000000)!=0) + { + r=(r << 1)^0x04c11db7; /* The same as the ethernet generator + polynomial, although we use an + unreflected alg and an init/final + of 0, not 0xffffffff */ + } + else + { + r <<= 1; + } + } + return (r & 0xffffffff); + } + + public byte[] header_base; + public int header; + public int header_len; + public byte[] body_base; + public int body; + public int body_len; + + internal int version() + { + return header_base[header+4]&0xff; + } + internal int continued() + { + return (header_base[header+5]&0x01); + } + public int bos() + { + return (header_base[header+5]&0x02); + } + public int eos() + { + return (header_base[header+5]&0x04); + } + public long granulepos() + { + long foo = header_base[header+13]&0xff; + foo = (foo<<8) | (uint)(header_base[header+12]&0xff); + foo = (foo<<8) | (uint)(header_base[header+11]&0xff); + foo = (foo<<8) | (uint)(header_base[header+10]&0xff); + foo = (foo<<8) | (uint)(header_base[header+9]&0xff); + foo = (foo<<8) | (uint)(header_base[header+8]&0xff); + foo = (foo<<8) | (uint)(header_base[header+7]&0xff); + foo = (foo<<8) | (uint)(header_base[header+6]&0xff); + return(foo); + } + public int serialno() + { + return (header_base[header+14]&0xff)| + ((header_base[header+15]&0xff)<<8)| + ((header_base[header+16]&0xff)<<16)| + ((header_base[header+17]&0xff)<<24); + } + internal int pageno() + { + return (header_base[header+18]&0xff)| + ((header_base[header+19]&0xff)<<8)| + ((header_base[header+20]&0xff)<<16)| + ((header_base[header+21]&0xff)<<24); + } + + internal void checksum() + { + uint crc_reg=0; + uint a, b; + + for(int i=0;i> 24) & 0xff; + crc_reg = (crc_reg<<8)^crc_lookup[a^b]; + //crc_reg = (crc_reg<<8)^(uint)(crc_lookup[((crc_reg >> 24)&0xff)^(header_base[header+i]&0xff)]); + } + for(int i=0;i> 24) & 0xff; + crc_reg = (crc_reg<<8)^crc_lookup[a^b]; + + //crc_reg = (crc_reg<<8)^(uint)(crc_lookup[((crc_reg >> 24)&0xff)^(body_base[body+i]&0xff)]); + } + header_base[header+22]=(byte)crc_reg/*&0xff*/; + header_base[header+23]=(byte)(crc_reg>>8)/*&0xff*/; + header_base[header+24]=(byte)(crc_reg>>16)/*&0xff*/; + header_base[header+25]=(byte)(crc_reg>>24)/*&0xff*/; + } + + public Page() + { + for(uint i=0; i + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +using System; +using System.Text; + +namespace csogg +{ + public class StreamState + { + byte[] body_data; /* bytes from packet bodies */ + int body_storage; /* storage elements allocated */ + int body_fill; /* elements stored; fill mark */ + private int body_returned; /* elements of fill returned */ + + + int[] lacing_vals; /* The values that will go to the segment table */ + long[] granule_vals; /* pcm_pos values for headers. Not compact + this way, but it is simple coupled to the + lacing fifo */ + int lacing_storage; + int lacing_fill; + int lacing_packet; + int lacing_returned; + + public int e_o_s; /* set when we have buffered the last packet in the + logical bitstream */ + int serialno; + int pageno; + long packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a seperate abstraction + layer) also knows about the gap */ + long granulepos; + + StreamState(int serialno) : this() + { + init(serialno); + } + + public StreamState() + { + init(); + } + + void init() + { + body_storage=16*1024; + body_data=new byte[body_storage]; + lacing_storage=1024; + lacing_vals=new int[lacing_storage]; + granule_vals=new long[lacing_storage]; + } + public void init(int serialno) + { + if(body_data==null){ init(); } + else + { + for(int i=0; iPCM decoder + Block vb = new Block(); // local working space for packet->PCM decode + + byte[] buffer; + int bytes = 0; + + public AudioFormat ReadHeader(Stream source) { + input = source; + oy.init(); // Now we can read pages + + // grab some data at the head of the stream. We want the first page + // (which is guaranteed to be small and only contain the Vorbis + // stream initial header) We need the first page to get the stream serialno. + + // submit a 4k block to libvorbis' Ogg layer + int index = oy.buffer(4096); + buffer = oy.data; + bytes = input.Read(buffer, index, 4096); + oy.wrote(bytes); + + // Get the first page. + if (oy.pageout(og) != 1) { + // have we simply run out of data? If so, we're done. + if (bytes < 4096) return default(AudioFormat); + } + + // Get the serial number and set up the rest of decode. + // serialno first; use it to set up a logical stream + os.init(og.serialno()); + + // extract the initial header from the first page and verify that the + // Ogg bitstream is in fact Vorbis data + + // I handle the initial header first instead of just having the code + // read all three Vorbis headers at once because reading the initial + // header is an easy way to identify a Vorbis bitstream and it's + // useful to see that functionality seperated out. + + vi.init(); + vc.init(); + os.pagein(og); + os.packetout(op); + vi.synthesis_headerin(vc, op); + + // At this point, we're sure we're Vorbis. We've set up the logical + // (Ogg) bitstream decoder. Get the comment and codebook headers and + // set up the Vorbis decoder + + // The next two packets in order are the comment and codebook headers. + // They're likely large and may span multiple pages. Thus we reead + // and submit data until we get our two packets, watching that no + // pages are missing. If a page is missing, error out; losing a + // header page is the only place where missing data is fatal. */ + + int i = 0; + while (i < 2) { + while (i < 2) { + int result = oy.pageout(og); + if (result == 0) break; // Need more data + // Don't complain about missing or corrupt data yet. We'll + // catch it at the packet output phase + + if (result == 1) { + os.pagein(og); // we can ignore any errors here + // as they'll also become apparent + // at packetout + while (i < 2) { + result = os.packetout(op); + if (result == 0) break; + vi.synthesis_headerin(vc, op); + i++; + } + } + } + + // no harm in not checking before adding more + index = oy.buffer(4096); + buffer = oy.data; + bytes = input.Read(buffer, index, 4096); + oy.wrote(bytes); + } + + // OK, got and parsed all three headers. Initialize the Vorbis + // packet->PCM decoder. + vd.synthesis_init(vi); // central decode state + vb.init(vd); // local state for most of the decode + + AudioFormat format; + format.Channels = vi.channels; + format.BitsPerSample = 16; + format.SampleRate = vi.rate; + return format; + } + + public IEnumerable StreamData( Stream source ) { + // the original iterator may not always return enough samples, + // so we will do our own buffering here. + + rawChunk = new AudioChunk(); + foreach( AudioChunk chunk in StreamDataCore( source ) ) { + if( rawPcm == null ) + InitRaw( chunk ); + if( rawIndex + chunk.Length > rawPcm.Length ) + ResizeRaw( rawIndex + chunk.Length ); + + Buffer.BlockCopy( chunk.Data, 0, rawPcm, rawIndex, chunk.Length ); + rawIndex += chunk.Length; + if( rawIndex >= (vi.rate / 4) ) { + rawChunk.Length = rawIndex; + rawIndex = 0; + yield return rawChunk; + } + } + + rawChunk.Length = rawIndex; + yield return rawChunk; + yield break; + } + + void InitRaw(AudioChunk chunk) { + rawPcm = new byte[vi.rate / 4]; + rawChunk.Data = rawPcm; + rawChunk.Length = rawPcm.Length; + } + + void ResizeRaw(int newLen) { + byte[] oldPcm = rawPcm; + rawPcm = new byte[rawIndex + chunk.Length]; + Buffer.BlockCopy( oldPcm, 0, rawPcm, 0, rawIndex ); + rawChunk.Data = rawPcm; + rawChunk.Length = rawPcm.Length; + } + + IEnumerable StreamDataCore( Stream source ) { + input = source; + int convsize = 4096 * 2; + byte[] convbuffer = new byte[convsize]; // take 8k out of the data segment, not the stack + convsize = 4096 / vi.channels; + oy.init(); // Now we can read pages + + int eos = 0; + // so multiple block decodes can + // proceed in parallel. We could init + // multiple vorbis_block structures for vd here + + float[][] pcm = null; + int[] _index = new int[vi.channels]; + // The rest is just a straight decode loop until end of stream + + while (eos == 0) { + while (eos == 0) { + int result = oy.pageout(og); + if (result == 0) break; // need more data + + os.pagein(og); // can safely ignore errors at this point + while (true) { + result = os.packetout(op); + + if (result == 0) break; // need more data + if (result == -1) continue; + // missing or corrupt data at this page position + // no reason to complain; already complained above + + // we have a packet. Decode it + int samples; + if (vb.synthesis(op) == 0) { // test for success! + vd.synthesis_blockin(vb); + } + + // **pcm is a multichannel float vector. In stereo, for + // example, pcm[0] is left, and pcm[1] is right. samples is + // the size of each channel. Convert the float values + // (-1.<=range<=1.) to whatever PCM format and write it out + + while ((samples = vd.synthesis_pcmout(ref pcm, _index)) > 0) { + int bout = (samples < convsize ? samples : convsize); + + // convert floats to 16 bit signed ints (host order) and interleave + for (int ch = 0; ch < vi.channels; ch++) { + int ptr = ch * 2; + int offset = _index[ch]; + float[] chPcm = pcm[ch]; + + for (int j = 0; j < bout; j++) { + int val = (int)(chPcm[offset + j] * 32767); + if (val > 32767) val = 32767; + if (val < -32768) val = -32768; + if (val < 0) val = val | 0x8000; + + convbuffer[ptr] = (byte)(val); + convbuffer[ptr + 1] = (byte)((uint)val >> 8); + ptr += 2 * (vi.channels); + } + } + + chunk.Data = convbuffer; + chunk.Length = 2 * vi.channels * bout; + vd.synthesis_read(bout); + yield return chunk; + } + } + if (og.eos() != 0) eos = 1; + } + + if (eos == 0) { + int index = oy.buffer(4096); + buffer = oy.data; + bytes = input.Read(buffer, index, 4096); + oy.wrote(bytes); + if (bytes == 0) eos = 1; + } + } + + // clean up this logical bitstream; before exit we see if we're + // followed by another [chained] + os.clear(); + + // ogg_page and ogg_packet structs always point to storage in + // libvorbis. They're never freed or manipulated directly + vi.clear(); // must be called last + + // OK, clean up the framer + oy.clear(); + yield break; + } + } +} diff --git a/SharpWave/csvorbis/csorbisException.cs b/SharpWave/csvorbis/csorbisException.cs new file mode 100644 index 000000000..0e039f080 --- /dev/null +++ b/SharpWave/csvorbis/csorbisException.cs @@ -0,0 +1,36 @@ +/* csvorbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * Ported to C# from JOrbis by: Mark Crichton + * + * Thanks go to the JOrbis team, for licencing the code under the + * LGPL, making my job a lot easier. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +using System; + +namespace csvorbis +{ + public class csorbisException : Exception + { + public csorbisException () + :base(){} + public csorbisException (String s) + :base("csorbis: "+s){} + } +} diff --git a/src/Client/Platform.h b/src/Client/Platform.h index 8c503145e..de752551d 100644 --- a/src/Client/Platform.h +++ b/src/Client/Platform.h @@ -117,10 +117,11 @@ ReturnCode Platform_HttpFree(void); struct AudioFormat { UInt8 Channels, BitsPerSample; UInt16 Frequency }; #define AudioFormat_Eq(a, b) (a->Channels == b->Channels && a->BitsPerSample == b->BitsPerSample && a->Frequency == b->Frequency) -void Platform_AudioInit(Int32* handle, UInt8 buffers); +void Platform_AudioInit(Int32* handle, Int32 buffers); void Platform_AudioFree(Int32 handle); +struct AudioFormat* Platform_AudioGetFormat(Int32 handle); void Platform_AudioSetFormat(Int32 handle, struct AudioFormat* format); -void Platform_AudioPlayAsync(Int32 handle, void* data, UInt32 dataSize); -Int32 Platform_AudioNextFinishedAsync(Int32 handle); -bool Platform_AudioFinishedAsync(Int32 handle); +void Platform_AudioPlayData(Int32 handle, Int32 idx, void* data, UInt32 dataSize); +bool Platform_AudioIsCompleted(Int32 handle, Int32 idx); +bool Platform_AudioIsFinished(Int32 handle); #endif diff --git a/src/Client/WinPlatform.c b/src/Client/WinPlatform.c index 488e701a2..cbdd1bc3f 100644 --- a/src/Client/WinPlatform.c +++ b/src/Client/WinPlatform.c @@ -604,18 +604,22 @@ struct AudioContext { HWAVEOUT Handle; WAVEHDR Headers[AUDIO_MAX_CHUNKS]; struct AudioFormat Format; - UInt8 NumBuffers, PlayingAsync; + Int32 NumBuffers; }; struct AudioContext Audio_Contexts[20]; -void Platform_AudioInit(Int32* handle, UInt8 buffers) { - Int32 i; +void Platform_AudioInit(Int32* handle, Int32 buffers) { + Int32 i, j; for (i = 0; i < Array_Elems(Audio_Contexts); i++) { struct AudioContext* ctx = &Audio_Contexts[i]; if (ctx->NumBuffers) continue; - ctx->NumBuffers = buffers; - *handle = i; return; + + *handle = i; + for (j = 0; j < buffers; j++) { + ctx->Headers[j].dwFlags = WHDR_DONE; + } + return; } ErrorHandler_Fail("No free audio contexts"); } @@ -629,6 +633,11 @@ void Platform_AudioFree(Int32 handle) { ErrorHandler_CheckOrFail(result, "Audio - closing device"); } +struct AudioFormat* Platform_AudioGetFormat(Int32 handle) { + struct AudioContext* ctx = &Audio_Contexts[handle]; + return &ctx->Format; +} + void Platform_AudioSetFormat(Int32 handle, struct AudioFormat* format) { struct AudioContext* ctx = &Audio_Contexts[handle]; struct AudioFormat* cur = &ctx->Format; @@ -646,19 +655,18 @@ void Platform_AudioSetFormat(Int32 handle, struct AudioFormat* format) { fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; if (waveOutGetNumDevs() == 0u) ErrorHandler_Fail("No audio devices found"); - ReturnCode result = waveOutOpen(&ctx->Handle, UInt32_MaxValue, &fmt, NULL, NULL, CALLBACK_NULL); + ReturnCode result = waveOutOpen(&ctx->Handle, WAVE_MAPPER, &fmt, NULL, NULL, CALLBACK_NULL); ErrorHandler_CheckOrFail(result, "Audio - opening device"); } -void Platform_AudioPlayAsync(Int32 handle, void* data, UInt32 dataSize) { +void Platform_AudioPlayData(Int32 handle, Int32 idx, void* data, UInt32 dataSize) { struct AudioContext* ctx = &Audio_Contexts[handle]; - WAVEHDR* hdr = &ctx->Headers[0]; + WAVEHDR* hdr = &ctx->Headers[idx]; Platform_MemSet(hdr, 0, sizeof(WAVEHDR)); hdr->lpData = data; hdr->dwBufferLength = dataSize; hdr->dwLoops = 1; - ctx->PlayingAsync = true; ReturnCode result = waveOutPrepareHeader(ctx->Handle, hdr, sizeof(WAVEHDR)); ErrorHandler_CheckOrFail(result, "Audio - prepare header"); @@ -666,29 +674,24 @@ void Platform_AudioPlayAsync(Int32 handle, void* data, UInt32 dataSize) { ErrorHandler_CheckOrFail(result, "Audio - write header"); } -Int32 Platform_AudioNextFinishedAsync(Int32 handle) { +bool Platform_AudioIsCompleted(Int32 handle, Int32 idx) { + struct AudioContext* ctx = &Audio_Contexts[handle]; + WAVEHDR* hdr = &ctx->Headers[idx]; + if (!(hdr->dwFlags & WHDR_DONE)) return false; + + if (hdr->dwFlags & WHDR_PREPARED) { + ReturnCode result = waveOutUnprepareHeader(ctx->Handle, hdr, sizeof(WAVEHDR)); + ErrorHandler_CheckOrFail(result, "Audio - unprepare header"); + } + return true; +} + +bool Platform_AudioIsFinished(Int32 handle) { struct AudioContext* ctx = &Audio_Contexts[handle]; Int32 i; for (i = 0; i < ctx->NumBuffers; i++) { - WAVEHDR* hdr = &ctx->Headers[i]; - if (!(hdr->dwFlags & WHDR_DONE)) continue; - - if (hdr->dwFlags & WHDR_PREPARED) { - ReturnCode result = waveOutUnprepareHeader(ctx->Handle, hdr, sizeof(WAVEHDR)); - ErrorHandler_CheckOrFail(result, "Audio - unprepare header"); - } - return i; + if (!Platform_AudioIsCompleted(handle, i)) return false; } - return -1; -} - -bool Platform_AudioFinishedAsync(Int32 handle) { - struct AudioContext* ctx = &Audio_Contexts[handle]; - if (!ctx->PlayingAsync) return true; - Int32 index = Platform_AudioNextFinishedAsync(handle); - - if (index >= 0) return false; - ctx->PlayingAsync = false; return true; } #endif