From 36c425906f39d1ba19dc4436fcdcbfd9ee19b999 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Mon, 6 Aug 2018 01:04:05 +1000 Subject: [PATCH] rewrite sharpwave somewhat --- ClassicalSharp.sln | 10 + ClassicalSharp/Audio/AudioPlayer.Sounds.cs | 309 ++++++------ ClassicalSharp/Audio/AudioPlayer.cs | 2 +- ClassicalSharp/Audio/Soundboard.cs | 13 +- ClassicalSharp/ClassicalSharp.csproj | 7 +- ClassicalSharp/SharpWave.dll | Bin 81920 -> 65536 bytes Launcher2/Launcher2.csproj | 9 +- Launcher2/Patcher/SoundPatcher.cs | 11 +- SharpWave/ICodec.cs | 17 + SharpWave/IMediaContainer.cs | 48 ++ SharpWave/Properties/AssemblyInfo.cs | 31 ++ SharpWave/SharpWave.csproj | 87 ++++ SharpWave/SharpWave.dll.config | 6 + SharpWave/Utils/MemUtils.cs | 35 ++ SharpWave/VolumeMixer.cs | 39 ++ SharpWave/csvorbis/Block.cs | 90 ++++ SharpWave/csvorbis/CodeBook.cs | 403 +++++++++++++++ SharpWave/csvorbis/Comment.cs | 91 ++++ SharpWave/csvorbis/DspState.cs | 319 ++++++++++++ SharpWave/csvorbis/FuncFloor.cs | 560 +++++++++++++++++++++ SharpWave/csvorbis/FuncMapping.cs | 267 ++++++++++ SharpWave/csvorbis/FuncResidue.cs | 312 ++++++++++++ SharpWave/csvorbis/Info.cs | 204 ++++++++ SharpWave/csvorbis/Mdct.cs | 249 +++++++++ SharpWave/csvorbis/Ogg/Buffer.cs | 220 ++++++++ SharpWave/csvorbis/Ogg/Packet.cs | 42 ++ SharpWave/csvorbis/Ogg/Page.cs | 137 +++++ SharpWave/csvorbis/Ogg/StreamState.cs | 387 ++++++++++++++ SharpWave/csvorbis/Ogg/SyncState.cs | 273 ++++++++++ SharpWave/csvorbis/StaticCodeBook.cs | 290 +++++++++++ SharpWave/csvorbis/VUtils.cs | 58 +++ SharpWave/csvorbis/VorbisCodec.cs | 269 ++++++++++ SharpWave/csvorbis/csorbisException.cs | 36 ++ src/Client/Platform.h | 9 +- src/Client/WinPlatform.c | 59 +-- 35 files changed, 4693 insertions(+), 206 deletions(-) create mode 100644 SharpWave/ICodec.cs create mode 100644 SharpWave/IMediaContainer.cs create mode 100644 SharpWave/Properties/AssemblyInfo.cs create mode 100644 SharpWave/SharpWave.csproj create mode 100644 SharpWave/SharpWave.dll.config create mode 100644 SharpWave/Utils/MemUtils.cs create mode 100644 SharpWave/VolumeMixer.cs create mode 100644 SharpWave/csvorbis/Block.cs create mode 100644 SharpWave/csvorbis/CodeBook.cs create mode 100644 SharpWave/csvorbis/Comment.cs create mode 100644 SharpWave/csvorbis/DspState.cs create mode 100644 SharpWave/csvorbis/FuncFloor.cs create mode 100644 SharpWave/csvorbis/FuncMapping.cs create mode 100644 SharpWave/csvorbis/FuncResidue.cs create mode 100644 SharpWave/csvorbis/Info.cs create mode 100644 SharpWave/csvorbis/Mdct.cs create mode 100644 SharpWave/csvorbis/Ogg/Buffer.cs create mode 100644 SharpWave/csvorbis/Ogg/Packet.cs create mode 100644 SharpWave/csvorbis/Ogg/Page.cs create mode 100644 SharpWave/csvorbis/Ogg/StreamState.cs create mode 100644 SharpWave/csvorbis/Ogg/SyncState.cs create mode 100644 SharpWave/csvorbis/StaticCodeBook.cs create mode 100644 SharpWave/csvorbis/VUtils.cs create mode 100644 SharpWave/csvorbis/VorbisCodec.cs create mode 100644 SharpWave/csvorbis/csorbisException.cs 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 db9479d36225e0b8e837dcbd56f3237bb87645fc..8ee22367704826e941e51158c2a2ff715eece98b 100644 GIT binary patch literal 65536 zcmeFad3==B^*?@}WuAFv$?{~9nQV{%28T=tWC9^%1r-H_x}Z1-S_nzRKwuyNM2CSO zYH#nV}xV35*w`%K03%~a{_jx80(0=>AM%=H=d~U?a zXLj|LF6)Ue>}gw4+TONwX?#WLg3i*Ol}k&zmX;oM{Or;t@s7^Qj12E#w&)Y42{BXA z#MI1%bDh%m33Y@|F@^XID5a8G`6nl?ATmYLv!&j|LHhIG6r_Vrf0~$bJ|m0+*mOH! zo;Uv}#PJ+cM3@Y5@L#DS{0Gzi6TDOi)FAa|0-v73gs5E6xq1cg2~Pnajq94Om?Y1y z?CI@kM_lS=*KHpNX@65-kjkFU?l>4(SLn=j{UnVEQScoxl=40Hy#>Cv!1osT-U8oS z;Cl;vZ-MVE@Vy1Tx4`!n_}&8luUH_P_SN{Ky{Rf(;BDxs z9JvGIb#W~O;h&`@h?*z{@Tv(FftJ|8@H&P&0psh1Fl1sVegWd6Rf*Y|hY=6alWxRR zBXKqb2|@-EivW{21pt9yg_BoGN@8oT+tO@`3zSHv8k9-FB;l%-xB)@ZBjYzBC|J~z z^h$IWC1F!fMfXs^CG{r_j-!_P<}M?#4BeLJZ)BFOPlku}@z{z4s7=C}Zj^;$-b2f_bT<-wPRX|V(dUg!J$ifOG{~L(-Ev`t zivLuy-J)EJXG!`k!E*E+w>TPgf!|svs@^WjZ*mpvO1Ea91|g>$X)=$k^-f3OeI)WR zh=fd+SZv!Tn4!DodLuD4Q|GoiNHE(bs(A*CL)rv(acsh^;i@)*83s(q&S z#awfv*Ku7mL1$g)m#B-H$VSwsxf8hvy z6>wW_TPBi4Eq-e#d;}oqLo=3Wx*3zE#n?oR)QbA=q{^C^dd%;b`Sn_K8xhPPpN_IW zA7VKtzbD;(mJTTa-FC|8?#MC0fGZdCRIMgj2A(kt)t(E(PDKDt9rhdB%YQ2Z8~2WO6WI1_PeRFv|=r2$+@$C+N3W^U%Ec zAW#7w%cFuE{T&i5bBBc%Cuh;3kGr`q>Ec<~2a^wcx))}~aHl57#QCjkr?NmslHAhD zARif$keMVWwK6l*1hr3M4}cw|h6-bpHUbzD$C4SHyvY!f;2X6AWcULp zRc@N4gJij>9YXV{c4I>(tTWaF2%D67cMu8#=6RS5D9MDvVs=G`77=QR{&LWLzbU1&8sjymajkHix?({mLCn}xBOD>I3xzgzhL<*o|6NN z<+Xe}>trbbPygS4q90Z<`@9a5K8C}p3p%&$dOhm^QV={+(Yn#OGS=@B@P^1 zDx{=%XnmlGY{>MYEmT(5s}|1H^5?erR76^I#Qa!VwRyTnN$c%vj%nR<*A~G@-K{oF zOqsiw2Ab|L1|%;>ExnZ}k}r*osVP3g;q7tbp$$TMx1I$LE31W=l^AIVtRWXvLtD z!wK`}4#vXh22>g`Qdms;Jk~g{!V=cUkhFrKMiJ+z-6_bY%t|ll*q6nw86u)<){6CP?9abWeNRNYwxuYmu7gElQSA9)zP2me;9a_#UQ)FgmM2 zecceFVR-SFO&Q&NzPVLjg}f=x6}XxaFY!LRhAIO62%$usn>BMqv53iWb0|XX_ifxpz4Me z#k7PMkxG*+z?P5%wuU-bRojl31rMs3ns6*?!m1e_6N4WOVkSlvyshRTt^4DJiD-jjL{;?7D{d%y-#Wy$gnb| zsnitsp+!aH6ljxHdJxWw6U6BCKucgLrB`4v&N3uZOIJ%q zdJ(EeI;-Vk7e@c2`EeRdj!g~v4Ee4seB4BgTp7CU`a5`lN~=cneJrV{txt5EjIy?r z2g^5W6}BJ^+d-;Fl~Rt1QMwl6-cepT@^@J?ru5Mi3l&%g%&W9XwemNi8miX_^@xZy#N;f23Sh2>~K4T{&C(^ z6x%R3CAd5Z${2lywheDU6HlU=a=Y{b&K-*F@FXTkO1_lm;QhgIrYAlgO5~QOXcgI> z1V%#=Fx+96cz-ZzIcb>9Xs(E+^3xLFXiuVsa}HRBojKHk(o@9#yl8GZdaw{@h7YB* zq1Au%7H6q}siJupe9)uInuKD>Nhr{^6E+R#CT3)gAx&VL%H7`|^G0esiCWUl%#Z0N z*Q+ex#m0cPfWats1|gHnN>#||^G0;Hvs^FF{Oz9

1k`UQr`4QSdW8Lq56?~Df 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 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