diff --git a/.gitignore b/.gitignore index d9a0016101c..3cd44f30853 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,9 @@ project.lock.json /MonoGame.Framework/MonoGame.Framework.WindowsUniversal.project.lock.json artifacts/ +# JetBrains Rider +.idea/ + #Tooling _ReSharper*/ *.resharper @@ -93,3 +96,4 @@ Test/*.csproj *.zip Installers/MacOS/Scripts/Framework/postinstall +IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/MonoGame.Framework.dll.config diff --git a/.gitmodules b/.gitmodules index ad282c1a9e3..b89dc6185d3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "ThirdParty/Dependencies"] path = ThirdParty/Dependencies url = https://github.com/Mono-Game/MonoGame.Dependencies.git +[submodule "ThirdParty/NVorbis"] + path = ThirdParty/NVorbis + url = https://github.com/ioctlLR/NVorbis.git diff --git a/Build/Module.xml b/Build/Module.xml index 96d93aa22cc..57035b845f1 100644 --- a/Build/Module.xml +++ b/Build/Module.xml @@ -1,17 +1,16 @@ - + MonoGame.Framework Generate Angle,Linux,WindowsGL Angle,MacOS,iOS,WindowsGL,Android,tvOS,Linux - Android,Angle,Linux,Ouya,Windows8,Windows,WindowsGL,WindowsPhone,WindowsPhone81,WindowsUniversal,iOS,tvOS - Android,Angle,Linux,Ouya,Windows8,Windows,WindowsGL,WindowsPhone,WindowsPhone81,WindowsUniversal,iOS,MacOS,Web,tvOS + Android,Angle,Linux,Windows8,Windows,WindowsGL,WindowsPhone81,WindowsUniversal,iOS,tvOS + Android,Angle,Linux,Windows8,Windows,WindowsGL,WindowsPhone81,WindowsUniversal,iOS,MacOS,Web,tvOS,PSVita,XBoxOne true + false - + + - - PackageManagement - diff --git a/Build/MonoDevelopPolicies.xml b/Build/MonoDevelopPolicies.xml index c9a0f40e222..29c5eac6b66 100644 --- a/Build/MonoDevelopPolicies.xml +++ b/Build/MonoDevelopPolicies.xml @@ -1,4 +1,4 @@ - - + + diff --git a/Build/Projects/2MGFX.definition b/Build/Projects/2MGFX.definition index d2348c38da7..c1bee7a5c5a 100644 --- a/Build/Projects/2MGFX.definition +++ b/Build/Projects/2MGFX.definition @@ -106,6 +106,9 @@ MonoGame.Framework\TextureFilter.cs + + MonoGame.Framework\TextureFilterMode.cs + MonoGame.Framework\VertexElementUsage.cs @@ -160,7 +163,15 @@ MonoGame.Framework\Utilities\Hash.cs - + + + + MonoGame.Framework.Content.Pipeline\ExternalTool.cs + + + MonoGame.Framework.Content.Pipeline\LoadedTypeCollection.c + + @@ -184,6 +195,8 @@ + + @@ -200,5 +213,6 @@ + diff --git a/Build/Projects/FrameworkReferences.Net.definition b/Build/Projects/FrameworkReferences.Net.definition index 8061512184e..1ca6964a989 100644 --- a/Build/Projects/FrameworkReferences.Net.definition +++ b/Build/Projects/FrameworkReferences.Net.definition @@ -36,17 +36,6 @@ - - - - - - - - - @@ -66,17 +55,6 @@ - - - - - MicrosoftXnaGamerServices - - - MicrosoftXnaFramework - - - diff --git a/Build/Projects/FrameworkReferences.definition b/Build/Projects/FrameworkReferences.definition index 793d912f16f..065351d5f95 100644 --- a/Build/Projects/FrameworkReferences.definition +++ b/Build/Projects/FrameworkReferences.definition @@ -8,6 +8,9 @@ + + + @@ -16,12 +19,6 @@ - - @@ -48,17 +45,6 @@ - - - - - - - - - @@ -103,9 +89,6 @@ - @@ -147,42 +130,9 @@ - - - - - - - - - - - - - MicrosoftXnaFramework - - - MicrosoftXnaGamerServices - - - @@ -244,9 +194,6 @@ - diff --git a/Build/Projects/Lidgren.Network.References.definition b/Build/Projects/Lidgren.Network.References.definition index 8f617d6b307..a006e8d0835 100644 --- a/Build/Projects/Lidgren.Network.References.definition +++ b/Build/Projects/Lidgren.Network.References.definition @@ -25,12 +25,6 @@ - - - - - - diff --git a/Build/Projects/Lidgren.Network.definition b/Build/Projects/Lidgren.Network.definition index 9da3fa8efe7..e46c2372200 100644 --- a/Build/Projects/Lidgren.Network.definition +++ b/Build/Projects/Lidgren.Network.definition @@ -3,7 +3,7 @@ Name="Lidgren.Network" Path="ThirdParty/Lidgren.Network" Type="Library" - Platforms="Android,Linux,MacOS,Ouya,Windows,WindowsGL,iOS"> + Platforms="Android,Linux,MacOS,Windows,WindowsGL,iOS"> @@ -91,6 +93,7 @@ + @@ -112,6 +115,7 @@ + @@ -131,6 +135,7 @@ + @@ -280,6 +285,14 @@ Windows Processors\MGFX\ShaderProfile.cs + + Windows + Processors\MGFX\ShaderProfile.OpenGL.cs + + + Windows + Processors\MGFX\ShaderProfile.DirectX.cs + Windows Processors\MGFX\TechniqueInfo.cs @@ -314,6 +327,7 @@ + @@ -362,6 +376,7 @@ + @@ -380,6 +395,7 @@ + @@ -418,6 +434,7 @@ + diff --git a/Build/Projects/MonoGame.Framework.Net.definition b/Build/Projects/MonoGame.Framework.Net.definition index feec5c47ce7..da0fc10e541 100644 --- a/Build/Projects/MonoGame.Framework.Net.definition +++ b/Build/Projects/MonoGame.Framework.Net.definition @@ -1,5 +1,5 @@ - + - - MonoGameXnaFramework - - + @@ -47,11 +42,9 @@ IOS;GLES;OPENGL;OPENAL;NET TRACE;LINUX;OPENGL;OPENAL;NET;DESKTOPGL MONOMAC;OPENGL;OPENAL;NET - TRACE;ANDROID;GLES;OPENGL;OUYA;NET TRACE;WINDOWS;DIRECTX;WINDOWS_MEDIA_SESSION;NET TRACE;NETFX_CORE;WINRT;WINDOWS_STOREAPP;DIRECTX;DIRECTX11_1;WINDOWS_MEDIA_ENGINE;NET TRACE;WINDOWS;OPENGL;OPENAL;NET;DESKTOPGL - TRACE;SILVERLIGHT;WINDOWS_PHONE;WINRT;DIRECTX;NET TRACE;WINRT;WINDOWS_PHONE81;DIRECTX;WINDOWS_STOREAPP TRACE;NETFX_CORE;WINDOWS_UAP;WINRT;DIRECTX;DIRECTX11_1;WINDOWS_MEDIA_ENGINE @@ -69,45 +62,19 @@ - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - + + + + + + + + + + + + + @@ -115,122 +82,120 @@ - - WindowsPhone - + - Android,MacOS,Ouya,Windows,WindowsGL,Linux + Android,MacOS,Windows,WindowsGL,Linux MacOS,Windows,WindowsGL,Linux - Android,iOS,Linux,MacOS,Ouya,Windows,WindowsGL + Android,iOS,Linux,MacOS,Windows,WindowsGL - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - WindowsPhone,WindowsPhone81,Windows8,WindowsUniversal + WindowsPhone81,Windows8,WindowsUniversal - + - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android @@ -296,7 +261,7 @@ - Windows8,Windows,WindowsPhone,WindowsPhone81,WindowsUniversal + Windows8,Windows,WindowsPhone81,WindowsUniversal Windows diff --git a/Build/Projects/MonoGame.Framework.definition b/Build/Projects/MonoGame.Framework.definition index 2f41d93ca96..2961d08d4ec 100644 --- a/Build/Projects/MonoGame.Framework.definition +++ b/Build/Projects/MonoGame.Framework.definition @@ -1,5 +1,5 @@ - + - Angle,iOS,Linux,Windows,WindowsGL,MacOS,Android,Ouya,tvOS + Angle,iOS,Linux,Windows,WindowsGL,MacOS,Android,tvOS XAudioAudio,WebAudio - Windows,Windows8,WindowsPhone,WindowsPhone81,WindowsUniversal + Windows,Windows8,WindowsPhone81,WindowsUniversal OpenALAudio,WebAudio Web OpenALAudio,XAudioAudio + + WindowsGL,Linux + + XNADESIGNPROVIDED - - - - + + Android + + + WindowsGL,Linux + + + iOS + + + MacOS + + + Angle + + + tvOS + + + Windows + + + WindowsPhone81 + + + Windows8 + + + WindowsUniversal + + + Web + MacOS @@ -154,25 +186,81 @@ PreserveNewest - WindowsGL,Linux,MacOS + MacOS OpenTK.dll.config PreserveNewest + + WindowsGL,Linux + MonoGame.Framework.dll.config + PreserveNewest + + + + WindowsGL,Linux + x86\SDL2.dll + PreserveNewest + + + WindowsGL,Linux + x64\SDL2.dll + PreserveNewest + + + WindowsGL,Linux + x86\soft_oal.dll + PreserveNewest + + + WindowsGL,Linux + x64\soft_oal.dll + PreserveNewest + + + WindowsGL,Linux + x86\libSDL2-2.0.so.0 + PreserveNewest + + + WindowsGL,Linux + x64\libSDL2-2.0.so.0 + PreserveNewest + + + WindowsGL,Linux + x86\libopenal.so.1 + PreserveNewest + + + WindowsGL,Linux + x64\libopenal.so.1 + PreserveNewest + + + WindowsGL,Linux + libSDL2-2.0.0.dylib + PreserveNewest + + + WindowsGL,Linux + libopenal.1.dylib + PreserveNewest + - Android,Ouya + Android libs\armeabi-v7a\libopenal32.so - Android,Ouya + Android libs\arm64-v8a\libopenal32.so - Android,Ouya + Android libs\x86\libopenal32.so - Android,Ouya + Android libs\x86_64\libopenal32.so @@ -190,9 +278,7 @@ - - _FrameworkDispatcherProvided - + @@ -203,7 +289,7 @@ Angle,Linux,MacOS,Windows,Windows8,WindowsGL,WindowsUniversal - Android,iOS,Ouya,WindowsPhone,WindowsPhone81,Web + Android,iOS,WindowsPhone81,Web,tvOS @@ -212,7 +298,7 @@ Windows8,WindowsPhone81 - Windows8,WindowsPhone,WindowsPhone81 + Windows8,WindowsPhone81 @@ -232,7 +318,7 @@ - Android,MacOS,Ouya + Android,MacOS @@ -245,9 +331,21 @@ Angle,Linux,MacOS,Windows,WindowsGL,WindowsUniversal - Android,Angle,iOS,Linux,MacOS,Ouya,WindowsGL,WindowsPhone,tvOS + Android,Angle,iOS,Linux,MacOS,WindowsGL,tvOS + + Android + + + Angle,Linux,Windows,WindowsGL,Web + + + iOS,tvOS,MacOS + + + Windows8,WindowsPhone81,WindowsUniversal + @@ -260,11 +358,22 @@ - Linux,WindowsGL,Angle,Android,Ouya + Linux,WindowsGL,Angle,Android + + + + OpenALAudio + + + XAudioAudio + + + WebAudio + - Android,Angle,iOS,Linux,MacOS,Ouya,WindowsGL,Web,tvOS + Android,Angle,iOS,Linux,MacOS,WindowsGL,Web,tvOS @@ -273,6 +382,9 @@ WindowsGL,Linux + + WindowsGL,Linux + OpenALAudio @@ -308,10 +420,21 @@ + + + + + - + + + + + + + @@ -373,13 +496,13 @@ - Angle,Linux,MacOS,Windows8,Windows,WindowsGL,WindowsPhone,WindowsPhone81,WindowsUniversal + Angle,Linux,MacOS,Windows8,Windows,WindowsGL,WindowsPhone81,WindowsUniversal - Android,Angle,iOS,Linux,MacOS,Ouya,Windows8,Windows,WindowsGL,WindowsPhone,WindowsPhone81,WindowsUniversal,tvOS + Android,Angle,iOS,Linux,MacOS,Windows8,Windows,WindowsGL,WindowsPhone81,WindowsUniversal,tvOS - Android,Angle,iOS,Linux,MacOS,Ouya,Windows8,Windows,WindowsGL,WindowsPhone,WindowsPhone81,WindowsUniversal,tvOS + Android,Angle,iOS,Linux,MacOS,Windows8,Windows,WindowsGL,WindowsPhone81,WindowsUniversal,tvOS @@ -391,7 +514,7 @@ - Angle,Linux,WindowsGL,WindowsPhone,Web,tvOS + Angle,Linux,WindowsGL,Web,tvOS @@ -402,7 +525,7 @@ - Angle,Android,iOS,Linux,Ouya,Windows,WindowsGL,tvOS + Angle,Android,iOS,Linux,Windows,WindowsGL,tvOS @@ -413,7 +536,18 @@ - + + Windows,WindowsGL,Linux,Windows8,WindowsPhone81,WindowsUniversal,Web + + + WindowsGL,Linux + + + Windows8,WindowsPhone81,WindowsUniversal + + + Android,Angle,iOS,MacOS,tvOS + @@ -454,8 +588,28 @@ Web - + + Android,Angle,iOS,Linux,MacOS,WindowsGL,tvOS,Web + + + DirectXGraphics + + + Android,Angle,iOS,Linux,MacOS,WindowsGL,tvOS,Web + + + DirectXGraphics + + + OpenGLGraphics,ANGLEGraphics + + + WebGraphics + + + WindowsGL,Linux + DirectXGraphics @@ -473,11 +627,13 @@ + - Angle,Linux,Windows8,Windows,WindowsGL,WindowsPhone,WindowsPhone81,WindowsUniversal + Angle,Linux,Windows8,Windows,WindowsGL,WindowsPhone81,WindowsUniversal + @@ -488,18 +644,27 @@ - iOS,Android,Ouya + iOS,Android DirectXGraphics ANGLEGraphics,OpenGLGraphics - iOS,Android,Ouya + iOS,Android WebGraphics + + WindowsGL,Linux + + + WindowsGL,Linux + + + WindowsGL,Linux + @@ -539,6 +704,15 @@ + + DirectXGraphics + + + OpenGLGraphics + + + WebGraphics + @@ -575,7 +749,7 @@ WebGraphics - Android,Angle,iOS,Linux,MacOS,Ouya,WindowsGL,tvOS + Android,Angle,iOS,Linux,MacOS,WindowsGL,tvOS @@ -634,6 +808,7 @@ + Windows @@ -749,14 +924,19 @@ - - + + WindowsGL,Linux + + - Android,WindowsPhone,WindowsPhone81 + WindowsPhone81 + + + Android iOS @@ -764,11 +944,8 @@ tvOS - - Ouya - - - Angle,Linux,MacOS,WindowsGL + + Linux,WindowsGL MacOS @@ -795,22 +972,28 @@ - Android,Ouya,Windows8,Windows,WindowsPhone,WindowsPhone81,WindowsUniversal,iOS,tvOS - - - Angle,Linux,MacOS,WindowsGL + Angle,Android,MacOS,Windows8,Windows,WindowsPhone81,WindowsUniversal,iOS,tvOS - - MacOS + + Linux,WindowsGL Web - Android,Ouya + Android + + + Android,Windows,WindowsGL,Linux + + + WindowsGL,Linux + + + Windows - Android,iOS,Windows8,WindowsPhone,WindowsPhone81,WindowsUniversal + Android,iOS,Windows8,WindowsPhone81,WindowsUniversal Android @@ -818,17 +1001,14 @@ iOS - - WindowsPhone - Windows8,WindowsPhone81,WindowsUniversal - + - Android,iOS,Windows8,WindowsPhone,WindowsPhone81,WindowsUniversal + Android,iOS,Windows8,WindowsPhone81,WindowsUniversal Android @@ -836,13 +1016,29 @@ iOS - - WindowsPhone - Windows8,WindowsPhone81,WindowsUniversal + + WindowsGL,Linux + + + Windows + + + MacOS + + + WindowsGL,Linux,Windows,MacOS + + + + Linux,WindowsGL + + + Linux,WindowsGL + @@ -853,9 +1049,9 @@ - Windows8,WindowsPhone,WindowsPhone81,WindowsUniversal + Windows8,WindowsPhone81,WindowsUniversal - + Windows8,WindowsPhone81,WindowsUniversal MSBuild:Compile @@ -869,10 +1065,10 @@ - iOS,Windows8,WindowsPhone,Android,Ouya,WindowsPhone81,WindowsUniversal + iOS,Windows8,Android,WindowsPhone81,WindowsUniversal - Android,Ouya + Android iOS @@ -880,9 +1076,6 @@ Windows8,WindowsPhone81,WindowsUniversal - - WindowsPhone - Windows @@ -893,11 +1086,8 @@ Windows - - WindowsPhone - - Angle,WindowsGL,Linux,MacOS,iOS,Android,Ouya,Web,tvOS + Angle,WindowsGL,Linux,MacOS,iOS,Android,Web,tvOS @@ -914,7 +1104,7 @@ Angle,MacOS,Web - Android,Ouya + Android iOS,tvOS @@ -925,15 +1115,12 @@ Windows - - WindowsPhone - Windows8,WindowsPhone81,WindowsUniversal - Angle,Linux,WindowsGL,WindowsPhone,Web,tvOS + Angle,Linux,WindowsGL,Web,tvOS iOS @@ -942,13 +1129,13 @@ MacOS - Android,Ouya + Android Windows - Angle,Linux,WindowsGL,WindowsPhone,Web,tvOS + Angle,Linux,WindowsGL,Web,tvOS iOS @@ -960,7 +1147,7 @@ MacOS - Android,Ouya + Android Windows @@ -968,39 +1155,31 @@ - Android,iOS,Ouya + Android,iOS - Android,iOS,Ouya + Android,iOS - Android,iOS,Ouya + Android,iOS - Android,iOS,Ouya + Android,iOS - Android,iOS,Ouya + Android,iOS - Android,iOS,Ouya + Android,iOS - Android,iOS,Ouya + Android,iOS - Android,iOS,Ouya + Android,iOS - Android,iOS,Ouya - - - - - - - - Android,Angle,iOS,Ouya,Windows8,Windows,WindowsGL,WindowsPhone,Web,WindowsPhone81,WindowsUniversal,Linux,tvOS + Android,iOS @@ -1014,68 +1193,84 @@ + + Android,iOS,MacOS,Windows8,WindowsPhone81,WindowsUniversal,tvOS + + + Angle,Linux,Windows,WindowsGL,WindowsPhone,Web + + + - + - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - Android,Ouya + Android - + Angle,Linux,WindowsGL - + Angle,Linux,WindowsGL - + + WindowsGL,Linux + + Angle,Linux,WindowsGL - + + MonoGame.bmp Angle,Linux,WindowsGL + + Angle,Linux,WindowsGL + gamecontrollerdb.txt + @@ -1143,9 +1338,6 @@ MacOS - - MacOS - @@ -1165,16 +1357,16 @@ Windows8,WindowsPhone81 - Windows8,WindowsPhone,WindowsPhone81,WindowsUniversal + Windows8,WindowsPhone81,WindowsUniversal - Windows8,Windows,WindowsPhone,WindowsPhone81,WindowsUniversal + Windows8,Windows,WindowsPhone81,WindowsUniversal Windows8,WindowsPhone81 - + WindowsUniversal @@ -1186,7 +1378,7 @@ WindowsUniversal - + Windows @@ -1202,26 +1394,6 @@ - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - - WindowsPhone - - Web @@ -1294,6 +1466,107 @@ DirectXGraphics + + + Utilities\NVorbis\BufferedReadStream.cs + NVorbis + + + Utilities\NVorbis\DataPacket.cs + NVorbis + + + Utilities\NVorbis\Huffman.cs + NVorbis + + + Utilities\NVorbis\IContainerReader.cs + NVorbis + + + Utilities\NVorbis\IPacketProvider.cs + NVorbis + + + Utilities\NVorbis\IVorbisStreamStatus.cs + NVorbis + + + Utilities\NVorbis\Mdct.cs + NVorbis + + + Utilities\NVorbis\NewStreamEventArgs.cs + NVorbis + + + Utilities\NVorbis\ParameterChangeEventArgs.cs + NVorbis + + + Utilities\NVorbis\RingBuffer.cs + NVorbis + + + Utilities\NVorbis\StreamReadBuffer.cs + NVorbis + + + Utilities\NVorbis\Utils.cs + NVorbis + + + Utilities\NVorbis\VorbisCodebook.cs + NVorbis + + + Utilities\NVorbis\VorbisFloor.cs + NVorbis + + + Utilities\NVorbis\VorbisMapping.cs + NVorbis + + + Utilities\NVorbis\VorbisMode.cs + NVorbis + + + Utilities\NVorbis\VorbisReader.cs + NVorbis + + + Utilities\NVorbis\VorbisResidue.cs + NVorbis + + + Utilities\NVorbis\VorbisStreamDecoder.cs + NVorbis + + + Utilities\NVorbis\VorbisTime.cs + NVorbis + + + Utilities\NVorbis\Ogg\OggContainerReader.cs + NVorbis + + + Utilities\NVorbis\Ogg\OggCrc.cs + NVorbis + + + Utilities\NVorbis\Ogg\OggPacket.cs + NVorbis + + + Utilities\NVorbis\Ogg\OggPacketReader.cs + NVorbis + + + Utilities\NVorbis\Ogg\OggPageFlags.cs + NVorbis + diff --git a/Build/Projects/MonoGame.Tests.definition b/Build/Projects/MonoGame.Tests.definition index 4f12279e114..bc321d2793c 100644 --- a/Build/Projects/MonoGame.Tests.definition +++ b/Build/Projects/MonoGame.Tests.definition @@ -5,14 +5,14 @@ - + - + Default 1591,0436 Build/MonoDevelopPolicies.xml @@ -33,6 +33,33 @@ + + + WindowsGL + SDL2.dll + PreserveNewest + + + WindowsGL + soft_oal.dll + PreserveNewest + + + Linux + x64\libSDL2-2.0.so.0 + PreserveNewest + + + Linux + x64\libopenal.so.1 + PreserveNewest + + + + Linux + PreserveNewest + + @@ -86,15 +113,17 @@ iOS + + - - - + + + @@ -106,9 +135,8 @@ - - + @@ -117,11 +145,19 @@ - + + + + + + Windows + + + @@ -130,25 +166,44 @@ - - - - - - - - - - - - - + + + + + + + + + + + Windows + + + + + + + + + + + + + + + + + Windows + + + - - - - - + + + + + + @@ -156,6 +211,9 @@ Windows,MacOS,Linux + + Windows,MacOS,Linux + Windows,MacOS,Linux @@ -174,9 +232,21 @@ Windows,MacOS,Linux + + Windows,MacOS,Linux + + + Windows,MacOS,Linux + Windows,MacOS,Linux + + Windows,MacOS,Linux + + + Windows,MacOS,Linux + Windows,MacOS,Linux @@ -195,6 +265,86 @@ Windows,MacOS,Linux + + Windows,MacOS,Linux + + + Windows,MacOS,Linux + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -241,35 +391,77 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - PreserveNewest + Windows PreserveNewest + Windows PreserveNewest + Windows PreserveNewest + Windows PreserveNewest + Windows PreserveNewest + Windows PreserveNewest + Windows PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows @@ -290,25 +482,25 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest @@ -365,6 +557,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -392,6 +593,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -529,10 +733,22 @@ PreserveNewest + + + PreserveNewest + + + PreserveNewest PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -551,7 +767,16 @@ PreserveNewest - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + PreserveNewest @@ -695,6 +920,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -705,6 +933,7 @@ PreserveNewest + PreserveNewest Windows @@ -737,6 +966,66 @@ PreserveNewest Windows + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + PreserveNewest + Windows + + + + + PreserveNewest + WindowsGL,Linux + + + PreserveNewest + WindowsGL,Linux + + + PreserveNewest + WindowsGL,Linux + + + PreserveNewest + WindowsGL,Linux + + + PreserveNewest + WindowsGL,Linux + + + PreserveNewest + WindowsGL,Linux + + + PreserveNewest + WindowsGL,Linux + + + PreserveNewest + WindowsGL,Linux + + + PreserveNewest + WindowsGL,Linux + + + PreserveNewest @@ -806,21 +1095,39 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest + + + PreserveNewest + + + PreserveNewest PreserveNewest + + + PreserveNewest + + + PreserveNewest PreserveNewest - + PreserveNewest - + PreserveNewest diff --git a/Build/Projects/Pipeline.definition b/Build/Projects/Pipeline.definition index 9b1a820ff27..5808ea9aed0 100644 --- a/Build/Projects/Pipeline.definition +++ b/Build/Projects/Pipeline.definition @@ -11,7 +11,7 @@ - + @@ -28,6 +28,10 @@ TRACE;MONOMAC;GTK2 TRACE;LINUX;GTK3 + + v4.6.1 + + True @@ -39,29 +43,29 @@ + - - - + - + - - - + + + + @@ -71,309 +75,269 @@ - - - - Windows - - - Windows - - - Windows - - - Windows - - - Windows - Form - - - Windows - MainView.cs + + + + + + MainWindow.cs - + + + + Global.cs Windows - Form - + Windows - NewContentDialog.cs - + Windows - Component - - Windows - Component + + + + MainWindow.cs + MainWindow.glade + Linux + + + Global.cs + Linux - - Windows - Component + + Linux - - Windows + + Linux - - Windows + + + + Global.cs + MacOS - - Windows + + MacOS - - Windows - - - Windows - AboutDialog.cs + + MacOS - - Windows - - - Windows + + + + + + + + + + + + + + + + Pad.cs + + + + BuildOutput.cs + + + + PropertyGridControl.cs + + + + PropertyGridTable.cs + + + + + + AddItemDialog.cs + + + + DeleteDialog.cs + + + + DialogBase.cs + + + + EditDialog.cs + + + + NewItemDialog.cs + + + + PathDialog.cs + + + ReferenceDialog.cs - - Windows - - - Windows - AddFileDialog.cs - - - Windows - AddFileDialog.cs + + + + + Icons.monogame.png - - Windows - - - Windows - AddFolderDialog.cs - - - Windows - AddFolderDialog.cs + + Icons.Settings.png - - Windows - - - Windows - TextEditDialog.cs - - - Windows - TextEditDialog.cs + + LICENSE.txt + LICENSE.txt - - Windows - MainView.cs + + + + TreeView.Root.png - - Windows - ResXFileCodeGenerator - Resources.Designer.cs - Designer + + TreeView.File.png - - Windows - AboutDialog.cs + + TreeView.FileMissing.png - - Windows - ReferenceDialog.cs + + TreeView.Folder.png - - Windows - True - Resources.resx - True - - - Windows - SettingsSingleFileGenerator - Settings.Designer.cs - - - Windows - True - Settings.settings - True - - - Windows - NewContentDialog.cs + + TreeView.FolderMissing.png + + + TreeView.Missing.png - - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS,Linux - - - MacOS - - - Linux - - - - - - - - - - - - - - - - + + Build.EndFailed.png + + + Build.EndSucceed.png + + + Build.Fail.png + + + Build.Information.png + + + Build.Processing.png + + + Build.Skip.png + + + Build.Start.png + + + Build.Succeed.png + + + Build.SucceedWithWarnings.png + - - - Toolbar.New.png + + + Commands.Build.png - - Toolbar.Open.png + + Commands.CancelBuild.png - - Toolbar.Save.png + + Commands.Clean.png - - Toolbar.NewItem.png + + Commands.Close.png - - Toolbar.ExistingItem.png + + Commands.Delete.png - - Toolbar.NewFolder.png + + Commands.ExistingFolder.png - - Toolbar.ExistingFolder.png + + Commands.ExistingItem.png - - Toolbar.Build.png + + Commands.Help.png - - Toolbar.Rebuild.png + + Commands.New.png - - Toolbar.Clean.png + + Commands.NewFolder.png - - Toolbar.FilterOutput.png + + Commands.NewItem.png - - - - Linux + + Commands.Open.png + + + Commands.OpenItem.png + + + Commands.Rebuild.png + + + Commands.Redo.png + + + Commands.Rename.png + + + Commands.Save.png + + + Commands.SaveAs.png + + + Commands.Undo.png - + PreserveNewest - - + + PreserveNewest - - + + PreserveNewest - - + + PreserveNewest - - + + PreserveNewest - - + + PreserveNewest - - + + PreserveNewest - - + + PreserveNewest - - + + PreserveNewest - - + + PreserveNewest - - + + PreserveNewest - + MacOS diff --git a/Build/Projects/PipelineReferences.definition b/Build/Projects/PipelineReferences.definition index ca930345589..eacdb9c35d2 100644 --- a/Build/Projects/PipelineReferences.definition +++ b/Build/Projects/PipelineReferences.definition @@ -2,6 +2,11 @@ + + + + + - - - - - - - - + + + + + + + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..b42d7a0769c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,407 @@ +# Change Log + + +## 3.6 Release - 2/28/2017 + + - Fixed XML deserialization of Curve type. [#5494](https://github.com/MonoGame/MonoGame/pull/5494) + - Fix #5498 Pipeline Tool template loading on MacOS. [#5501](https://github.com/MonoGame/MonoGame/pull/5501) + - Fix typo in the exclude.addins which cause warnings when installing the Addin in XS. [#5500](https://github.com/MonoGame/MonoGame/pull/5500) + - Added support for arbitrary defines passed to the Effect compiler. [#5496](https://github.com/MonoGame/MonoGame/pull/5496) + - Fixed GraphicsDevice.Present() to check for current render target. [#5389](https://github.com/MonoGame/MonoGame/pull/5389) + - Custom texture compression for SpriteFonts. [#5299](https://github.com/MonoGame/MonoGame/pull/5299) + - Performance improvements to SpriteBatch.DrawString(). [#5226](https://github.com/MonoGame/MonoGame/pull/5226) + - Removed the OUYA platform [#5194](https://github.com/MonoGame/MonoGame/pull/5194) + - Dispose of all graphical resources in unit tests. [#5133](https://github.com/MonoGame/MonoGame/pull/5133) + - Throw NoSuitableGraphicsDeviceException if graphics device creation fails. [#5130](https://github.com/MonoGame/MonoGame/pull/5130) + - Optimized and added additional constructors to Color. [#5117](https://github.com/MonoGame/MonoGame/pull/5117) + - Added SamplerState.TextureFilterMode to correctly support comparison filtering. [#5112](https://github.com/MonoGame/MonoGame/pull/5112) + - Fixed Apply3D() on stereo SoundEffect. [#5099](https://github.com/MonoGame/MonoGame/pull/5099) + - Fixed Effect.OnApply to return void to match XNA. [#5090](https://github.com/MonoGame/MonoGame/pull/5090) + - Fix crash when DynamicSoundEffectInstance not disposed. [#5075](https://github.com/MonoGame/MonoGame/pull/5075) + - Texture2D.FromStream now correctly throws on null arguments. [#5050](https://github.com/MonoGame/MonoGame/pull/5050) + - Implemented GraphicsAdapter for DirectX platforms. [#5024](https://github.com/MonoGame/MonoGame/pull/5024) + - Fixed initialization of GameComponent when created within another GameComponent. [#5020](https://github.com/MonoGame/MonoGame/pull/5020) + - Improved SoundEffect internal platform extendability. [#5006](https://github.com/MonoGame/MonoGame/pull/5006) + - Refactored audio processing for platform extensibility. [#5001](https://github.com/MonoGame/MonoGame/pull/5001) + - Refactored texture processing for platform extensibility. [#4996](https://github.com/MonoGame/MonoGame/pull/4996) + - Refactor ShaderProfile to allow for pipeline extensibility. [#4992](https://github.com/MonoGame/MonoGame/pull/4992) + - Removed unnessasary dictionary lookup for user index buffers for DirectX platforms. [#4988](https://github.com/MonoGame/MonoGame/pull/4988) + - New SetRenderTargets() method which allows for variable target count. [#4987](https://github.com/MonoGame/MonoGame/pull/4987) + - Added support for XACT reverb and filter effects. [#4974](https://github.com/MonoGame/MonoGame/pull/4974) + - Remove array in GamePadDPad constructor. [#4970](https://github.com/MonoGame/MonoGame/pull/4970) + - Updated to the latest version of Protobuild. [#4964](https://github.com/MonoGame/MonoGame/pull/4964) + - Fixed static VBs and IBs on UWP on XB1. [#4955](https://github.com/MonoGame/MonoGame/pull/4955) + - Updated to the latest version of Protobuild. [#4950](https://github.com/MonoGame/MonoGame/pull/4950) + - Update Xamarin Studio addin for latest platform changes. [#4926](https://github.com/MonoGame/MonoGame/pull/4926) + - Replace OpenTK with custom OpenGL bindings [#4874](https://github.com/MonoGame/MonoGame/pull/4874) + - Fix Mouse updating when moving the Window. [#4924](https://github.com/MonoGame/MonoGame/pull/4924) + - Fix incorrect use of startIndex in Texture2D.GetData DX. [#4833](https://github.com/MonoGame/MonoGame/pull/4833) + - Cleanup of AssemblyInfo for framework assembly. [#4810](https://github.com/MonoGame/MonoGame/pull/4810) + - New SDL2 backend for desktop GL platforms. [#4428](https://github.com/MonoGame/MonoGame/pull/4428) + - Two MaterialProcessor properties fixed. [#4746](https://github.com/MonoGame/MonoGame/pull/4746) + - Fixed thumbstick virtual buttons to always use independent axes. [#4742](https://github.com/MonoGame/MonoGame/pull/4742) + - Fixed back buffer MSAA on DirectX platforms. [#4739](https://github.com/MonoGame/MonoGame/pull/4739) + - Added new CHANGELOG.md to project. [#4732](https://github.com/MonoGame/MonoGame/pull/4732) + - Added obsolete attribute and updated documentation. [#4731](https://github.com/MonoGame/MonoGame/pull/4731) + - Fixed layout of UWP windows in VS template to ignore window chrome. [#4727](https://github.com/MonoGame/MonoGame/pull/4727) + - Remove support for reading raw assets through ContentManager. [#4726](https://github.com/MonoGame/MonoGame/pull/4726) + - Implemented DynamicSoundEffectInstance for DirectX and OpenAL platforms. [#4715](https://github.com/MonoGame/MonoGame/pull/4715) + - Removed unused Yeti Mp3 compressor. [#4713](https://github.com/MonoGame/MonoGame/pull/4713) + - MonoGame Portable Assemblies. [#4712](https://github.com/MonoGame/MonoGame/pull/4712) + - Fixed RGBA64 packing and added unit tests. [#4683](https://github.com/MonoGame/MonoGame/pull/4683) + - Fix Gamepad crash when platform doesn't support the amount. [#4677](https://github.com/MonoGame/MonoGame/pull/4677) + - Fixed Song stopping before they are finished on Windows. [#4668](https://github.com/MonoGame/MonoGame/pull/4668) + - Removed the Linux .deb installer. [#4665](https://github.com/MonoGame/MonoGame/pull/4665) + - OpenAssetImporter is now automatically selected for all the formats it supports. [#4663](https://github.com/MonoGame/MonoGame/pull/4663) + - Fixed broken unit tests under Linux. [#4614](https://github.com/MonoGame/MonoGame/pull/4614) + - Split out Title Container into partial classes. [#4590](https://github.com/MonoGame/MonoGame/pull/4590) + - Added Rider Support to Linux installer. [#4589](https://github.com/MonoGame/MonoGame/pull/4589) + - Implement vertexStride in VertexBuffer.SetData for OpenGL. [#4568](https://github.com/MonoGame/MonoGame/pull/4568) + - Performance improvement to SpriteBatch vertex generation. [#4547](https://github.com/MonoGame/MonoGame/pull/4547) + - Optimization of indices initialization in SpriteBatcher. [#4546](https://github.com/MonoGame/MonoGame/pull/4546) + - Optimized ContentReader to decode LZ4 compressed streams directly. [#4522](https://github.com/MonoGame/MonoGame/pull/4522) + - TitleContainer partial class cleanup. [#4520](https://github.com/MonoGame/MonoGame/pull/4520) + - Remove raw asset support from ContentManager. [#4489](https://github.com/MonoGame/MonoGame/pull/4489) + - Initial implementation of RenderTargetCube for OpenGL. [#4488](https://github.com/MonoGame/MonoGame/pull/4488) + - Removed unnecessary platform differences in MGFX. [#4486](https://github.com/MonoGame/MonoGame/pull/4486) + - SoundEffect fixes and tests. [#4469](https://github.com/MonoGame/MonoGame/pull/4469) + - Cleanup FX syntax for shader compiler. [#4462](https://github.com/MonoGame/MonoGame/pull/4462) + - General Improvements to Pipeline Gtk implementation. [#4459](https://github.com/MonoGame/MonoGame/pull/4459) + - ShaderProfile Refactor. [#4438](https://github.com/MonoGame/MonoGame/pull/4438) + - GraphicsDeviceManager partial class refactor. [#4425](https://github.com/MonoGame/MonoGame/pull/4425) + - Remove legacy Storage classes. [#4320](https://github.com/MonoGame/MonoGame/pull/4320) + - Added mipmap generation for DirectX render targets. [#4189](https://github.com/MonoGame/MonoGame/pull/4189) + + +## 3.5.1 Release - 3/30/2016 + + - Fixed negative values when pressing up on left thumbstick on Mac. + - Removed exception and just return empty state when requesting an invalid GamePad index. + - Fixed texture processing for 64bpp textures. + - Fixed Texture2D.SaveAsPng on Mac. + + +## 3.5 Release - 3/17/2016 + + - Content Pipeline Integration for Xamarin Studio and MonoDevleop on Mac and Linux. + - Automatic inclusion of XNBs into your final project on Mac and Linux. + - Improved Mac and Linux installers. + - Assemblies are now installed locally on Mac and Linux just like they are on Windows. + - New cross-platform Desktop project where same binary and content will work on Windows, Linux and Mac desktops. + - Better Support for Xamarin.Mac and Xam.Mac. + - Apple TV support (requires to be built from source at the moment). + - Various sound system fixes. + - New GraphicsMetrics API. + - Optimizations to SpriteBatch performance and garbage generation. + - Many improvements to the Pipeline tool: added toolbar, new filtered output view, new templates, drag and drop, and more. + - New GamePad support for UWP. + - Mac and Linux now support Vorbis compressed music. + - Major refactor of texture support in content pipeline. + - Added 151 new unit tests. + - Big improvements to FBX and model content processing. + - Various fixes to XML serialization. + - MediaLibrary implementation for Windows platforms. + - Removed PlayStation Mobile platform. + - Added content pipeline extension template project. + - Support for binding multiple vertex buffers in a draw call. + - Fixed deadzone issues in GamePad support. + - OcclusionQuery support for DX platforms. + - Fixed incorrect z depth in SpriteBatch. + - Lots of OpenTK backend fixes. + - Much improved font processing. + - Added new VertexPosition vertex format. + - Better VS project template installation under Windows. + + +## 3.4 Release - 4/29/2015 + + - Removed old XNA content pipeline extensions. + - Added all missing PackedVector types. + - Replacement of old SDL joystick path with OpenTK. + - Added SamplerState.ComparisonFunction feature to DX and OGL platforms. + - Fixed bug where content importers would not be autodetected on upper case file extensions. + - Fixed compatibility with XNA sound effect XNBs. + - Lots of reference doc improvements. + - Added SamplerState.BorderColor feature to DX and OGL platforms. + - Lots of improvements to the Mac, Linux and Windows versions of the Pipeline GUI tool. + - Fixes for bad key mapping on Linux. + - Support for texture arrays on DX platforms. + - Fixed broken ModelMesh.Tag + - VS templates will now only install if VS is detected on your system. + - Added Color.MonoGameOrange. + - Fixed Xact SoundBack loading bug on Android. + - Added support for a bunch of missing render states to MGFX. + - Added support for sRGB texture formats to DX and OGL platforms. + - Added RasterizerState.DepthClipEnable support for DX and OGL platforms. + - New support for the Windows 10 UAP plafform. + - Fixed bug which caused the GamePad left thumbstick to not work correctly. + - Preliminary base classed for future Joystick API. + - Performance improvement on iOS by avoiding unnessasary GL context changes. + - Fixed bug where MediaPlayer volume affected all sounds. + - New XamarinStudio/MonoDevelop Addin for Mac. + - New Mac installer packages. + + +## 3.3 Release - 3/16/2015 + + - Support for vertex texture fetch on Windows. + - New modern classes for KeyboardInput and MessageBox. + - Added more validation to draw calls and render states. + - Cleaned up usage of statics to support multiple GraphicsDevice instances. + - Support Window.Position on WindowsGL platform. + - Reduction of redundant OpenGL calls. + - Fullscreen support for Windows DX platform. + - Implemented Texture2D SaveAsPng and SaveAsJpeg for Android. + - Improved GamePad deadzone calculations. + - We now use FFmpeg for audio content building. + - BoundingSphere fixes and optimizations. + - Many improvements to Linux platform. + - Various fixes to FontTextureProcessor. + - New Windows Universal App template for Windows Store and Windows Phone support. + - Many fixes to reduce garbage generation during runtime. + - Adding support for TextureFormatOptions to FontDescriptionProcessor. + - XNA compatibility improvements to FontDescriptionProcessor. + - Resuscitated the unit test framework with 100s of additional unit tests. + - BoundingFrustum fixes and optimizations. + - Added VS2013 project templates. + - Moved to new MonoGame logo. + - Added MSAA render target support for OpenGL platforms. + - Added optional content compression support to content pipeline and runtime. + - TextureCube content reader and GetData fixes. + - New OpenAL software implementation for Android. + - Xact compatibility improvements. + - Lots of Android fixes and improvements. + - Added MediaLibrary implementation for Android, iOS, Windows Phone, and Windows Store. + - Added ReflectiveWriter implementation to content pipeline. + - Fixes to Texture2D.GetData on DirectX platforms. + - SpriteFont rendering performance optimizations. + - Huge refactor of ModelProcessor to be more compatible with XNA. + - Moved NET and GamerServices into its own MonoGame.Framework.Net assembly. + - Runtime support for ETC1 textures for Androud. + - Improved compatibility for FBXImporter and XImporter. + - Multiple SpritBatch compatibility fixes. + - We now use FreeImage in TextureImporter to support many more input formats. + - MGFX parsing and render state improvements. + - New Pipeline GUI tool for managing content projects for Windows, Mac, and Linux desktops. + - New implementation of content pipeline IntermediateSerializer. + - All tools and content pipeline built for 64-bit. + - New documentation system. + - Implement web platform (JSIL) stubs. + - Lots of fixes to PSM. + - Added Protobuild support for project generation. + - Major refactor of internals to better separate platform specific code. + - Added MGCB command line tool to Windows installer. + + +## 3.2 Release - 4/7/2014 + + - Implemented missing PackedVector types. + - VS2013 support for MonoGame templates. + - Big improvement to XInput performance on Windows/Windows8. + - Added GameWindow.TextInput event enhancement. + - Added Xamarin.Mac compatability. + - Support for WPF interop under DirectX. + - Enhancement to support multiple GameWindows on Windows under DirectX. + - Various SpriteFont compatibility improvements. + - OpenAL performance/memory/error handling improvements. + - Reduction of Effect runtime memory usage. + - Support for DXT/S3TC textures on Android. + - Touch support on Windows desktop games. + - Added new RenderTarget3D enhancement. + - OUYA gamepad improvements. + - Internal improvements to reduce garbage generation. + - Various windowing fixes for OpenTK on Linux, Mac, and Windows. + - Automatic support for content reloading on resume for Android. + - Support for TextureCube, Texture3D, and RenderTargetCube on DirectX. + - Added TitleContainer.SupportRetina enhancement for loading @2x content. + - Lots of Android/Kindle compatibility fixes. + - Added enhancement GameWindow.IsBorderless. + - OpenGL now supports multiple render targets. + - Game.IsRunningSlowly working accurately to XNA. + - Game tick resolution improvements. + - XACT compatibility improvements. + - Various fixes and improvements to math types. + - DrawUserIndexedPrimitives now works with 32bit indicies. + - GamerServices fixes under iOS. + - Various MonoGame FX improvements and fixes. + - Render target fixes for Windows Phone. + - MediaPlayer/MediaQueue/Song fixes on Windows Phone. + - XNA accuracy fixes to TitleContainer. + - Fixes to SpriteBatch performance and compatibility with XNA. + - Threading fixes around SoundEffectInstance. + - Support for Song.Duration. + - Fixed disposal of OpenGL shader program cache. + - Improved support of PoT textures in OpenGL. + - Implemented missing EffectParameter SetValue/GetValue calls. + - Touch fixes to Windows Phone. + - Fixes to orientation support in iOS. + - Lots of PSM fixes which make it usable for 2D games. + - New Windows desktop platform using DirectX/XAudio. + - Old Windows project renamed WindowsGL. + - Fixed offsetInBytes parameter in IndexBuffer/VertexBuffer SetData. + - Fixed subpixel offset when viewport is changed in OpenGL. + - Tons of content pipeline improvements making it close to complete. + + +## 3.0.1 Release - 3/3/2013 + + - Fix template error. + - Fix offsetInBytes parameter in IndexBuffer/VertexBuffer SetData. + - Fixes the scale applied on the origin in SpriteBatch. + - Fixed render targets on WP8. + - Removed minVertexIndex Exception. + - Fixed some threading issues on iOS. + - Use generic link for opening store on iOS. + - Fix Matrix::Transpose. + - Fixed vertexOffset in DrawUserIndexedPrimitives in GL. + - Keys.RightControl/RightShift Support for WinRT. + - Dispose in ShaderProgramCache. + - IsRunningSlowly Fix. + + +## 3.0 Release - 1/21/2013 + + - 3D (many thanks to Infinite Flight Studios for the code and Sickhead Games in taking the time to merge the code in). + - New platforms: Windows 8, Windows Phone 8, OUYA, PlayStation Mobile (including Vita). + - Custom Effects. + - PVRTC support for iOS. + - iOS supports compressed Songs. + - Skinned Meshs. + - VS2012 templates. + - New Windows Installer. + - New MonoDevelop Package/AddIn. + - A LOT of bug fixes. + - Closer XNA 4 compatibility. + + +## 2.5.1 Release - 6/18/2012 + + - Updated android to use enumerations rather than hardocded ids as part of the Mono for Android 4.2 update. + - Changed the Android video player to make use of the ViewView. + - Corrected namespaces for SongReader and SoundEffectReader. + - Updated the Keyboard mapping for android. + - Added RectangleArrayReader. + - Removed links to the third party GamePadBridge. + - Added some missing mouseState operators. + - Replaced all calls to DateTime.Now with DateTime.UtcNow. + - Fixed SpriteFont rendering (again). + - Added code to correclty dispose of Textures on all platforms. + - Added some fixes for the sound on iOS. + - Adding missing MediaQueue class. + - Fixed Rectangle Intersect code. + - Changed the way UserPrimitives work on windows. + - Made sure the @2x file support on iOS works. + - Updated project templates. + - Added project templates for MacOS. + - Fixed MonoDevelop.MonoGame AddIn so it works on Linux. + + +## 2.5 Release - 3/29/2012 + +### Fixes and Features + - Minor fixes to the Networking stack to make it more reliable when looking for games. + - SpriteBatch Fixes including making sure the matrix parameter is applied in both gles 1.1 and gles 2.0. + - Updated IDrawable and IUpdatable interfaces to match XNA 4.0. + - Fixed the Tick method. + - Updated VideoPlayer constructor contract to match XNA 4.0. + - Added Code to Lookup the Host Application Guid for Networking, the guid id is now pulled from the AssemblyInfo.cs if one is present. + - Uses OpenAL on all platforms except Android. + - Added Dxt5 decompression support. + - Improves SpriteFont to conform more closely to XNA 4.0. + - Moved DynamicVertexBuffer and DynamicIndexBuffer into its own files. + +### iOS + - Fixed Console.WriteLine problem. + - Fixed loading of @2x Retina files. + - Fixed Landscape Rendering. + - Fixed Orientations changes correctly animate. + - Fixed Guide.BeginShowKeyboardInput. + - Fixed StorageDevice AOT compile problem. + - Fixed SpriteBatch to respect matrices when drawn. + - Fixed DoubleTap, improves touches in serial Game instances. + - Fixed App startup in non-Portrait orientations. + - Fixed UnauthorizedAccessException using TitleContainer. + - Fixed a runtime JIT error that was occuring with List(). + - Guide.ShowKeyboard is not working. + - App Backgrounding has regressed. A patch is already being tested in the develop branch and the fix will be rolled out as part of the v2.5.1. + +### Android + - Project Templates for MonoDevelop. + - Fixed a few issues with Gestures. + - Fixed the name of the assembly to be MonoGame.Framework.Android. + - Fixed a Memory Leak in Texture Loading. + - Force linear filter and clamp wrap on npot textures in ES2.0 on Android. + - Added SetData and GetData support for Texture2D. + - Guide.SignIn picks up the first email account on the phone. + - CatapultWars does not render correctly under gles 1.1. + +### MacOS X + - SoundEffectInstance.Stop now works correctly. + +### Linux + - Project Templates for Visual Studio and MonoDevelop. + - Fixed a bug when loading of Wav files. + +### Windows + - Project Templates for Visual Studio and MonoDevelop. + - Fixed a bug when loading of Wav files. + - Added Game.IsMouseVisible implementation for Windows. + - Guide.SignIn picks up the logged in user. + - Added a new Installer to install the MonoDevelop and / or Visual Studio Templates and binaries. + + +## 2.1 Release - 10/28/2011 + +### Features + - Content Manager rewritten to use partial classes and implementation of cached assets that are loaded. Greatly improves memory footprint. + - Experimental support for GamePads and Joysticks. Enhancements will be coming to integrate better for developers. + - ContentReader improvements across the board. + - Improved support for XACT audio. + - StarterKits VectorRumble. + +### iOS + - Gesture support has been improved. + - Better support for portrait to landscape rotations. + - Fixed a rendering bug related to upsidedown portrait mode. + - Better WaveBank support. + - The Guide functionality is only available in iOS, for this release. + +### Android + - Updated to support Mono for Android 4.0. + - Improvements to the Orientation Support. + - Changed Sound system to use SoundPool. + - Added Tap and DoubleTap Gesture Support. + +### MacOS X + - A lot of enhancements and fixes for Full Screen and Windowed control. + - Cursor support fixed for IsMouseVisible. + - Implementation of IsActive property and the events Activated and Deactivated. + - First steps of DrawPrimitives, DrawUserPrimitives, DrawIndexedPrimitives. + - Better WaveBank support. + - Support for ApplyChanges() and setting the backbuffer and viewport sizes correctly. + +### Linux + - All new implementation which share quite a bit of code between MacOS X and Windows. + - Added shader support via the Effects class. + +### Windows + - All new implementation which shares quite a bit of code between MacOS and Linux. + + +## 2.0 Release - 10/28/2011 + + - Project renamed MonoGame. + - Project moved to GitHub. + - Support for Linux, Mac, Linux, and OpenGL on Windows. + + +## 0.7 Release - 12/2/2009 + + - First stable release. + - Originally named XnaTouch. + - iPhone support only. + - 2D rendering support. + - Audio support. + - Networking support. + - Partial multitouch support. + - Partial accelerometer support. diff --git a/CODESTYLE.md b/CODESTYLE.md new file mode 100644 index 00000000000..f7c5216ac02 --- /dev/null +++ b/CODESTYLE.md @@ -0,0 +1,228 @@ +> #### NOTE: This code style standard for MonoGame is a work in progress and much of the code does not currently conform to these rules. This is something that will be addressed by the core team. + +# Introduction +As the MonoGame project gains more traction and becomes more widely used, we are aiming to provide a more professional and consistent look to the large amount of source now in the project. It was a broadly supported decision by the core development team to follow the Microsoft coding guidelines (the default provided in Visual Studio's C# editor). These coding guidelines listed below have been based on a [MSDN blog post](http://blogs.msdn.com/b/brada/archive/2005/01/26/361363.aspx) from 2005 by Brad Abrams describing the internal coding guidelines at Microsoft, with some changes to suit our project. +# Coding Guidelines +## Tabs & Indenting +Tab characters (\0x09) should not be used in code. All indentation should be done with 4 space characters. +## Bracing +Open braces should always be at the beginning of the line after the statement that begins the block. Contents of the brace should be indented by 4 spaces. Single statements do not have braces. For example: +``` +if (someExpression) +{ + DoSomething(); + DoAnotherThing(); +} +else + DoSomethingElse(); +``` + +`case` statements should be indented from the switch statement like this: +``` +switch (someExpression) +{ + case 0: + DoSomething(); + break; + + case 1: + DoSomethingElse(); + break; + + case 2: + { + int n = 1; + DoAnotherThing(n); + } + break; +} +``` + +Braces are not used for single statement blocks immediately following a `for`, `foreach`, `if`, `do`, etc. The single statement block should always be on the following line and indented by four spaces. This increases code readability and maintainability. +``` +for (int i = 0; i < 100; ++i) + DoSomething(i); +``` + +## Single line property statements +Single line property statements can have braces that begin and end on the same line. This should only be used for simple property statements. Add a single space before and after the braces. +``` +public class Foo +{ + int bar; + + public int Bar + { + get { return bar; } + set { bar = value; } + } +} +``` + +## Commenting +Comments should be used to describe intention, algorithmic overview, and/or logical flow. It would be ideal, if from reading the comments alone, someone other than the author could understand a functions intended behavior and general operation. While there are no minimum comment requirements and certainly some very small routines need no commenting at all, it is hoped that most routines will have comments reflecting the programmers intent and approach. + +Comments must provide added value or explanation to the code. Simply describing the code is not helpful or useful. +``` + // Wrong + // Set count to 1 + count = 1; + + // Right + // Set the initial reference count so it isn't cleaned up next frame + count = 1; +``` + +### Copyright/License notice +Each file should start with a copyright notice. To avoid errors in doc comment builds, you dont want to use triple-slash doc comments. This is a short statement declaring the project name, copyright notice and directing the reader to the license document elsewhere in the project. +``` +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. +``` + +### Documentation Comments +All methods should use XML doc comments. For internal dev comments, the `` tag should be used. +``` +public class Foo +{ + /// Public stuff about the method + /// What a neat parameter! + /// Cool internal stuff! + public void MyMethod(int bar) + { + + } +} +``` + +### Comment Style +The // (two slashes) style of comment tags should be used in most situations. Where ever possible, place comments above the code instead of beside it. Here are some examples: +``` + // This is required for WebClient to work through the proxy + GlobalProxySelection.Select = new WebProxy("http://itgproxy"); + + // Create object to access Internet resources + WebClient myClient = new WebClient(); +``` + +## Spacing +Spaces improve readability by decreasing code density. Here are some guidelines for the use of space characters within code: + +Do use a single space after a comma between function arguments. +``` +Console.In.Read(myChar, 0, 1); // Right +Console.In.Read(myChar,0,1); // Wrong +``` +Do not use a space after the parenthesis and function arguments +``` +CreateFoo(myChar, 0, 1) // Right +CreateFoo( myChar, 0, 1 ) // Wrong +``` +Do not use spaces between a function name and parenthesis. +``` +CreateFoo() // Right +CreateFoo () // Wrong +``` +Do not use spaces inside brackets. +``` +x = dataArray[index]; // Right +x = dataArray[ index ]; // Wrong +``` +Do use a single space before flow control statements +``` +while (x == y) // Right +while(x==y) // Wrong +``` +Do use a single space before and after binary operators +``` +if (x == y) // Right +if (x==y) // Wrong +``` +Do not use a space between a unary operator and the operand +``` +++i; // Right +++ i; // Wrong +``` +Do not use a space before a semi-colon. Do use a space after a semi-colon if there is more on the same line +``` +for (int i = 0; i < 100; ++i) // Right +for (int i=0 ; i<100 ; ++i) // Wrong +``` + +## Naming +Follow all .NET Framework Design Guidelines for both internal and external members. Highlights of these include: +* Do not use Hungarian notation +* Do use an underscore prefix for member variables, e.g. `_foo` +* Do use camelCasing for member variables (first word all lowercase, subsequent words initial uppercase) +* Do use camelCasing for parameters +* Do use camelCasing for local variables +* Do use PascalCasing for function, property, event, and class names (all words initial uppercase) +* Do prefix interfaces names with I +* Do not prefix enums, classes, or delegates with any letter + +The reasons to extend the public rules (no Hungarian, underscore prefix for member variables, etc.) is to produce a consistent source code appearance. In addition a goal is to have clean readable source. Code legibility should be a primary goal. + +## File Organization +* Source files should contain only one public type, although multiple internal types are permitted if required +* Source files should be given the name of the public type in the file +* Directory names should follow the namespace for the class after `Framework`. For example, I would expect to find the public class `Microsoft.Xna.Framework.Graphics.GraphicsDevice` in **MonoGame.Framework\Graphics\GraphicsDevice.cs** +* Class members should be grouped logically, and encapsulated into regions (Fields, Constructors, Properties, Events, Methods, Private interface implementations, Nested types) +* Using statements should be before the namespace declaration. +``` +using System; + +namespace MyNamespace +{ + public class MyClass : IFoo + { + #region Fields + int foo; + #endregion + + #region Properties + public int Foo { get { } set { } } + #endregion + + #region Constructors + public MyClass() + { + + } + #endregion + + #region Events + public event EventHandler FooChanged { add { } remove { } } + #endregion + + #region Methods + void DoSomething() + { + + } + + void FindSomething() + { + + } + #endregion + + #region Private interface implementations + void IFoo.DoSomething() + { + DoSomething(); + } + #endregion + + #region Nested types + class NestedType + { + + } + #endregion + } +} +``` + +# Useful Links +[C# Coding Conventions (MSDN)](http://msdn.microsoft.com/en-us/library/ff926074.aspx) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aeb326c3599..d33f92b98ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ Here are a few simple rules and suggestions to remember when contributing to Mon * **PLEASE** be sure to write simple and descriptive commit messages. * **DO NOT** surprise us with new APIs or big new features. Open an issue to discuss your ideas first. * **DO NOT** reorder type members as it makes it difficult to compare code changes in a PR. -* **DO** try to follow our [coding style](https://github.com/mono/MonoGame/wiki/Coding-Guidelines) for new code. +* **DO** try to follow our [coding style](CODESTYLE.md) for new code. * **DO** give priority to the existing style of the file you're changing. * **DO** try to add to our [unit tests](Test) when adding new features or fixing bugs. * **DO NOT** send PRs for code style changes or make code changes just for the sake of style. diff --git a/Documentation/Styles/MonoGame/css/sharpdoc.css b/Documentation/Styles/MonoGame/css/sharpdoc.css index 0df6f1e0b62..a8ca4c3e676 100644 --- a/Documentation/Styles/MonoGame/css/sharpdoc.css +++ b/Documentation/Styles/MonoGame/css/sharpdoc.css @@ -378,17 +378,6 @@ background-image:url("../images/android.png"); } -.sharpdoc div.icon.ouya -{ - background-image:url("../images/ouya.png"); -} - -.sharpdoc div.icon.psm -{ - background-image:url("../images/psm.png"); -} - - .sharpdoc .content .section .members table { table-layout:fixed; width:100%; diff --git a/Documentation/config.xml b/Documentation/config.xml index 4f73210c32c..319231d96a7 100644 --- a/Documentation/config.xml +++ b/Documentation/config.xml @@ -50,6 +50,8 @@ + + @@ -88,11 +90,6 @@ ..\MonoGame.Framework\bin\Windows8\AnyCPU\Release\MonoGame.Framework.dll - - - ..\MonoGame.Framework\bin\WindowsPhone\x86\Release\MonoGame.Framework.dll - - ..\MonoGame.Framework\bin\Web\AnyCPU\Release\MonoGame.Framework.dll @@ -112,11 +109,4 @@ --> - - - diff --git a/Documentation/content_intro.md b/Documentation/content_intro.md index e1a862a8b23..dce6e337783 100644 --- a/Documentation/content_intro.md +++ b/Documentation/content_intro.md @@ -6,7 +6,7 @@ This section will cover the following topics: - What is Game Content - [Using The Pipeline Tool](using_pipeline_tool.md) - - [Using TrueType Fonts](adding_fonts.md) + - [Using TrueType Fonts](adding_ttf_fonts.md) - [Custom Effects](custom_effects.md) - Custom Content Types diff --git a/Documentation/mgcb.md b/Documentation/mgcb.md index 888755d965c..947d0d64002 100644 --- a/Documentation/mgcb.md +++ b/Documentation/mgcb.md @@ -48,15 +48,12 @@ An optional parameter which adds an assembly reference which contains importers, Set the target platform for this build. It must be a member of the TargetPlatform enum: * Windows * Xbox360 -* WindowsPhone * iOS * Android * Linux * MacOSX * WindowsStoreApp * NativeClient -* Ouya -* PlayStationMobile * PlayStation4 * WindowsPhone8 * RaspberryPi diff --git a/Documentation/pipeline.md b/Documentation/pipeline.md index 9c545da97d0..b8daab6855a 100644 --- a/Documentation/pipeline.md +++ b/Documentation/pipeline.md @@ -4,10 +4,10 @@ The MonoGame Pipeline Tool (Pipeline.exe) is the front-end GUI editor for MonoGa

-The Pipeline Tool is still relatively new to MonoGame, but it already has the following features: +The Pipeline Tool has the following features: * Create, open, and save MGCB projects. - * Import existing .contentproj. + * Import existing XNA .contentproj. * Tree view showing content of project. * Property grid for editing content settings. * Full undo/redo support. @@ -17,7 +17,7 @@ The Pipeline Tool is still relatively new to MonoGame, but it already has the fo * Support for custom importers/processors/writers. * Template format for adding new custom content types. -Currently the Pipeline Tool is only available on Windows and is part of the SDK installation. The Mac and Linux versions are in development and should be available soon. +The Pipeline Tool is included in the SDK installation. [Read detailed documentation](using_pipeline_tool.md) diff --git a/Documentation/setting_up_monogame_linux.md b/Documentation/setting_up_monogame_linux.md index bc859a09ec8..8f49b03acc7 100644 --- a/Documentation/setting_up_monogame_linux.md +++ b/Documentation/setting_up_monogame_linux.md @@ -13,27 +13,18 @@ sudo apt-get install libopenal-dev mono-runtime ### Developing MonoGame Applications -##### From Ubuntu 15.04 or newer: - -* Go to [MonoGame Downloads page](http://www.monogame.net/downloads/) -* Download MonoGame for Ubuntu -* Open up "monogame-sdk.deb" and install it -* That's it, MonoGame SDK is installed - -##### From other Linux distros: - * Go to [MonoGame Downloads page](http://www.monogame.net/downloads/) -* Download MonoGame for other Linux distros +* Download MonoGame for Linux * Open up terminal and type in: ``` cd Downloads chmod +x monogame-sdk.run sudo ./monogame-sdk.run ``` -* During the installation process the installer will ask you if you wish to install any missing dependencies automatically. If you for some reason don't want to install them automatically or the dependency installer is not available for your linux distribution, here is the list of needed packages: +* During the installation process the installer will give you the following list of dependencies, please make sure they are installed: * monodevelop ([http://www.monodevelop.com/download/](http://www.monodevelop.com/download/)) * libopenal-dev - * referenceassemblies-pcl * gtk-sharp3 + * referenceassemblies-pcl (needed to use PCL template) * ttf-mscorefonts-installer (recommended, but not needed) * That's it, MonoGame SDK is installed diff --git a/Documentation/setting_up_monogame_source.md b/Documentation/setting_up_monogame_source.md index c3e49a55d8a..ae1148aab22 100644 --- a/Documentation/setting_up_monogame_source.md +++ b/Documentation/setting_up_monogame_source.md @@ -4,22 +4,23 @@ This section will help you setup MonoGame by building it from source code. Install the tools for the system you are building from: * Windows: - * [GitHub for Windows](https://windows.github.com/) - * [Visual Studio](https://www.visualstudio.com/) or [Xamarin Studio](http://www.monodevelop.com/download/) - * Optional download Xamarin.Android and Windows Phone 8 SDK + * [Git for Windows](https://git-scm.com/download/win) + * [Visual Studio](https://www.visualstudio.com/) + * [Xamarin.Android](https://www.xamarin.com/download) (Optional) + * [Windows Phone 8 SDK](https://www.microsoft.com/en-us/download/details.aspx?id=35471) (Optional) * Mac: - * [GitHub for Mac](https://mac.github.com/) - * [Xamarin Studio](http://www.monodevelop.com/download/) - * Optional download Xamarin.Android and Xamarin.iOS + * [Git](https://git-scm.com/download/mac) + * [Xamarin Studio](https://store.xamarin.com/) + * Xamarin.Android and Xamarin.iOS can be installed with the Xamarin Studio installer (Optional) * Linux: - * Install package called "git" + * [Git](https://git-scm.com/download/linux) * [Monodevelop](http://www.monodevelop.com/download/linux/) ### Getting the source code -If on Windows, start up Git Shell, if on Unix, start the terminal and type in the following: +Start up a Terminal (Mac/Linux) or Git Bash (Windows) and clone the MonoGame repository: ``` -git clone https://github.com/mono/MonoGame.git +git clone https://github.com/MonoGame/MonoGame.git cd MonoGame git submodule init git submodule update @@ -27,12 +28,13 @@ git submodule update ### Building from source -If on Windows just start Protobuild.exe. +MonoGame uses [Protobuild](https://protobuild.org/) to generate project and solution files. Protobuild.exe will be in your MonoGame folder. To run Protobuild: -if on Unix open terminal and type in: -``` -cd MonoGame -mono Protobuild.exe -``` +- On Windows run Protobuild.exe either by double-clicking or by executing it from the command line. +- On Mac/Linux open a terminal and run `mono Protobuild.exe` in the MonoGame folder. + +Once the project and solution files are generated you can build them with the IDE you installed. + +### Referencing the projects -Now the sln files are generated and you can build them with either Monodevelop or Visual Studio. +First get the MonoGame SDK from the [downloads page](http://www.monogame.net/downloads/) and install it to get the IDE templates. Start up the IDE you have installed and create a new project from one of the templates. Click Add > Existing Project... on your solution and select the MonoGame.Framework project that matches the template (i.e. MonoGame.Framework.Windows.csproj for a MonoGame Windows project template). The project files are located in MonoGame/MonoGame.Framework. Delete the existing MonoGame.Framework reference and add a reference to the added project by clicking Add Reference... > Projects and selecting the project. You can run your game now. If you make changes to the MonoGame.Framework project it will automatically rebuild when running your game. diff --git a/Documentation/tutorials.md b/Documentation/tutorials.md index 423b8edc6f6..c36028ca27c 100644 --- a/Documentation/tutorials.md +++ b/Documentation/tutorials.md @@ -35,7 +35,6 @@ Tara Walker's "Building a Shooter Game" tutorial series. - [BMFont rendering with MonoGame](http://www.craftworkgames.com/blog/tutorial-bmfont-rendering-with-monogame/) - [RB Whitaker's MonoGame Tutorials](http://rbwhitaker.wikidot.com/monogame-tutorials) - [RB Whitaker's XNA Tutorials including sharers, 2D, 3D, Game loops, advancted topics](http://rbwhitaker.wikidot.com/xna-tutorials) - - [Another list of tutorials from the MonoGame github](https://github.com/mono/MonoGame/wiki/Tutorials) - [Setting window position tutorial](http://projectdrake.net/blog/?p=176) - [Using Spine with MonoGame - by Randolph Burt (Randeroo)](http://randolphburt.co.uk/2013/03/30/dragons-and-dancing-crabs/) - [Mac porting series](http://benkane.wordpress.com/2012/01/20/the-great-porting-adventure-day-8/) diff --git a/Documentation/using_pipeline_tool.md b/Documentation/using_pipeline_tool.md index 3fca7416b60..20da1827815 100644 --- a/Documentation/using_pipeline_tool.md +++ b/Documentation/using_pipeline_tool.md @@ -1,4 +1,4 @@ -The [Pipeline Tool](pipeline.md) is used to organize and define content for use with MonoGame. It is installed as part of the MonoGame SDK Installer or can be build [directly from source](https://github.com/mono/MonoGame/tree/develop/Tools/Pipeline) if needed. +The [Pipeline Tool](pipeline.md) is used to organize and build content for use with MonoGame. It is installed as part of the MonoGame SDK Installer or can be built [directly from source](https://github.com/mono/MonoGame/tree/develop/Tools/Pipeline) if needed. ## Create A Project @@ -27,7 +27,7 @@ Note that currently the Pipeline tool is not setup to support multiple target pl ## Adding Content Items -Once you have a project setup you can then add content to it for building. You can do this from the "Edit" menu: +Once you have a project setup you can add content to it for building. You can do this from the "Edit" menu:

@@ -39,12 +39,12 @@ Selecting "New Item..." will bring up the New Item dialog which displays a list

-When you select "Add Item..." you get to select an existing item from disk to add to the content project. +When you select "Existing Item..." you get to select an existing item from disk to add to the content project. ## Custom Content Processors -Justl ike XNA, the MonoGame content pipeline supports custom content processors. To use them you need to rebuild them correctly to work against MonoGame. +Just like XNA, the MonoGame content pipeline supports custom content processors. To use them you need to rebuild them correctly to work against MonoGame. The first step is removing all `Microsoft.Xna.Framework.XXX` references and replacing them with references to `MonoGame.Framework` and `MonoGame.Framework.Content.Pipeline`. This is required as you will no longer be building against Microsoft XNA. @@ -52,19 +52,26 @@ Once you references are working you then need to change your assembly target pla After you have done these fixes you should be able to add these new processors to the content project "References". +## Building Content + +The Pipeline Tool has 3 actions related to building content: Build, Rebuild and Clean. Build will build all content that needs to be built and put the xnb's in the output directory (bin by default). Content will be skipped if it hasn't changed since the last build. The time source content was last edited is saved in the intermediate directory (obj by default) to determine if content changed since the last build. Clean will empty the output and intermediate directories. Rebuild will first Clean and then Build. ## Linking Content To Your Game -Once you have built your content you have a few different ways to add the XNBs to your game project. +Once you have built your content you have a few different ways to add the xnb's to your game project. They all have the same goal, to get the built xnb's in your project output folder so a ContentManager can easily find and load them. + +### MonoGameContentReference + +The simplest method is to setup your game project from one of the templates that come with the SDK. When you create a new project it will include a Content.mgcb file with its Build Action set to MonoGameContentReference. This build action is defined in the .targets file [here](https://github.com/MonoGame/MonoGame/blob/develop/MonoGame.Framework.Content.Pipeline/MonoGame.Content.Builder.targets). MonoGameContentReference is set up so that when the project is built, the mgcb will build any new/modified content and copy the resulting xnb's to the project output directory so they can be used in the project. Note that this way you don't even have to manually build the content with the Pipeline Tool. Just add your content to the .mgcb with the Pipeline Tool and the rest will happen when you build your project. The content files do not need to be added to your project. ### Manual Copy -The simplest and method is to simply copy the content into a Content folder under the output folder where you game executable is. This should work for most desktop builds. +If you don't want to use the automated process, you can build the content project with the Pipeline Tool and copy the xnb's to the output folder of your project manually. ### Add As Content -If you are using Visual Studio you can simply add the content files into your C# game project. Create a folder in the project called Content then right click on the folder and select Add -> Existing Item. +If you are using Visual Studio you can simply add the xnb files to your C# game project. Create a folder in the project called Content then right click on the folder and select Add -> Existing Item.

@@ -85,7 +92,7 @@ Once the files are added you need to select them all and change their build acti ### Add With Wildcard -The more automatic option is to hand edit your game .csproj and have it include you content using wildcards. To do this just open the .csproj with any text editor then add the following after any other ``: +The more automatic option is to hand edit your game .csproj and have it include you content using wildcards. To do this just open the .csproj with any text editor then add the following after any other ``: ``` @@ -100,4 +107,4 @@ Then any files you put in a Content folder within your game project will automat ## Reporting Bugs -The MonoGame content pipeline, MGCB, and the Pipeline tool are all very new in MonoGame. If you run into any problems with them please ask for help on the [community site](http://community.monogame.net/) or submit a [bug report on GitHub](https://github.com/mono/MonoGame/issues). +If you run into any problems with MGCB or the Pipeline Tool, please ask for help on the [community site](http://community.monogame.net/) or submit a [bug report on GitHub](https://github.com/MonoGame/MonoGame/issues). diff --git a/Documentation/welcome.md b/Documentation/welcome.md index 79ba78ef564..b313fc08a29 100644 --- a/Documentation/welcome.md +++ b/Documentation/welcome.md @@ -3,8 +3,6 @@ Welcome to the MonoGame game library documentation hub. ### WORK IN PROGRESS Note that the MonoGame documentation project is currently a work in progress and applies to the **latest development build** of MonoGame. -If you are looking for documentation on previous releases of MonoGame you can reference our [GitHub wiki](http://github.com/mono/MonoGame/wiki) and the original [Codeplex documentation](http://monogame.codeplex.com/documentation) while we're getting this all sorted out. - ### We Need Your Help! Truly great open source projects require high quality documentation. This is call for volunteers to help us make the MonoGame documentation truly great. If you can help write tutorials, guides, code snippets, reference docs, video walkthroughs or just any improvement to our current documentation we could use your help! diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/MonoDevelop.MonoGame.Android.csproj b/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/MonoDevelop.MonoGame.Android.csproj index b8cb33f833b..b6209ad111b 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/MonoDevelop.MonoGame.Android.csproj +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/MonoDevelop.MonoGame.Android.csproj @@ -62,7 +62,6 @@ - @@ -104,8 +103,8 @@ PreserveNewest - - PreserveNewest - + + + diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/templates/Android/Activity1.cs b/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/templates/Android/Activity1.cs index db79b7ad21e..6aa5efde19e 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/templates/Android/Activity1.cs +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/templates/Android/Activity1.cs @@ -8,10 +8,6 @@ using Android.Widget; using Android.OS; -#if OUYA -using Ouya.Console.Api; -#endif - using Microsoft.Xna.Framework; namespace ${Namespace} @@ -22,14 +18,11 @@ namespace ${Namespace} Theme = "@style/Theme.Splash", AlwaysRetainTaskState=true, LaunchMode=LaunchMode.SingleInstance, + ScreenOrientation = ScreenOrientation.FullUser, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden | ConfigChanges.Keyboard | ConfigChanges.ScreenSize)] - #if OUYA - [IntentFilter(new[] { Intent.ActionMain } - , Categories = new[] { Intent.CategoryLauncher, OuyaIntent.CategoryGame })] - #endif public class Activity1 : AndroidGameActivity { protected override void OnCreate (Bundle bundle) diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/templates/MonoGameOUYAProject.xpt.xml b/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/templates/MonoGameOUYAProject.xpt.xml deleted file mode 100644 index dac32e754c3..00000000000 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame.Android/templates/MonoGameOUYAProject.xpt.xml +++ /dev/null @@ -1,64 +0,0 @@ - - diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/MonoDevelop.MonoGame.Mac.csproj b/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/MonoDevelop.MonoGame.Mac.csproj index c8153acaaec..888733b7cfa 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/MonoDevelop.MonoGame.Mac.csproj +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/MonoDevelop.MonoGame.Mac.csproj @@ -39,18 +39,10 @@ /Applications/Xamarin Studio.app/Contents/Resources/lib/monodevelop/bin/MonoDevelop.Core.dll False - - /Applications/Xamarin Studio.app/Contents/Resources/lib/monodevelop/AddIns/MonoDevelop.MonoMac/MonoDevelop.MonoMac.dll - False - /Applications/Xamarin Studio.app/Contents/Resources/lib/monodevelop/AddIns/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.dll False - - /Applications/Xamarin Studio.app/Contents/Resources/lib/monodevelop/AddIns/MonoDevelop.MacDev/MonoDevelop.MacDev.dll - False - diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/templates/MonoGameOSXProject.xpt.xml b/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/templates/MonoGameOSXProject.xpt.xml index 39676ae2f16..0ab9c08f043 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/templates/MonoGameOSXProject.xpt.xml +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/templates/MonoGameOSXProject.xpt.xml @@ -27,8 +27,6 @@ - - @@ -41,6 +39,11 @@ + + + + + diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/templates/MonoGameXamMacProject.xpt.xml b/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/templates/MonoGameXamMacProject.xpt.xml index 712488048eb..d49598230c7 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/templates/MonoGameXamMacProject.xpt.xml +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame.Mac/templates/MonoGameXamMacProject.xpt.xml @@ -27,8 +27,6 @@ - - @@ -42,6 +40,15 @@ + + + + + + + + + diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame.iOS/templates/iOS/Info_tvOS.plist.xml b/IDE/MonoDevelop/MonoDevelop.MonoGame.iOS/templates/iOS/Info_tvOS.plist.xml index 9316a2c3a1f..419e6eaddd0 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame.iOS/templates/iOS/Info_tvOS.plist.xml +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame.iOS/templates/iOS/Info_tvOS.plist.xml @@ -24,8 +24,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIMainStoryboardFile - Main XSAppIconAssets Resources/Images.xcassets/AppIcons.appiconset diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame/ContentItemTemplate.cs b/IDE/MonoDevelop/MonoDevelop.MonoGame/ContentItemTemplate.cs index 1ab59e10dac..27564325e14 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame/ContentItemTemplate.cs +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame/ContentItemTemplate.cs @@ -31,10 +31,11 @@ public override void Load (XmlElement filenode, FilePath baseDirectory) } } - public override bool AddToProject (SolutionItem policyParent, Project project, string language, string directory, string name) + public override bool AddToProject(SolutionFolderItem policyParent, Project project, string language, string directory, string name) { - ProjectFile file = template.AddFileToProject (policyParent, project, language, directory, name); - if (file != null) { + ProjectFile file = template.AddFileToProject(policyParent, project, language, directory, name); + if (file != null) + { file.BuildAction = BuildAction.Content; file.CopyToOutputDirectory = mode; return true; diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame/MonoDevelop.MonoGame.addin.xml b/IDE/MonoDevelop/MonoDevelop.MonoGame/MonoDevelop.MonoGame.addin.xml index 8865dfee4eb..122f3c62e53 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame/MonoDevelop.MonoGame.addin.xml +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame/MonoDevelop.MonoGame.addin.xml @@ -23,9 +23,10 @@ + + - @@ -60,15 +61,24 @@ + + + + + + + + + + + - - @@ -77,8 +87,8 @@ - - + + @@ -87,14 +97,15 @@ - + + + --> @@ -109,9 +120,11 @@ + @@ -151,9 +164,11 @@ + @@ -163,7 +178,6 @@ - @@ -202,4 +216,4 @@ - \ No newline at end of file + diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame/MonoDevelop.MonoGame.csproj b/IDE/MonoDevelop/MonoDevelop.MonoGame/MonoDevelop.MonoGame.csproj index cf6630daa7e..dc254792e2c 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame/MonoDevelop.MonoGame.csproj +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame/MonoDevelop.MonoGame.csproj @@ -23,9 +23,9 @@ - - - + + + @@ -71,10 +71,8 @@ - - @@ -91,6 +89,9 @@ + + + @@ -132,6 +133,9 @@ PreserveNewest + + PreserveNewest + diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/Game1.cs b/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/Game1.cs index 5841d2e443b..67e26cbe970 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/Game1.cs +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/Game1.cs @@ -2,7 +2,6 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Storage; using Microsoft.Xna.Framework.Input; namespace ${Namespace} diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/app.manifest b/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/app.manifest new file mode 100644 index 00000000000..724f8c407db --- /dev/null +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/app.manifest @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + + + + diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGamePipelineProject.xpt.xml b/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGamePipelineProject.xpt.xml index e58c278b12c..6e329adc742 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGamePipelineProject.xpt.xml +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGamePipelineProject.xpt.xml @@ -13,7 +13,7 @@ - + diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGameProject.xpt.xml b/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGameProject.xpt.xml index 730231e1488..e30915ea618 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGameProject.xpt.xml +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGameProject.xpt.xml @@ -24,12 +24,11 @@ - - + @@ -38,8 +37,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGameWindowsProject.xpt.xml b/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGameWindowsProject.xpt.xml index 8eecb2bf760..d046d4f7c3e 100644 --- a/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGameWindowsProject.xpt.xml +++ b/IDE/MonoDevelop/MonoDevelop.MonoGame/templates/MonoGameWindowsProject.xpt.xml @@ -28,6 +28,7 @@ + diff --git a/IDE/MonoDevelop/default.build b/IDE/MonoDevelop/default.build index 1facd50a2f1..75af7fb1681 100644 --- a/IDE/MonoDevelop/default.build +++ b/IDE/MonoDevelop/default.build @@ -42,12 +42,10 @@ - - diff --git a/Installers/Linux/DEBIAN/control b/Installers/Linux/DEBIAN/control deleted file mode 100644 index ad304f094d8..00000000000 --- a/Installers/Linux/DEBIAN/control +++ /dev/null @@ -1,6 +0,0 @@ -Package: monogame-sdk -Section: Development -Architecture: amd64 -Depends: libgtk-3-0 (>= 3.0.0), ttf-mscorefonts-installer (>= 1.0), monodevelop (>= 5.0), gtk-sharp3 (>= 2.99), referenceassemblies-pcl (>= 2014.04) -Maintainer: Harry -Description: MonoGame SDK diff --git a/Installers/Linux/DEBIAN/postinst b/Installers/Linux/DEBIAN/postinst deleted file mode 100755 index 1b89f49eb49..00000000000 --- a/Installers/Linux/DEBIAN/postinst +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -xdg-mime install /tmp/mgcb.xml --novendor -xdg-mime default "Monogame Pipeline.desktop" text/mgcb -sudo -H -u $SUDO_USER bash -c 'mdtool setup install -y /tmp/MonoDevelop.MonoGame.mpack' - -rm -rf /opt/MonoGameSDK -ln -s /usr/lib/mono/xbuild/MonoGame/v3.0/Tools /opt/MonoGameSDK - -rm -f /usr/share/applications/Monogame\ Pipeline.desktop -echo "[Desktop Entry]\nVersion=1.0\nEncoding=UTF-8\nName=MonoGame Pipeline\nGenericName=MonoGame Pipeline\nComment=Used to create platform specific .xnb files\nExec=monogame-pipeline %F\nTryExec=monogame-pipeline\nIcon=monogame\nStartupNotify=true\nTerminal=false\nType=Application\nMimeType=text/mgcb;\nCategories=Development;" >> /usr/share/applications/Monogame\ Pipeline.desktop - diff --git a/Installers/Linux/Main/mgcb b/Installers/Linux/Main/mgcb deleted file mode 100755 index 7a9d85e4d42..00000000000 --- a/Installers/Linux/Main/mgcb +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -mono /usr/lib/mono/xbuild/MonoGame/v3.0/Tools/MGCB.exe "$@" diff --git a/Installers/Linux/Main/monogame-pipeline b/Installers/Linux/Main/monogame-pipeline deleted file mode 100755 index a9ccd43314d..00000000000 --- a/Installers/Linux/Main/monogame-pipeline +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -mono /usr/lib/mono/xbuild/MonoGame/v3.0/Tools/Pipeline.exe "$@" diff --git a/Installers/Linux/RUN/postinstall.sh b/Installers/Linux/RUN/postinstall.sh index f879c510275..939b37431d1 100755 --- a/Installers/Linux/RUN/postinstall.sh +++ b/Installers/Linux/RUN/postinstall.sh @@ -1,4 +1,24 @@ -#!/bin/sh +#!/bin/bash + +# Functions +echodep() +{ + line=" - $1" + + while [ ${#line} -lt 50 ] + do + line="$line." + done + + echo -ne "$line" + + if eval "$2" + then + echo -e "\e[32m[Found]\e[0m" + else + echo -e "\e[31m[Not Found]\e[0m" + fi +} # Check installation priviledge if [ "$(id -u)" != "0" ]; then @@ -6,36 +26,62 @@ if [ "$(id -u)" != "0" ]; then exit 1 fi -# Check previous versions -if type "mgcb" > /dev/null 2>&1 +DIR=$(pwd) +IDIR="/usr/lib/mono/xbuild/MonoGame/v3.0" + +# Find MonoDevelop +MDTOOL="?????" + +if type "monodevelop" > /dev/null 2>&1 then - echo "Please uninstall any previous versions of MonoGame SDK" 1>&2 - exit 1 + if eval "monodevelop --help | grep 'MonoDevelop 6' > /dev/null 2>&1" + then + MDTOOL="mdtool" + fi fi -DIR=$(pwd) -IDIR="/usr/lib/mono/xbuild/MonoGame/v3.0" +if type "monodevelop-stable" > /dev/null 2>&1 +then + if eval "monodevelop-stable --help | grep 'MonoDevelop 6' > /dev/null 2>&1" + then + MDTOOL="mdtool-stable" + fi +fi # Show dependency list -echo "Please make sure the following packages are installed:" -echo " - monodevelop" -echo " - libopenal-dev" -echo " - referenceassemblies-pcl / mono-pcl" -echo " - ttf-mscorefonts-installer / mscore-fonts" -echo " - gtk-sharp3" +echo "Dependencies:" +echodep "mono-runtime" "type 'mono' > /dev/null 2>&1" +echodep "gtk-sharp3" "type 'gacutil' > /dev/null 2>&1 && gacutil /l gtk-sharp | grep -q 3.0.0.0" +echo "" +echo "Optional Dependencies:" +echodep "MonoDevelop 6" "$MDTOOL > /dev/null 2>&1" +echodep "Rider" "type 'rider' > /dev/null 2>&1" +echodep "referenceassemblies-pcl / mono-pcl" "test -d /usr/lib/mono/xbuild/Microsoft/Portable" +echodep "ttf-mscorefonts-installer / mscore-fonts" "fc-list | grep -q Arial" +echo "" read -p "Continue (Y, n): " choice2 case "$choice2" in n|N ) exit ;; *) ;; esac -# MonoDevelop addin -read -p "Install monodevelop addin(Y, n): " choice2 -case "$choice2" in - n|N ) ;; - *) - sudo -H -u $SUDO_USER bash -c "mdtool setup install -y $DIR/Main/MonoDevelop.MonoGame.mpack" -esac +# Check previous versions +if type "mgcb" > /dev/null 2>&1 +then + echo "Previous version detected, trying to uninstall..." + + # Try and uninstall previus versions + if [ -f /opt/monogame/uninstall.sh ] + then + sudo sh /opt/monogame/uninstall.sh + elif [ -f /opt/MonoGameSDK/uninstall.sh ] + then + sudo sh /opt/MonoGameSDK/uninstall.sh + else + echo "Could not uninstall, please uninstall any previous version of MonoGame SDK manually." 1>&2 + exit 1 + fi +fi # MonoGame SDK installation echo "Installing MonoGame SDK..." @@ -50,13 +96,44 @@ ln -s "$IDIR" "/opt/MonoGameSDK" chmod +x "$IDIR/Tools/ffmpeg" chmod +x "$IDIR/Tools/ffprobe" +# Rider stuff +if type "rider" > /dev/null 2>&1 +then + echo "Installing Rider files..." + + FINDCOMMAND=$(type -a rider) + COMMAND=$(echo $FINDCOMMAND| cut -d' ' -f 3) + + FINDRIDER=$(cat $COMMAND | grep "RUN_PATH") + RIDER=$(echo $FINDRIDER| cut -d"'" -f 2) + + RIDERDIR=$(dirname $(dirname $RIDER)) + RXBUILD="$RIDERDIR/lib/ReSharperHost/linux-x64/mono/lib/mono/xbuild/MonoGame" + + mkdir -p "$RXBUILD" + ln -s "$IDIR" "$RXBUILD/v3.0" +fi + +# MonoDevelop addin +if [ "$MONODEVELOP" != "?????" ] +then + echo "Installing MonoDevelop Addin..." + sudo -H -u $SUDO_USER bash -c "$MDTOOL setup install -y $DIR/Main/MonoDevelop.MonoGame.mpack > /dev/null" +fi + # Monogame Pipeline terminal commands echo "Creating launcher items..." -cp $DIR/Main/monogame-pipeline /usr/bin/monogame-pipeline -chmod +x /usr/bin/monogame-pipeline +cat > /usr/bin/monogame-pipeline-tool <<'endmsg' +#!/bin/bash +mono /usr/lib/mono/xbuild/MonoGame/v3.0/Tools/Pipeline.exe "$@" +endmsg +chmod +x /usr/bin/monogame-pipeline-tool -cp $DIR/Main/mgcb /usr/bin/mgcb +cat > /usr/bin/mgcb <<'endmsg' +#!/bin/bash +mono /usr/lib/mono/xbuild/MonoGame/v3.0/Tools/MGCB.exe "$@" +endmsg chmod +x /usr/bin/mgcb # MonoGame icon @@ -65,15 +142,30 @@ cp $DIR/Main/monogame.svg /usr/share/icons/hicolor/scalable/mimetypes/monogame.s gtk-update-icon-cache /usr/share/icons/hicolor/ -f # Application launcher -rm -f /usr/share/applications/Monogame\ Pipeline.desktop -echo -e "[Desktop Entry]\nVersion=1.0\nEncoding=UTF-8\nName=MonoGame Pipeline\nGenericName=MonoGame Pipeline\nComment=Used to create platform specific .xnb files\nExec=monogame-pipeline %F\nTryExec=monogame-pipeline\nIcon=monogame\nStartupNotify=true\nTerminal=false\nType=Application\nMimeType=text/mgcb;\nCategories=Development;" | sudo tee --append /usr/share/applications/Monogame\ Pipeline.desktop > /dev/null +cat > /usr/share/applications/MonogamePipeline.desktop <<'endmsg' +[Desktop Entry] +Version=1.0 +Encoding=UTF-8 +Name=MonoGame Pipeline Tool +GenericName=MonoGame Pipeline Tool +Comment=Creates platform specific content files. +Exec=monogame-pipeline-tool %F +TryExec=monogame-pipeline-tool +Icon=monogame +StartupNotify=true +Terminal=false +Type=Application +MimeType=text/mgcb; +Categories=Development; +endmsg # Mimetype echo "Adding mimetype..." -xdg-mime install $DIR/Main/mgcb.xml --novendor -xdg-mime default "Monogame Pipeline.desktop" text/mgcb +xdg-mime install $DIR/Main/mgcb.xml --novendor > /dev/null +xdg-mime default "MonogamePipeline.desktop" text/mgcb # Uninstall script chmod +x $IDIR/uninstall.sh -echo "To uninstall the pipeline please run $IDIR/uninstall.sh" +ln -s $IDIR/uninstall.sh /usr/bin/monogame-uninstall +echo "To uninstall MonoGame SDK you can run \"monogame-uninstall\" from terminal." diff --git a/Installers/Linux/RUN/uninstall.sh b/Installers/Linux/RUN/uninstall.sh index 9654ad15a3d..53fc13ee323 100755 --- a/Installers/Linux/RUN/uninstall.sh +++ b/Installers/Linux/RUN/uninstall.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash #check removale priviledge if [ "$(id -u)" != "0" ]; then @@ -7,7 +7,8 @@ if [ "$(id -u)" != "0" ]; then fi #remove terminal commands for mgcb and pipeline tool -rm -f /usr/bin/monogame-pipeline +rm -f /usr/bin/monogame-pipeline-tool +rm -f /usr/bin/monogame-uninstall rm -f /usr/bin/mgcb #remove application icon diff --git a/Installers/Linux/compile.sh b/Installers/Linux/compile.sh deleted file mode 100755 index 3bd3bd45d22..00000000000 --- a/Installers/Linux/compile.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -dpkg --build tmp_deb monogame-sdk.deb -./../../ThirdParty/Dependencies/makeself/makeself.sh tmp_run/ monogame-sdk.run "Monogame Pipeline Installer" ./postinstall.sh diff --git a/Installers/MacOS/Scripts/Pipeline/postinstall b/Installers/MacOS/Scripts/Pipeline/postinstall index 27cfeeaa056..c89b179a820 100755 --- a/Installers/MacOS/Scripts/Pipeline/postinstall +++ b/Installers/MacOS/Scripts/Pipeline/postinstall @@ -37,6 +37,9 @@ if [ -d '/Applications/Xamarin Studio.app' ] then /Applications/Xamarin\ Studio.app/Contents/MacOS/mdtool setup uninstall MonoDevelop.MonoGame -y fi +if [ ! -d '/Library/Frameworks/Mono.framework/Versions/4.6.2' ] + ln -s /Library/Frameworks/Mono.framework/Versions/Current /Library/Frameworks/Mono.framework/Versions/4.6.2 +fi sudo rm /usr/local/bin/mgcb sudo rm /usr/local/bin/monogame-uninstall " >> /usr/local/bin/monogame-uninstall diff --git a/Installers/Windows/MonoGame.nsi b/Installers/Windows/MonoGame.nsi index 649ba74cec7..aa9b4cbe378 100644 --- a/Installers/Windows/MonoGame.nsi +++ b/Installers/Windows/MonoGame.nsi @@ -75,7 +75,7 @@ RequestExecutionLevel admin ; The stuff to install Section "MonoGame Core Components" CoreComponents ;No components page, name is not important SectionIn RO - + ; Install the VS support files. SetOutPath ${MSBuildInstallDir} File '..\..\MonoGame.Framework.Content.Pipeline\MonoGame.Content.Builder.targets' @@ -96,6 +96,7 @@ Section "MonoGame Core Components" CoreComponents ;No components page, name is n !insertmacro VS_ASSOCIATE_EDITOR 'MonoGame Pipeline' '11.0' 'mgcb' '${MSBuildInstallDir}\Tools\Pipeline.exe' !insertmacro VS_ASSOCIATE_EDITOR 'MonoGame Pipeline' '12.0' 'mgcb' '${MSBuildInstallDir}\Tools\Pipeline.exe' !insertmacro VS_ASSOCIATE_EDITOR 'MonoGame Pipeline' '14.0' 'mgcb' '${MSBuildInstallDir}\Tools\Pipeline.exe' + !insertmacro VS_ASSOCIATE_EDITOR 'MonoGame Pipeline' '15.0' 'mgcb' '${MSBuildInstallDir}\Tools\Pipeline.exe' !insertmacro APP_ASSOCIATE 'mgcb' 'MonoGame.ContentBuilderFile' 'A MonoGame content builder project.' '${MSBuildInstallDir}\Tools\Pipeline.exe,0' 'Open with Pipeline' '${MSBuildInstallDir}\Tools\Pipeline.exe "%1"' ; Install the assemblies for all the platforms we can @@ -106,17 +107,27 @@ Section "MonoGame Core Components" CoreComponents ;No components page, name is n File '..\..\MonoGame.Framework\bin\Android\AnyCPU\Release\*.dll' File '..\..\MonoGame.Framework\bin\Android\AnyCPU\Release\*.xml' - ; Install OUYA Assemblies - SetOutPath '$INSTDIR\Assemblies\OUYA' - File '..\..\MonoGame.Framework\bin\Ouya\AnyCPU\Release\*.dll' - File '..\..\MonoGame.Framework\bin\Ouya\AnyCPU\Release\*.xml' - - ; Install Desktop OpenGL Assemblies + ; Install DesktopGL Assemblies SetOutPath '$INSTDIR\Assemblies\DesktopGL' File /nonfatal '..\..\MonoGame.Framework\bin\WindowsGL\AnyCPU\Release\*.dll' File /nonfatal ' ..\..\MonoGame.Framework\bin\WindowsGL\AnyCPU\Release\*.xml' - File '..\..\ThirdParty\Dependencies\OpenTK.dll' - File '..\..\ThirdParty\Dependencies\OpenTK.dll.config' + File '..\..\ThirdParty\Dependencies\SDL\MacOS\Universal\libSDL2-2.0.0.dylib' + File '..\..\ThirdParty\Dependencies\openal-soft\MacOS\Universal\libopenal.1.dylib' + File '..\..\ThirdParty\Dependencies\MonoGame.Framework.dll.config' + + ; Install x86 DesktopGL Dependencies + SetOutPath '$INSTDIR\Assemblies\DesktopGL\x86' + File '..\..\ThirdParty\Dependencies\SDL\Windows\x86\SDL2.dll' + File '..\..\ThirdParty\Dependencies\openal-soft\Windows\x86\soft_oal.dll' + File '..\..\ThirdParty\Dependencies\SDL\Linux\x86\libSDL2-2.0.so.0' + File '..\..\ThirdParty\Dependencies\openal-soft\Linux\x86\libopenal.so.1' + + ; Install x64 DesktopGL Dependencies + SetOutPath '$INSTDIR\Assemblies\DesktopGL\x64' + File '..\..\ThirdParty\Dependencies\SDL\Windows\x64\SDL2.dll' + File '..\..\ThirdParty\Dependencies\openal-soft\Windows\x64\soft_oal.dll' + File '..\..\ThirdParty\Dependencies\SDL\Linux\x64\libSDL2-2.0.so.0' + File '..\..\ThirdParty\Dependencies\openal-soft\Linux\x64\libopenal.so.1' ; Install Windows Desktop DirectX Assemblies SetOutPath '$INSTDIR\Assemblies\Windows' @@ -133,18 +144,6 @@ Section "MonoGame Core Components" CoreComponents ;No components page, name is n File '..\..\MonoGame.Framework\bin\WindowsPhone81\AnyCPU\Release\*.dll' File '..\..\MonoGame.Framework\bin\WindowsPhone81\AnyCPU\Release\*.xml' - ; Install Windows Phone ARM Assemblies - SetOutPath '$INSTDIR\Assemblies\WindowsPhone\ARM' - File '..\..\MonoGame.Framework\bin\WindowsPhone\ARM\Release\*.dll' - File '..\..\MonoGame.Framework\bin\WindowsPhone\ARM\Release\*.xml' - File '..\..\MonoGame.Framework\bin\WindowsPhone\ARM\Release\*.winmd' - - ; Install Windows Phone x86 Assemblies - SetOutPath '$INSTDIR\Assemblies\WindowsPhone\x86' - File '..\..\MonoGame.Framework\bin\WindowsPhone\x86\Release\*.dll' - File '..\..\MonoGame.Framework\bin\WindowsPhone\x86\Release\*.xml' - File '..\..\MonoGame.Framework\bin\WindowsPhone\x86\Release\*.winmd' - ; Install Windows 10 UAP Assemblies SetOutPath '$INSTDIR\Assemblies\WindowsUniversal' File '..\..\MonoGame.Framework\bin\WindowsUniversal\AnyCPU\Release\*.dll' @@ -156,17 +155,17 @@ Section "MonoGame Core Components" CoreComponents ;No components page, name is n SetOutPath '$INSTDIR\Assemblies\iOS' File '..\..\MonoGame.Framework\bin\iOS\iPhoneSimulator\Release\*.dll' File '..\..\MonoGame.Framework\bin\iOS\iPhoneSimulator\Release\*.xml' + SetOutPath '$INSTDIR\Assemblies\tvOS' + File '..\..\MonoGame.Framework\bin\tvOS\iPhoneSimulator\Release\*.dll' + File '..\..\MonoGame.Framework\bin\tvOS\iPhoneSimulator\Release\*.xml' SkipiOSAssemblies: WriteRegStr HKLM 'SOFTWARE\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Desktop OpenGL' '' '$INSTDIR\Assemblies\DesktopGL' WriteRegStr HKLM 'SOFTWARE\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Windows' '' '$INSTDIR\Assemblies\Windows' WriteRegStr HKLM 'SOFTWARE\Microsoft\.NETFramework\v4.5.50709\AssemblyFoldersEx\${APPNAME} for Windows Store' '' '$INSTDIR\Assemblies\Windows8' WriteRegStr HKLM 'SOFTWARE\Microsoft\.NETFramework\v4.5.50709\AssemblyFoldersEx\${APPNAME} for Windows Phone 8.1' '' '$INSTDIR\Assemblies\WindowsPhone81' - WriteRegStr HKLM 'SOFTWARE\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Windows Phone ARM' '' '$INSTDIR\Assemblies\WindowsPhone\ARM' - WriteRegStr HKLM 'SOFTWARE\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Windows Phone x86' '' '$INSTDIR\Assemblies\WindowsPhone\x86' WriteRegStr HKLM 'SOFTWARE\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Windows 10 Universal' '' '$INSTDIR\Assemblies\WindowsUniversal' WriteRegStr HKLM 'SOFTWARE\Microsoft\MonoAndroid\v2.3\AssemblyFoldersEx\${APPNAME} for Android' '' '$INSTDIR\Assemblies\Android' - WriteRegStr HKLM 'SOFTWARE\Microsoft\MonoAndroid\v2.3\AssemblyFoldersEx\${APPNAME} for OUYA' '' '$INSTDIR\Assemblies\OUYA' WriteRegStr HKLM 'SOFTWARE\Microsoft\MonoTouch\v1.0\AssemblyFoldersEx\${APPNAME} for iOS' '' '$INSTDIR\Assemblies\iOS' IfFileExists $WINDIR\SYSWOW64\*.* Is64bit Is32bit @@ -178,10 +177,7 @@ Section "MonoGame Core Components" CoreComponents ;No components page, name is n WriteRegStr HKLM 'SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.5.50709\AssemblyFoldersEx\${APPNAME} for Windows Store' '' '$INSTDIR\Assemblies\Windows8' WriteRegStr HKLM 'SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.5.50709\AssemblyFoldersEx\${APPNAME} for Windows Phone 8.1' '' '$INSTDIR\Assemblies\WindowsPhone81' WriteRegStr HKLM 'SOFTWARE\Wow6432Node\Microsoft\MonoAndroid\v2.3\AssemblyFoldersEx\${APPNAME} for Android' '' '$INSTDIR\Assemblies\Android' - WriteRegStr HKLM 'SOFTWARE\Wow6432Node\Microsoft\MonoAndroid\v2.3\AssemblyFoldersEx\${APPNAME} for OUYA' '' '$INSTDIR\Assemblies\OUYA' WriteRegStr HKLM 'SOFTWARE\Wow6432Node\Microsoft\MonoTouch\v1.0\AssemblyFoldersEx\${APPNAME} for iOS' '' '$INSTDIR\Assemblies\iOS' - WriteRegStr HKLM 'SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Windows Phone ARM' '' '$INSTDIR\Assemblies\WindowsPhone\ARM' - WriteRegStr HKLM 'SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Windows Phone x86' '' '$INSTDIR\Assemblies\WindowsPhone\x86' WriteRegStr HKLM 'SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Windows 10 Universal' '' '$INSTDIR\Assemblies\WindowsUniversal' End32Bitvs64BitCheck: @@ -203,19 +199,11 @@ Section "MonoGame Core Components" CoreComponents ;No components page, name is n SectionEnd -Section "OpenAL" OpenAL - ; SetOutPath $INSTDIR - File '..\..\ThirdParty\Dependencies\oalinst.exe' - ExecWait '"$INSTDIR\oalinst.exe /S"' -SectionEnd - Section "Visual Studio 2010 Templates" VS2010 - ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\10.0" "UserProjectTemplatesLocation" - ExpandEnvStrings $1 $1 - IfFileExists "$1\Visual C#\*.*" InstallTemplates CannotInstallTemplates + IfFileExists `$DOCUMENTS\Visual Studio 2010\Templates\ProjectTemplates\Visual C#\*.*` InstallTemplates CannotInstallTemplates InstallTemplates: - SetOutPath "$1\Visual C#\MonoGame" + SetOutPath "$DOCUMENTS\Visual Studio 2010\Templates\ProjectTemplates\Visual C#\MonoGame" File /r '..\..\ProjectTemplates\VisualStudio2010\*.zip' GOTO EndTemplates CannotInstallTemplates: @@ -226,11 +214,9 @@ SectionEnd Section "Visual Studio 2012 Templates" VS2012 - ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\11.0" "UserProjectTemplatesLocation" - ExpandEnvStrings $1 $1 - IfFileExists "$1\Visual C#\*.*" InstallTemplates CannotInstallTemplates + IfFileExists `$DOCUMENTS\Visual Studio 2012\Templates\ProjectTemplates\Visual C#\*.*` InstallTemplates CannotInstallTemplates InstallTemplates: - SetOutPath "$1\Visual C#\MonoGame" + SetOutPath "$DOCUMENTS\Visual Studio 2012\Templates\ProjectTemplates\Visual C#\MonoGame" File /r '..\..\ProjectTemplates\VisualStudio2012\*.zip' File /r '..\..\ProjectTemplates\VisualStudio2010\*.zip' GOTO EndTemplates @@ -242,11 +228,9 @@ SectionEnd Section "Visual Studio 2013 Templates" VS2013 - ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\12.0" "UserProjectTemplatesLocation" - ExpandEnvStrings $1 $1 - IfFileExists "$1\Visual C#\*.*" InstallTemplates CannotInstallTemplates + IfFileExists `$DOCUMENTS\Visual Studio 2013\Templates\ProjectTemplates\Visual C#\*.*` InstallTemplates CannotInstallTemplates InstallTemplates: - SetOutPath "$1\Visual C#\MonoGame" + SetOutPath "$DOCUMENTS\Visual Studio 2013\Templates\ProjectTemplates\Visual C#\MonoGame" File /r '..\..\ProjectTemplates\VisualStudio2013\*.zip' File /r '..\..\ProjectTemplates\VisualStudio2010\*.zip' GOTO EndTemplates @@ -258,12 +242,11 @@ SectionEnd Section "Visual Studio 2015 Templates" VS2015 - ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\14.0" "UserProjectTemplatesLocation" - ExpandEnvStrings $1 $1 - IfFileExists "$1\Visual C#\*.*" InstallTemplates CannotInstallTemplates + IfFileExists `$DOCUMENTS\Visual Studio 2015\Templates\ProjectTemplates\Visual C#\*.*` InstallTemplates CannotInstallTemplates InstallTemplates: - SetOutPath "$1\Visual C#\MonoGame" + SetOutPath "$DOCUMENTS\Visual Studio 2015\Templates\ProjectTemplates\Visual C#\MonoGame" File /r '..\..\ProjectTemplates\VisualStudio2010\*.zip' + File /r '..\..\ProjectTemplates\VisualStudio2013\WindowsPhone8.1.zip' File /r '..\..\ProjectTemplates\VisualStudio2015\*.zip' GOTO EndTemplates CannotInstallTemplates: @@ -272,6 +255,21 @@ Section "Visual Studio 2015 Templates" VS2015 SectionEnd +Section "Visual Studio 2017 Templates" VS2017 + + IfFileExists `$DOCUMENTS\Visual Studio 2017\Templates\ProjectTemplates\Visual C#\*.*` InstallTemplates CannotInstallTemplates + InstallTemplates: + SetOutPath "$DOCUMENTS\Visual Studio 2017\Templates\ProjectTemplates\Visual C#\MonoGame" + File /r '..\..\ProjectTemplates\VisualStudio2010\*.zip' + File /r '..\..\ProjectTemplates\VisualStudio2013\WindowsPhone8.1.zip' + File /r '..\..\ProjectTemplates\VisualStudio2015\*.zip' + GOTO EndTemplates + CannotInstallTemplates: + DetailPrint "Visual Studio 2017 not found" + EndTemplates: + +SectionEnd + ; Optional section (can be disabled by the user) Section "Start Menu Shortcuts" Menu CreateDirectory $SMPROGRAMS\${APPNAME} @@ -295,67 +293,67 @@ Section "Start Menu Shortcuts" Menu SectionEnd LangString CoreComponentsDesc ${LANG_ENGLISH} "Install the Runtimes and the MSBuild extensions for MonoGame" -LangString OpenALDesc ${LANG_ENGLISH} "Install the OpenAL drivers" LangString MonoDevelopDesc ${LANG_ENGLISH} "Install the project templates for MonoDevelop" LangString VS2010Desc ${LANG_ENGLISH} "Install the project templates for Visual Studio 2010" LangString VS2012Desc ${LANG_ENGLISH} "Install the project templates for Visual Studio 2012" LangString VS2013Desc ${LANG_ENGLISH} "Install the project templates for Visual Studio 2013" LangString VS2015Desc ${LANG_ENGLISH} "Install the project templates for Visual Studio 2015" +LangString VS2017Desc ${LANG_ENGLISH} "Install the project templates for Visual Studio 2017" LangString MenuDesc ${LANG_ENGLISH} "Add a link to the MonoGame website to your start menu" !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${CoreComponents} $(CoreComponentsDesc) - !insertmacro MUI_DESCRIPTION_TEXT ${OpenAL} $(OpenALDesc) !insertmacro MUI_DESCRIPTION_TEXT ${MonoDevelop} $(MonoDevelopDesc) !insertmacro MUI_DESCRIPTION_TEXT ${VS2010} $(VS2010Desc) !insertmacro MUI_DESCRIPTION_TEXT ${VS2012} $(VS2012Desc) !insertmacro MUI_DESCRIPTION_TEXT ${VS2013} $(VS2013Desc) !insertmacro MUI_DESCRIPTION_TEXT ${VS2015} $(VS2015Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${VS2017} $(VS2017Desc) !insertmacro MUI_DESCRIPTION_TEXT ${Menu} $(MenuDesc) !insertmacro MUI_FUNCTION_DESCRIPTION_END Function checkVS2010 -ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\10.0" "UserProjectTemplatesLocation" -ExpandEnvStrings $1 $1 -IfFileExists "$1\Visual C#\*.*" end disable +IfFileExists `$DOCUMENTS\Visual Studio 2010\Templates\ProjectTemplates\Visual C#\*.*` end disable disable: SectionSetFlags ${VS2010} $0 end: FunctionEnd Function checkVS2012 -ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\11.0" "UserProjectTemplatesLocation" -ExpandEnvStrings $1 $1 -IfFileExists "$1\Visual C#\*.*" end disable +IfFileExists `$DOCUMENTS\Visual Studio 2012\Templates\ProjectTemplates\Visual C#\*.*` end disable disable: SectionSetFlags ${VS2012} $0 end: FunctionEnd Function checkVS2013 -ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\12.0" "UserProjectTemplatesLocation" -ExpandEnvStrings $1 $1 -IfFileExists "$1\Visual C#\*.*" end disable +IfFileExists `$DOCUMENTS\Visual Studio 2013\Templates\ProjectTemplates\Visual C#\*.*` end disable disable: SectionSetFlags ${VS2013} $0 end: FunctionEnd Function checkVS2015 -ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\14.0" "UserProjectTemplatesLocation" -ExpandEnvStrings $1 $1 -IfFileExists "$1\Visual C#\*.*" end disable +IfFileExists `$DOCUMENTS\Visual Studio 2015\Templates\ProjectTemplates\Visual C#\*.*` end disable disable: SectionSetFlags ${VS2015} $0 end: FunctionEnd +Function checkVS2017 +IfFileExists `$DOCUMENTS\Visual Studio 2017\Templates\ProjectTemplates\Visual C#\*.*` end disable + disable: + SectionSetFlags ${VS2017} $0 + end: +FunctionEnd + Function .onInit IntOp $0 $0 | ${SF_RO} Call checkVS2010 Call checkVS2012 Call checkVS2013 Call checkVS2015 + Call checkVS2017 IntOp $0 ${SF_SELECTED} | ${SF_RO} SectionSetFlags ${core_id} $0 FunctionEnd @@ -375,7 +373,6 @@ Section "Uninstall" DeleteRegKey HKLM 'SOFTWARE\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Windows Phone x86' DeleteRegKey HKLM 'SOFTWARE\Microsoft\.NETFramework\v4.5.50709\AssemblyFoldersEx\${APPNAME} for Windows 10 UAP' DeleteRegKey HKLM 'SOFTWARE\Microsoft\MonoAndroid\v2.3\AssemblyFoldersEx\${APPNAME} for Android' - DeleteRegKey HKLM 'SOFTWARE\Microsoft\MonoAndroid\v2.3\AssemblyFoldersEx\${APPNAME} for OUYA' DeleteRegKey HKLM 'SOFTWARE\Microsoft\MonoTouch\v1.0\AssemblyFoldersEx\${APPNAME} for iOS' DeleteRegKey HKCU 'Software\Microsoft\VisualStudio\10.0\Default Editors\mgcb' @@ -397,7 +394,6 @@ Section "Uninstall" DeleteRegKey HKLM 'SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Windows Phone x86' DeleteRegKey HKLM 'SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319\AssemblyFoldersEx\${APPNAME} for Windows 10 UAP' DeleteRegKey HKLM 'SOFTWARE\Wow6432Node\Microsoft\MonoAndroid\v2.3\AssemblyFoldersEx\${APPNAME} for Android' - DeleteRegKey HKLM 'SOFTWARE\Wow6432Node\Microsoft\MonoAndroid\v2.3\AssemblyFoldersEx\${APPNAME} for OUYA' DeleteRegKey HKLM 'SOFTWARE\Wow6432Node\Microsoft\MonoTouch\v1.0\AssemblyFoldersEx\${APPNAME} for iOS' @@ -413,18 +409,11 @@ Section "Uninstall" RMDir /r "$0\AddIns\MonoDevelop.MonoGame" ${EndIf} - ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\10.0" "UserProjectTemplatesLocation" - ExpandEnvStrings $1 $1 - RMDir /r "$1\Visual C#\MonoGame" - ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\11.0" "UserProjectTemplatesLocation" - ExpandEnvStrings $1 $1 - RMDir /r "$1\Visual C#\MonoGame" - ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\12.0" "UserProjectTemplatesLocation" - ExpandEnvStrings $1 $1 - RMDir /r "$1\Visual C#\MonoGame" - ReadRegStr $1 HKCU "SOFTWARE\Microsoft\VisualStudio\14.0" "UserProjectTemplatesLocation" - ExpandEnvStrings $1 $1 - RMDir /r "$1\Visual C#\MonoGame" + RMDir /r "$DOCUMENTS\Visual Studio 2010\Templates\ProjectTemplates\Visual C#\MonoGame" + RMDir /r "$DOCUMENTS\Visual Studio 2012\Templates\ProjectTemplates\Visual C#\MonoGame" + RMDir /r "$DOCUMENTS\Visual Studio 2013\Templates\ProjectTemplates\Visual C#\MonoGame" + RMDir /r "$DOCUMENTS\Visual Studio 2015\Templates\ProjectTemplates\Visual C#\MonoGame" + RMDir /r "$DOCUMENTS\Visual Studio 2017\Templates\ProjectTemplates\Visual C#\MonoGame" RMDir /r "${MSBuildInstallDir}" RMDir /r "$SMPROGRAMS\${APPNAME}" diff --git a/Installers/default.build b/Installers/default.build index ad6ccf2f339..e33fd87de36 100644 --- a/Installers/default.build +++ b/Installers/default.build @@ -54,16 +54,29 @@ - - + + + + + + + + + + + + + + + + - @@ -84,13 +97,13 @@ - - + + - - + + @@ -144,31 +157,6 @@ fi - - - - - - - - - - - - - - - - - - version: ${buildNumber} - - - - - - - @@ -189,9 +177,12 @@ fi - - - + + + + + + diff --git a/MonoGame.Framework.Content.Pipeline/Audio/AudioContent.cs b/MonoGame.Framework.Content.Pipeline/Audio/AudioContent.cs index 2c78e4df1fa..3fb343ea400 100644 --- a/MonoGame.Framework.Content.Pipeline/Audio/AudioContent.cs +++ b/MonoGame.Framework.Content.Pipeline/Audio/AudioContent.cs @@ -3,385 +3,197 @@ // file 'LICENSE.txt', which is part of this source code package. using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; -using System.Linq; namespace Microsoft.Xna.Framework.Content.Pipeline.Audio { ///

- /// Encapsulates and provides operations, such as format conversions, on the source audio. This type is produced by the audio importers and used by audio processors to produce compiled audio assets. + /// Encapsulates and provides operations, such as format conversions, on the + /// source audio. This type is produced by the audio importers and used by audio + /// processors to produce compiled audio assets. /// - public class AudioContent : ContentItem + /// Note that AudioContent can load and process audio files that are not supported by the importers. + public class AudioContent : ContentItem, IDisposable { - internal List data; - TimeSpan duration; - string fileName; - AudioFileType fileType; - AudioFormat format; - int loopLength; - int loopStart; + private bool _disposed; + private readonly string _fileName; + private readonly AudioFileType _fileType; + private ReadOnlyCollection _data; + private TimeSpan _duration; + private AudioFormat _format; + private int _loopStart; + private int _loopLength; /// - /// Gets the raw audio data. + /// The name of the original source audio file. /// - /// If unprocessed, the source data; otherwise, the processed data. - public ReadOnlyCollection Data { get { return data.AsReadOnly(); } } + [ContentSerializer(AllowNull = false)] + public string FileName { get { return _fileName; } } /// - /// Gets the duration (in milliseconds) of the audio data. + /// The type of the original source audio file. /// - /// Duration of the audio data. - public TimeSpan Duration { get { return duration; } } + public AudioFileType FileType { get { return _fileType; } } /// - /// Gets the file name containing the audio data. + /// The current raw audio data without header information. /// - /// The name of the file containing this data. - [ContentSerializerAttribute] - public string FileName { get { return fileName; } } - - /// - /// Gets the AudioFileType of this audio source. - /// - /// The AudioFileType of this audio source. - public AudioFileType FileType { get { return fileType; } } - - /// - /// Gets the AudioFormat of this audio source. - /// - /// The AudioFormat of this audio source. - public AudioFormat Format { get { return format; } } - - /// - /// Gets the loop length, in samples. - /// - /// The number of samples in the loop. - public int LoopLength { get { return loopLength; } } + /// + /// This changes from the source data to the output data after conversion. + /// For MP3 and WMA files this throws an exception to match XNA behavior. + /// + public ReadOnlyCollection Data + { + get + { + if (_disposed || _data == null) + throw new InvalidContentException("Could not read the audio data from file \"" + Path.GetFileName(_fileName) + "\"."); + return _data; + } + } /// - /// Gets the loop start, in samples. + /// The duration of the audio data. /// - /// The number of samples to the start of the loop. - public int LoopStart { get { return loopStart; } } + public TimeSpan Duration + { + get + { + return _duration; + } + } /// - /// Initializes a new instance of AudioContent. + /// The current format of the audio data. /// - /// Name of the audio source file to be processed. - /// Type of the processed audio: WAV, MP3 or WMA. - /// Constructs the object from the specified source file, in the format specified. - public AudioContent(string audioFileName, AudioFileType audioFileType) + /// This changes from the source format to the output format after conversion. + public AudioFormat Format { - fileName = audioFileName; - fileType = audioFileType; - Read(audioFileName); + get + { + return _format; + } } /// - /// Returns the sample rate for the given quality setting. + /// The current loop length in samples. /// - /// The quality setting. - /// The sample rate for the quality. - int QualityToSampleRate(ConversionQuality quality) + /// This changes from the source loop length to the output loop length after conversion. + public int LoopLength { - switch (quality) + get { - case ConversionQuality.Low: - return Math.Max(8000, format.SampleRate / 2); - } - - return Math.Max(8000, format.SampleRate); + return _loopLength; + } } /// - /// Returns the bitrate for the given quality setting. + /// The current loop start location in samples. /// - /// The quality setting. - /// The bitrate for the quality. - int QualityToBitRate(ConversionQuality quality) + /// This changes from the source loop start to the output loop start after conversion. + public int LoopStart { - switch (quality) + get { - case ConversionQuality.Low: - return 96000; - case ConversionQuality.Medium: - return 128000; + return _loopStart; } - - return 192000; } /// - /// Transcodes the source audio to the target format and quality. + /// Initializes a new instance of AudioContent. /// - /// Format to convert this audio to. - /// Quality of the processed output audio. For streaming formats, it can be one of the following: Low (96 kbps), Medium (128 kbps), Best (192 kbps). For WAV formats, it can be one of the following: Low (11kHz ADPCM), Medium (22kHz ADPCM), Best (44kHz PCM) - /// - /// The name of the file that the converted audio should be saved into. This is used for SongContent, where - /// the audio is stored external to the XNB file. If this is null, then the converted audio is stored in - /// the Data property. - /// - public void ConvertFormat(ConversionFormat formatType, ConversionQuality quality, string saveToFile) + /// Name of the audio source file to be processed. + /// Type of the processed audio: WAV, MP3 or WMA. + /// Constructs the object from the specified source file, in the format specified. + public AudioContent(string audioFileName, AudioFileType audioFileType) { - var temporarySource = Path.GetTempFileName(); - var temporaryOutput = Path.GetTempFileName(); + _fileName = audioFileName; + try { - using (var fs = new FileStream(temporarySource, FileMode.Create, FileAccess.Write)) - { - var dataBytes = this.data.ToArray(); - fs.Write(dataBytes, 0, dataBytes.Length); - } + // Get the full path to the file. + audioFileName = Path.GetFullPath(audioFileName); - string ffmpegCodecName, ffmpegMuxerName; - int format; - switch (formatType) - { - case ConversionFormat.Adpcm: - // ADPCM Microsoft - ffmpegCodecName = "adpcm_ms"; - ffmpegMuxerName = "wav"; - format = 0x0002; /* WAVE_FORMAT_ADPCM */ - break; - case ConversionFormat.Pcm: - // PCM signed 16-bit little-endian - ffmpegCodecName = "pcm_s16le"; - ffmpegMuxerName = "wav"; - format = 0x0001; /* WAVE_FORMAT_PCM */ - break; - case ConversionFormat.WindowsMedia: - // Windows Media Audio 2 - ffmpegCodecName = "wmav2"; - ffmpegMuxerName = "asf"; - format = 0x0161; /* WAVE_FORMAT_WMAUDIO2 */ - break; - case ConversionFormat.Xma: - throw new NotSupportedException( - "XMA is not a supported encoding format. It is specific to the Xbox 360."); - case ConversionFormat.ImaAdpcm: - // ADPCM IMA WAV - ffmpegCodecName = "adpcm_ima_wav"; - ffmpegMuxerName = "wav"; - format = 0x0011; /* WAVE_FORMAT_IMA_ADPCM */ - break; - case ConversionFormat.Aac: - // AAC (Advanced Audio Coding) - // Requires -strict experimental - ffmpegCodecName = "aac"; - ffmpegMuxerName = "ipod"; - format = 0x0000; /* WAVE_FORMAT_UNKNOWN */ - break; - case ConversionFormat.Vorbis: - // Vorbis - ffmpegCodecName = "libvorbis"; - ffmpegMuxerName = "ogg"; - format = 0x0000; /* WAVE_FORMAT_UNKNOWN */ - break; - default: - // Unknown format - throw new NotSupportedException(); - } + // Use probe to get the details of the file. + DefaultAudioProfile.ProbeFormat(audioFileName, out _fileType, out _format, out _duration, out _loopStart, out _loopLength); - string ffmpegStdout, ffmpegStderr; - var ffmpegExitCode = ExternalTool.Run( - "ffmpeg", - string.Format( - "-y -i \"{0}\" -vn -c:a {1} -b:a {2} -f:a {3} -strict experimental \"{4}\"", - temporarySource, - ffmpegCodecName, - QualityToBitRate(quality), - ffmpegMuxerName, - temporaryOutput), - out ffmpegStdout, - out ffmpegStderr); - if (ffmpegExitCode != 0) - { - throw new InvalidOperationException("ffmpeg exited with non-zero exit code: \n" + ffmpegStdout + "\n" + ffmpegStderr); - } + // Looks like XNA only cares about type mismatch when + // the type is WAV... else it is ok. + if ( (audioFileType == AudioFileType.Wav || _fileType == AudioFileType.Wav) && + audioFileType != _fileType) + throw new ArgumentException("Incorrect file type!", "audioFileType"); - byte[] rawData; - using (var fs = new FileStream(temporaryOutput, FileMode.Open, FileAccess.Read)) + // Only provide the data for WAV files. + if (audioFileType == AudioFileType.Wav) { - rawData = new byte[fs.Length]; - fs.Read(rawData, 0, rawData.Length); - } + byte[] rawData; - if (saveToFile != null) - { - using (var fs = new FileStream(saveToFile, FileMode.Create, FileAccess.Write)) + // Must be opened in read mode otherwise it fails to open + // read-only files (found in some source control systems) + using (var fs = new FileStream(_fileName, FileMode.Open, FileAccess.Read)) { - fs.Write(rawData, 0, rawData.Length); + rawData = new byte[fs.Length]; + fs.Read(rawData, 0, rawData.Length); } - this.data = null; - } - else - { - this.data = rawData.ToList(); - } - - // Get the audio metadata from the output file - string ffprobeStdout, ffprobeStderr; - var ffprobeExitCode = ExternalTool.Run( - "ffprobe", - string.Format("-i \"{0}\" -show_entries streams -v quiet -of flat", temporaryOutput), - out ffprobeStdout, - out ffprobeStderr); - if (ffprobeExitCode != 0) - { - throw new InvalidOperationException("ffprobe exited with non-zero exit code."); - } - - // Set default values if information is not available. - int averageBytesPerSecond = 0; - int bitsPerSample = 0; - int blockAlign = 0; - int channelCount = 0; - int sampleRate = 0; - double durationInSeconds = 0; + AudioFormat riffAudioFormat; + var stripped = DefaultAudioProfile.StripRiffWaveHeader(rawData, out riffAudioFormat); - var numberFormat = System.Globalization.CultureInfo.InvariantCulture.NumberFormat; - foreach (var line in ffprobeStdout.Split(new[] { '\r', '\n', '\0' }, StringSplitOptions.RemoveEmptyEntries)) - { - var kv = line.Split(new[] { '=' }, 2); - - switch (kv[0]) + if (riffAudioFormat != null) { - case "streams.stream.0.sample_rate": - sampleRate = int.Parse(kv[1].Trim('"'), numberFormat); - break; - case "streams.stream.0.bits_per_sample": - bitsPerSample = int.Parse(kv[1].Trim('"'), numberFormat); - break; - case "streams.stream.0.duration": - durationInSeconds = double.Parse(kv[1].Trim('"'), numberFormat); - break; - case "streams.stream.0.channels": - channelCount = int.Parse(kv[1].Trim('"'), numberFormat); - break; - case "streams.stream.0.bit_rate": - averageBytesPerSecond = (int.Parse(kv[1].Trim('"'), numberFormat) / 8); - break; + if (_format.BlockAlign != riffAudioFormat.BlockAlign) + throw new InvalidOperationException("Calcualted block align does not match RIFF " + _format.BlockAlign + " : " + riffAudioFormat.BlockAlign); + if (_format.ChannelCount != riffAudioFormat.ChannelCount || _format.Format != riffAudioFormat.Format + || _format.SampleRate != riffAudioFormat.SampleRate || _format.AverageBytesPerSecond != riffAudioFormat.AverageBytesPerSecond) + throw new InvalidOperationException("Probed audio format does not match RIFF"); } - } - // Calculate blockAlign. - switch (formatType) - { - case ConversionFormat.Adpcm: - case ConversionFormat.ImaAdpcm: - case ConversionFormat.Pcm: - // Block alignment value is the number of bytes in an atomic unit (that is, a block) of audio for a particular format. For Pulse Code Modulation (PCM) formats, the formula for calculating block alignment is as follows: - // • Block Alignment = Bytes per Sample x Number of Channels - // For example, the block alignment value for 16-bit PCM format mono audio is 2 (2 bytes per sample x 1 channel). For 16-bit PCM format stereo audio, the block alignment value is 4. - // https://msdn.microsoft.com/en-us/library/system.speech.audioformat.speechaudioformatinfo.blockalign(v=vs.110).aspx - // Get the raw PCM from the output WAV file - using (var reader = new BinaryReader(new MemoryStream(rawData))) - { - data = GetRawWavData(reader, ref blockAlign).ToList(); - } - break; - default: - // blockAlign is not available from ffprobe (and may or may not - // be relevant for non-PCM formats anyway) - break; + _data = Array.AsReadOnly(stripped); } - - this.duration = TimeSpan.FromSeconds(durationInSeconds); - this.format = new AudioFormat( - averageBytesPerSecond, - bitsPerSample, - blockAlign, - channelCount, - format, - sampleRate); - - // Loop start and length in number of samples. Defaults to entire sound - loopStart = 0; - if (data != null && bitsPerSample > 0 && channelCount > 0) - loopLength = data.Count / ((bitsPerSample / 8) * channelCount); - else - loopLength = 0; } - finally + catch (Exception ex) { - File.Delete(temporarySource); - File.Delete(temporaryOutput); + var message = string.Format("Failed to open file {0}. Ensure the file is a valid audio file and is not DRM protected.", Path.GetFileNameWithoutExtension(audioFileName)); + throw new InvalidContentException(message, ex); } } - private void Read(string filename) + /// + /// Transcodes the source audio to the target format and quality. + /// + /// Format to convert this audio to. + /// Quality of the processed output audio. For streaming formats, it can be one of the following: Low (96 kbps), Medium (128 kbps), Best (192 kbps). For WAV formats, it can be one of the following: Low (11kHz ADPCM), Medium (22kHz ADPCM), Best (44kHz PCM) + /// + /// The name of the file that the converted audio should be saved into. This is used for SongContent, where + /// the audio is stored external to the XNB file. If this is null, then the converted audio is stored in + /// the Data property. + /// + [Obsolete("You should prefer to use AudioProfile.")] + public void ConvertFormat(ConversionFormat formatType, ConversionQuality quality, string saveToFile) { - // Must be opened in read mode otherwise it fails to open read-only files (found in some source control systems) - using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) - { - var data = new byte[fs.Length]; - fs.Read(data, 0, data.Length); - this.data = data.ToList(); - } + // Call the legacy conversion code. + DefaultAudioProfile.ConvertToFormat(this, formatType, quality, saveToFile); } - // Borrowed from AudioLoader to get the raw data from the WAV - private static byte[] GetRawWavData(BinaryReader reader, ref int blockAlign) + public void SetData(byte[] data, AudioFormat format, TimeSpan duration, int loopStart, int loopLength) { - byte[] audioData; - - //header - string signature = new string(reader.ReadChars(4)); - if (signature != "RIFF") - { - throw new NotSupportedException("Specified stream is not a wave file."); - } - - reader.ReadInt32(); // riff_chunck_size - - string wformat = new string(reader.ReadChars(4)); - if (wformat != "WAVE") - { - throw new NotSupportedException("Specified stream is not a wave file."); - } - - // WAVE header - string format_signature = new string(reader.ReadChars(4)); - while (format_signature != "fmt ") - { - reader.ReadBytes(reader.ReadInt32()); - format_signature = new string(reader.ReadChars(4)); - } - - int format_chunk_size = reader.ReadInt32(); - - // total bytes read: tbp - int audio_format = reader.ReadInt16(); // 2 - int num_channels = reader.ReadInt16(); // 4 - int sample_rate = reader.ReadInt32(); // 8 - reader.ReadInt32(); // 12, byte_rate - blockAlign = reader.ReadInt16(); // 14, block_align - int bits_per_sample = reader.ReadInt16(); // 16 - - // reads residual bytes - if (format_chunk_size > 16) - reader.ReadBytes(format_chunk_size - 16); - - string data_signature = new string(reader.ReadChars(4)); - - while (data_signature.ToLowerInvariant() != "data") - { - reader.ReadBytes(reader.ReadInt32()); - data_signature = new string(reader.ReadChars(4)); - } - - if (data_signature != "data") - { - throw new NotSupportedException("Specified wave file is not supported."); - } - - int data_chunk_size = reader.ReadInt32(); - audioData = reader.ReadBytes(data_chunk_size); + if (data == null) + throw new ArgumentNullException("data"); + if (format == null) + throw new ArgumentNullException("format"); + + _data = Array.AsReadOnly(data); + _format = format; + _duration = duration; + _loopStart = loopStart; + _loopLength = loopLength; + } - return audioData; + public void Dispose() + { + _disposed = true; + _data = null; } - } + } } diff --git a/MonoGame.Framework.Content.Pipeline/Audio/AudioFileType.cs b/MonoGame.Framework.Content.Pipeline/Audio/AudioFileType.cs index 7894f95af49..bb3a8905b39 100644 --- a/MonoGame.Framework.Content.Pipeline/Audio/AudioFileType.cs +++ b/MonoGame.Framework.Content.Pipeline/Audio/AudioFileType.cs @@ -23,5 +23,10 @@ public enum AudioFileType /// The WMA format /// Wma, + + /// + /// The Ogg format + /// + Ogg, } } diff --git a/MonoGame.Framework.Content.Pipeline/Audio/AudioFormat.cs b/MonoGame.Framework.Content.Pipeline/Audio/AudioFormat.cs index ea2a02a0283..8a79ed7dce1 100644 --- a/MonoGame.Framework.Content.Pipeline/Audio/AudioFormat.cs +++ b/MonoGame.Framework.Content.Pipeline/Audio/AudioFormat.cs @@ -89,7 +89,6 @@ private List ConstructNativeWaveFormat() { using (var writer = new BinaryWriter(memory)) { - writer.Write((int)18); /* header size */ writer.Write((short)this.format); writer.Write((short)this.channelCount); writer.Write((int)this.sampleRate); diff --git a/MonoGame.Framework.Content.Pipeline/Audio/AudioProfile.cs b/MonoGame.Framework.Content.Pipeline/Audio/AudioProfile.cs new file mode 100644 index 00000000000..56e66312a46 --- /dev/null +++ b/MonoGame.Framework.Content.Pipeline/Audio/AudioProfile.cs @@ -0,0 +1,81 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using System.Collections.Generic; +using System.Linq; + + +namespace Microsoft.Xna.Framework.Content.Pipeline.Audio +{ + public abstract class AudioProfile + { + private static readonly LoadedTypeCollection _profiles = new LoadedTypeCollection(); + + /// + /// Find the profile for this target platform. + /// + /// The platform target for audio. + /// + public static AudioProfile ForPlatform(TargetPlatform platform) + { + var profile = _profiles.FirstOrDefault(h => h.Supports(platform)); + if (profile != null) + return profile; + + throw new PipelineException("There is no supported audio profile for the '" + platform + "' platform!"); + } + + /// + /// Returns true if this profile supports audio processing for this platform. + /// + public abstract bool Supports(TargetPlatform platform); + + /// + /// Converts the audio content to work on targeted platform. + /// + /// The platform to build the audio content for. + /// The suggested audio quality level. + /// The audio content to convert. + /// The quality used for conversion which could be different from the suggested quality. + public abstract ConversionQuality ConvertAudio(TargetPlatform platform, ConversionQuality quality, AudioContent content); + + /// + /// Converts the audio content to a streaming format that works on targeted platform. + /// + /// The platform to build the audio content for. + /// The suggested audio quality level. + /// he audio content to convert. + /// + /// The quality used for conversion which could be different from the suggested quality. + public abstract ConversionQuality ConvertStreamingAudio(TargetPlatform platform, ConversionQuality quality, AudioContent content, ref string outputFileName); + + + protected static int QualityToSampleRate(ConversionQuality quality, int sourceSampleRate) + { + switch (quality) + { + case ConversionQuality.Low: + return Math.Max(8000, (int)Math.Floor(sourceSampleRate / 2.0)); + case ConversionQuality.Medium: + return Math.Max(8000, (int)Math.Floor((sourceSampleRate / 4.0) * 3)); + } + + return Math.Max(8000, sourceSampleRate); + } + + protected static int QualityToBitRate(ConversionQuality quality) + { + switch (quality) + { + case ConversionQuality.Low: + return 96000; + case ConversionQuality.Medium: + return 128000; + } + + return 192000; + } + } +} diff --git a/MonoGame.Framework.Content.Pipeline/Audio/DefaultAudioProfile.cs b/MonoGame.Framework.Content.Pipeline/Audio/DefaultAudioProfile.cs new file mode 100644 index 00000000000..6974c88b21c --- /dev/null +++ b/MonoGame.Framework.Content.Pipeline/Audio/DefaultAudioProfile.cs @@ -0,0 +1,428 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using System.Globalization; +using System.IO; +using System.Linq; + + +namespace Microsoft.Xna.Framework.Content.Pipeline.Audio +{ + internal class DefaultAudioProfile : AudioProfile + { + public override bool Supports(TargetPlatform platform) + { + return platform == TargetPlatform.Android || + platform == TargetPlatform.DesktopGL || + platform == TargetPlatform.MacOSX || + platform == TargetPlatform.NativeClient || + platform == TargetPlatform.RaspberryPi || + platform == TargetPlatform.Windows || + platform == TargetPlatform.WindowsPhone8 || + platform == TargetPlatform.WindowsStoreApp || + platform == TargetPlatform.iOS; + } + + public override ConversionQuality ConvertAudio(TargetPlatform platform, ConversionQuality quality, AudioContent content) + { + // Default to PCM data. + var targetFormat = ConversionFormat.Pcm; + if (quality != ConversionQuality.Best) + { + if (platform == TargetPlatform.iOS || platform == TargetPlatform.MacOSX) + targetFormat = ConversionFormat.ImaAdpcm; + else + targetFormat = ConversionFormat.Adpcm; + } + + return ConvertToFormat(content, targetFormat, quality, null); + } + + public override ConversionQuality ConvertStreamingAudio(TargetPlatform platform, ConversionQuality quality, AudioContent content, ref string outputFileName) + { + // Most platforms will use AAC ("mp4") by default + var targetFormat = ConversionFormat.Aac; + + if ( platform == TargetPlatform.Windows || + platform == TargetPlatform.WindowsPhone8 || + platform == TargetPlatform.WindowsStoreApp) + targetFormat = ConversionFormat.WindowsMedia; + + else if (platform == TargetPlatform.DesktopGL) + targetFormat = ConversionFormat.Vorbis; + + // Get the song output path with the target format extension. + outputFileName = Path.ChangeExtension(outputFileName, AudioHelper.GetExtension(targetFormat)); + + // Make sure the output folder for the file exists. + Directory.CreateDirectory(Path.GetDirectoryName(outputFileName)); + + return ConvertToFormat(content, targetFormat, quality, outputFileName); + } + + public static void ProbeFormat(string sourceFile, out AudioFileType audioFileType, out AudioFormat audioFormat, out TimeSpan duration, out int loopStart, out int loopLength) + { + string ffprobeStdout, ffprobeStderr; + var ffprobeExitCode = ExternalTool.Run( + "ffprobe", + string.Format("-i \"{0}\" -show_format -show_entries streams -v quiet -of flat", sourceFile), + out ffprobeStdout, + out ffprobeStderr); + if (ffprobeExitCode != 0) + throw new InvalidOperationException("ffprobe exited with non-zero exit code."); + + // Set default values if information is not available. + int averageBytesPerSecond = 0; + int bitsPerSample = 0; + int blockAlign = 0; + int channelCount = 0; + int sampleRate = 0; + int format = 0; + string sampleFormat = null; + double durationInSeconds = 0; + var formatName = string.Empty; + + try + { + var numberFormat = CultureInfo.InvariantCulture.NumberFormat; + foreach (var line in ffprobeStdout.Split(new[] {'\r', '\n', '\0'}, StringSplitOptions.RemoveEmptyEntries)) + { + var kv = line.Split(new[] {'='}, 2); + + switch (kv[0]) + { + case "streams.stream.0.sample_rate": + sampleRate = int.Parse(kv[1].Trim('"'), numberFormat); + break; + case "streams.stream.0.bits_per_sample": + bitsPerSample = int.Parse(kv[1].Trim('"'), numberFormat); + break; + case "streams.stream.0.start_time": + { + double seconds; + if (double.TryParse(kv[1].Trim('"'), NumberStyles.Any, numberFormat, out seconds)) + durationInSeconds += seconds; + break; + } + case "streams.stream.0.duration": + durationInSeconds += double.Parse(kv[1].Trim('"'), numberFormat); + break; + case "streams.stream.0.channels": + channelCount = int.Parse(kv[1].Trim('"'), numberFormat); + break; + case "streams.stream.0.sample_fmt": + sampleFormat = kv[1].Trim('"').ToLowerInvariant(); + break; + case "streams.stream.0.bit_rate": + averageBytesPerSecond = (int.Parse(kv[1].Trim('"'), numberFormat)/8); + break; + case "format.format_name": + formatName = kv[1].Trim('"').ToLowerInvariant(); + break; + case "streams.stream.0.codec_tag": + { + var hex = kv[1].Substring(3, kv[1].Length - 4); + format = int.Parse(hex, NumberStyles.HexNumber); + break; + } + } + } + } + catch (Exception ex) + { + throw new InvalidOperationException("Failed to parse ffprobe output.", ex); + } + + // XNA seems to use the sample format for the bits per sample + // in the case of non-PCM formats like MP3 and WMA. + if (bitsPerSample == 0 && sampleFormat != null) + { + switch (sampleFormat) + { + case "u8": + case "u8p": + bitsPerSample = 8; + break; + case "s16": + case "s16p": + bitsPerSample = 16; + break; + case "s32": + case "s32p": + case "flt": + case "fltp": + bitsPerSample = 32; + break; + case "dbl": + case "dblp": + bitsPerSample = 64; + break; + } + } + + // Figure out the file type. + var durationMs = (int)Math.Floor(durationInSeconds * 1000.0); + if (formatName == "wav") + { + audioFileType = AudioFileType.Wav; + + // A quirk of XNA? + if (bitsPerSample == 32) + format = -2; + } + else if (formatName == "mp3") + { + audioFileType = AudioFileType.Mp3; + format = 1; + durationMs = (int)Math.Ceiling(durationInSeconds * 1000.0); + bitsPerSample = Math.Min(bitsPerSample, 16); + } + else if (formatName == "wma" || formatName == "asf") + { + audioFileType = AudioFileType.Wma; + format = 1; + durationMs = (int)Math.Ceiling(durationInSeconds * 1000.0); + bitsPerSample = Math.Min(bitsPerSample, 16); + } + else if (formatName == "ogg") + { + audioFileType = AudioFileType.Ogg; + format = 1; + durationMs = (int)Math.Ceiling(durationInSeconds * 1000.0); + bitsPerSample = Math.Min(bitsPerSample, 16); + } + else + audioFileType = (AudioFileType) (-1); + + // XNA seems to calculate the block alignment directly from + // the bits per sample and channel count regardless of the + // format of the audio data. + if (bitsPerSample > 0) + blockAlign = (bitsPerSample * channelCount) / 8; + + // XNA seems to only be accurate to the millisecond. + duration = TimeSpan.FromMilliseconds(durationMs); + + // Looks like XNA calculates the average bps from + // the sample rate and block alignment. + if (blockAlign > 0) + averageBytesPerSecond = sampleRate * blockAlign; + + audioFormat = new AudioFormat( + averageBytesPerSecond, + bitsPerSample, + blockAlign, + channelCount, + format, + sampleRate); + + // Loop start and length in number of samples. For some + // reason XNA doesn't report loop length for non-WAV sources. + loopStart = 0; + if (audioFileType != AudioFileType.Wav) + loopLength = 0; + else + loopLength = (int)Math.Floor(sampleRate * durationInSeconds); + } + + internal static byte[] StripRiffWaveHeader(byte[] data, out AudioFormat audioFormat) + { + audioFormat = null; + + using (var reader = new BinaryReader(new MemoryStream(data))) + { + var signature = new string(reader.ReadChars(4)); + if (signature != "RIFF") + return data; + + reader.ReadInt32(); // riff_chunck_size + + var wformat = new string(reader.ReadChars(4)); + if (wformat != "WAVE") + return data; + + // Look for the data chunk. + while (true) + { + var chunkSignature = new string(reader.ReadChars(4)); + if (chunkSignature.ToLowerInvariant() == "data") + break; + if (chunkSignature.ToLowerInvariant() == "fmt ") + { + int fmtLength = reader.ReadInt32(); + short formatTag = reader.ReadInt16(); + short channels = reader.ReadInt16(); + int sampleRate = reader.ReadInt32(); + int avgBytesPerSec = reader.ReadInt32(); + short blockAlign = reader.ReadInt16(); + short bitsPerSample = reader.ReadInt16(); + audioFormat = new AudioFormat(avgBytesPerSec, bitsPerSample, blockAlign, channels, formatTag, sampleRate); + + fmtLength -= 2 + 2 + 4 + 4 + 2 + 2; + if (fmtLength < 0) + throw new InvalidOperationException("riff wave header has unexpected format"); + reader.BaseStream.Seek(fmtLength, SeekOrigin.Current); + } + else + { + reader.BaseStream.Seek(reader.ReadInt32(), SeekOrigin.Current); + } + } + + var dataSize = reader.ReadInt32(); + data = reader.ReadBytes(dataSize); + } + + return data; + } + + public static void WritePcmFile(AudioContent content, string saveToFile) + { + string ffmpegStdout, ffmpegStderr; + var ffmpegExitCode = ExternalTool.Run( + "ffmpeg", + string.Format( + "-y -i \"{0}\" -vn -c:a pcm_s16le -b:a 192000 -f:a wav -strict experimental \"{1}\"", + content.FileName, + saveToFile), + out ffmpegStdout, + out ffmpegStderr); + if (ffmpegExitCode != 0) + throw new InvalidOperationException("ffmpeg exited with non-zero exit code: \n" + ffmpegStdout + "\n" + ffmpegStderr); + } + + public static ConversionQuality ConvertToFormat(AudioContent content, ConversionFormat formatType, ConversionQuality quality, string saveToFile) + { + var temporaryOutput = Path.GetTempFileName(); + try + { + string ffmpegCodecName, ffmpegMuxerName; + //int format; + switch (formatType) + { + case ConversionFormat.Adpcm: + // ADPCM Microsoft + ffmpegCodecName = "adpcm_ms"; + ffmpegMuxerName = "wav"; + //format = 0x0002; /* WAVE_FORMAT_ADPCM */ + break; + case ConversionFormat.Pcm: + // XNA seems to preserve the bit size of the input + // format when converting to PCM. + if (content.Format.BitsPerSample == 8) + ffmpegCodecName = "pcm_u8"; + else if (content.Format.BitsPerSample == 32) + ffmpegCodecName = "pcm_s32le"; + else + ffmpegCodecName = "pcm_s16le"; + ffmpegMuxerName = "wav"; + //format = 0x0001; /* WAVE_FORMAT_PCM */ + break; + case ConversionFormat.WindowsMedia: + // Windows Media Audio 2 + ffmpegCodecName = "wmav2"; + ffmpegMuxerName = "asf"; + //format = 0x0161; /* WAVE_FORMAT_WMAUDIO2 */ + break; + case ConversionFormat.Xma: + throw new NotSupportedException( + "XMA is not a supported encoding format. It is specific to the Xbox 360."); + case ConversionFormat.ImaAdpcm: + // ADPCM IMA WAV + ffmpegCodecName = "adpcm_ima_wav"; + ffmpegMuxerName = "wav"; + //format = 0x0011; /* WAVE_FORMAT_IMA_ADPCM */ + break; + case ConversionFormat.Aac: + // AAC (Advanced Audio Coding) + // Requires -strict experimental + ffmpegCodecName = "aac"; + ffmpegMuxerName = "ipod"; + //format = 0x0000; /* WAVE_FORMAT_UNKNOWN */ + break; + case ConversionFormat.Vorbis: + // Vorbis + ffmpegCodecName = "libvorbis"; + ffmpegMuxerName = "ogg"; + //format = 0x0000; /* WAVE_FORMAT_UNKNOWN */ + break; + default: + // Unknown format + throw new NotSupportedException(); + } + + string ffmpegStdout, ffmpegStderr; + int ffmpegExitCode; + do + { + ffmpegExitCode = ExternalTool.Run( + "ffmpeg", + string.Format( + "-y -i \"{0}\" -vn -c:a {1} -b:a {2} -ar {3} -f:a {4} -strict experimental \"{5}\"", + content.FileName, + ffmpegCodecName, + QualityToBitRate(quality), + QualityToSampleRate(quality, content.Format.SampleRate), + ffmpegMuxerName, + temporaryOutput), + out ffmpegStdout, + out ffmpegStderr); + if (ffmpegExitCode != 0) + quality--; + } while (quality >= 0 && ffmpegExitCode != 0); + + if (ffmpegExitCode != 0) + { + throw new InvalidOperationException("ffmpeg exited with non-zero exit code: \n" + ffmpegStdout + "\n" + ffmpegStderr); + } + + byte[] rawData; + using (var fs = new FileStream(temporaryOutput, FileMode.Open, FileAccess.Read)) + { + rawData = new byte[fs.Length]; + fs.Read(rawData, 0, rawData.Length); + } + + if (saveToFile != null) + { + using (var fs = new FileStream(saveToFile, FileMode.Create, FileAccess.Write)) + fs.Write(rawData, 0, rawData.Length); + } + + // Use probe to get the final format and information on the converted file. + AudioFileType audioFileType; + AudioFormat audioFormat; + TimeSpan duration; + int loopStart, loopLength; + ProbeFormat(temporaryOutput, out audioFileType, out audioFormat, out duration, out loopStart, out loopLength); + + AudioFormat riffAudioFormat; + byte[] data = StripRiffWaveHeader(rawData, out riffAudioFormat); + + // deal with adpcm + if (audioFormat.Format == 2) + { + // riff contains correct blockAlign + audioFormat = riffAudioFormat; + + // fix loopLength -> has to be multiple of sample per block + // see https://msdn.microsoft.com/de-de/library/windows/desktop/ee415711(v=vs.85).aspx + ushort samplesPerBlock = (ushort)(audioFormat.BlockAlign * 2 / audioFormat.ChannelCount - 12); // from https://github.com/sharpdx/SharpDX/blob/master/Source/SharpDX/Multimedia/WaveFormatAdpcm.cs + loopLength = (int)(audioFormat.SampleRate * duration.TotalSeconds); + int remainder = loopLength % samplesPerBlock; + loopLength += samplesPerBlock - remainder; + } + + content.SetData(data, audioFormat, duration, loopStart, loopLength); + } + finally + { + ExternalTool.DeleteFile(temporaryOutput); + } + + return quality; + } + } +} diff --git a/MonoGame.Framework.Content.Pipeline/Builder/PathHelper.cs b/MonoGame.Framework.Content.Pipeline/Builder/PathHelper.cs index 0342c42a2c5..8728a0c442c 100644 --- a/MonoGame.Framework.Content.Pipeline/Builder/PathHelper.cs +++ b/MonoGame.Framework.Content.Pipeline/Builder/PathHelper.cs @@ -3,7 +3,6 @@ // file 'LICENSE.txt', which is part of this source code package. using System; -using System.IO; using Microsoft.Xna.Framework.Content.Pipeline; namespace MonoGame.Framework.Content.Pipeline.Builder @@ -40,41 +39,6 @@ public static string NormalizeWindows(string path) return path.Replace('/', '\\'); } - /// - /// Returns a path string normalized to the current platform standard. - /// - public static string NormalizeOS(string path) - { -#if WINRT - return NormalizeWindows(path); -#else - path = path.Replace('\\', Path.DirectorySeparatorChar); - path = path.Replace('/', Path.DirectorySeparatorChar); - return path; -#endif - } - - /// - /// Returns a path string normalized to the current platform standard. - /// - /// Path to normalize - /// The platform to normalize for - /// The normalized path - public static string NormalizeOS(string path, TargetPlatform targetPlatform) - { - switch (targetPlatform) - { - case TargetPlatform.Windows: - case TargetPlatform.DesktopGL: - case TargetPlatform.WindowsPhone8: - case TargetPlatform.WindowsStoreApp: - return NormalizeWindows(path); - - default: - return Normalize(path); - } - } - /// /// Returns a path relative to the base path. /// @@ -92,24 +56,5 @@ public static string GetRelativePath(string basePath, string path) return Normalize(str); } - - /// - /// Returns a path relative to the base path. - /// - /// The path to make relative to. Must end with directory seperator. - /// The path to be made relative to the basePath. - /// The platform to normalize the path for. - /// The relative path or the original string if it is not absolute or cannot be made relative. - public static string GetRelativePath(string basePath, string path, TargetPlatform targetPlatform) - { - Uri uri; - if (!Uri.TryCreate(path, UriKind.Absolute, out uri)) - return path; - - uri = new Uri(basePath).MakeRelativeUri(uri); - var str = Uri.UnescapeDataString(uri.ToString()); - - return NormalizeOS(str, targetPlatform); - } } } diff --git a/MonoGame.Framework.Content.Pipeline/Builder/PipelineManager.cs b/MonoGame.Framework.Content.Pipeline/Builder/PipelineManager.cs index d781bad942b..d3a7da064de 100644 --- a/MonoGame.Framework.Content.Pipeline/Builder/PipelineManager.cs +++ b/MonoGame.Framework.Content.Pipeline/Builder/PipelineManager.cs @@ -59,7 +59,6 @@ private struct ProcessorInfo public string IntermediateDirectory { get; private set; } private ContentCompiler _compiler; - private MethodInfo _compileMethod; public ContentBuildLogger Logger { get; set; } diff --git a/MonoGame.Framework.Content.Pipeline/DdsLoader.cs b/MonoGame.Framework.Content.Pipeline/DdsLoader.cs index 459b57e947d..de971e50f0c 100644 --- a/MonoGame.Framework.Content.Pipeline/DdsLoader.cs +++ b/MonoGame.Framework.Content.Pipeline/DdsLoader.cs @@ -2,6 +2,7 @@ // This file is subject to the terms and conditions defined in // file 'LICENSE.txt', which is part of this source code package. +using System; using Microsoft.Xna.Framework.Content.Pipeline.Graphics; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics.PackedVector; @@ -14,6 +15,7 @@ namespace Microsoft.Xna.Framework.Content.Pipeline /// class DdsLoader { + [Flags] enum Ddsd : uint { Caps = 0x1, // Required in every DDS file @@ -26,6 +28,7 @@ enum Ddsd : uint Depth = 0x800000, // Required in a depth texture } + [Flags] enum DdsCaps : uint { Complex = 0x8, // Optional; must be used on any file that contains more than one surface (a mipmap, a cubic environment map, or mipmapped volume texture) @@ -33,6 +36,7 @@ enum DdsCaps : uint Texture = 0x1000, // Required } + [Flags] enum DdsCaps2 : uint { Cubemap = 0x200, @@ -47,6 +51,7 @@ enum DdsCaps2 : uint CubemapAllFaces = Cubemap | CubemapPositiveX | CubemapNegativeX | CubemapPositiveY | CubemapNegativeY | CubemapPositiveZ | CubemapNegativeZ, } + [Flags] enum Ddpf : uint { AlphaPixels = 0x1, @@ -69,6 +74,7 @@ static uint MakeFourCC(string cc) enum FourCC : uint { + A32B32G32R32F = 116, Dxt1 = 0x31545844, Dxt2 = 0x32545844, Dxt3 = 0x33545844, @@ -108,9 +114,10 @@ static SurfaceFormat GetSurfaceFormat(ref DdsPixelFormat pixelFormat, out bool r rbSwap = false; if (pixelFormat.dwFlags.HasFlag(Ddpf.FourCC)) { - // It is a compressed format switch (pixelFormat.dwFourCC) { + case FourCC.A32B32G32R32F: + return SurfaceFormat.Vector4; case FourCC.Dxt1: return SurfaceFormat.Dxt1; case FourCC.Dxt2: @@ -141,7 +148,7 @@ static SurfaceFormat GetSurfaceFormat(ref DdsPixelFormat pixelFormat, out bool r } else if (pixelFormat.dwRgbBitCount == 32) { - rbSwap = pixelFormat.dwBBitMask == 0xFF00; + rbSwap = pixelFormat.dwBBitMask == 0xFF; return SurfaceFormat.Color; } throw new ContentLoadException("Unsupported RGBA pixel format"); @@ -154,9 +161,23 @@ static SurfaceFormat GetSurfaceFormat(ref DdsPixelFormat pixelFormat, out bool r rbSwap = pixelFormat.dwBBitMask == 0x1F; return SurfaceFormat.Bgr565; } + else if (pixelFormat.dwRgbBitCount == 24) + { + rbSwap = pixelFormat.dwBBitMask == 0xFF; + return SurfaceFormat.Color; + } + else if (pixelFormat.dwRgbBitCount == 32) + { + rbSwap = pixelFormat.dwBBitMask == 0xFF; + return SurfaceFormat.Color; + } throw new ContentLoadException("Unsupported RGB pixel format"); } } + //else if (pixelFormat.dwFlags.HasFlag(Ddpf.Luminance)) + //{ + // return SurfaceFormat.Alpha8; + //} throw new ContentLoadException("Unsupported pixel format"); } @@ -184,6 +205,9 @@ static BitmapContent CreateBitmapContent(SurfaceFormat format, int width, int he case SurfaceFormat.Dxt5: return new Dxt5BitmapContent(width, height); + + case SurfaceFormat.Vector4: + return new PixelBitmapContent(width, height); } throw new ContentLoadException("Unsupported SurfaceFormat " + format); } @@ -194,21 +218,29 @@ static int GetBitmapSize(SurfaceFormat format, int width, int height) // https://msdn.microsoft.com/en-us/library/bb943991.aspx int pitch = 0; int rows = 0; - if (format == SurfaceFormat.Dxt1) - { - pitch = MathHelper.Max(1, ((width + 3) / 4)) * 8; - rows = (height + 3) / 4; - } - else if (format == SurfaceFormat.Dxt3 || format == SurfaceFormat.Dxt5) - { - pitch = MathHelper.Max(1, ((width + 3) / 4)) * 16; - rows = (height + 3) / 4; - } - else if (format == SurfaceFormat.Color) + + switch (format) { - pitch = (width * 32 + 7) / 8; - rows = height; + case SurfaceFormat.Color: + case SurfaceFormat.Bgra4444: + case SurfaceFormat.Bgra5551: + case SurfaceFormat.Bgr565: + case SurfaceFormat.Vector4: + pitch = width * format.GetSize(); + rows = height; + break; + + case SurfaceFormat.Dxt1: + case SurfaceFormat.Dxt3: + case SurfaceFormat.Dxt5: + pitch = ((width + 3) / 4) * format.GetSize(); + rows = (height + 3) / 4; + break; + + default: + throw new ContentLoadException("Unsupported SurfaceFormat " + format); } + return pitch * rows; } @@ -303,6 +335,35 @@ static internal TextureContent Import(string filename, ContentImporterContext co var content = CreateBitmapContent(format, w, h); var byteCount = GetBitmapSize(format, w, h); var bytes = reader.ReadBytes(byteCount); + if (rbSwap) + { + switch (format) + { + case SurfaceFormat.Bgr565: + ByteSwapBGR565(bytes); + break; + case SurfaceFormat.Bgra4444: + ByteSwapBGRA4444(bytes); + break; + case SurfaceFormat.Bgra5551: + ByteSwapBGRA5551(bytes); + break; + case SurfaceFormat.Color: + if (header.ddspf.dwRgbBitCount == 32) + ByteSwapRGBX(bytes); + else if (header.ddspf.dwRgbBitCount == 24) + ByteSwapRGB(bytes); + break; + } + } + if ((format == SurfaceFormat.Color) && header.ddspf.dwFlags.HasFlag(Ddpf.Rgb) && !header.ddspf.dwFlags.HasFlag(Ddpf.AlphaPixels)) + { + // Fill or add alpha with opaque + if (header.ddspf.dwRgbBitCount == 32) + ByteFillAlpha(bytes); + else if (header.ddspf.dwRgbBitCount == 24) + ByteExpandAlpha(ref bytes); + } content.SetPixelData(bytes); mipMaps.Add(content); w = MathHelper.Max(1, w / 2); @@ -314,5 +375,161 @@ static internal TextureContent Import(string filename, ContentImporterContext co return output; } + + static void ByteFillAlpha(byte[] bytes) + { + for (int i = 0; i < bytes.Length; i += 4) + { + bytes[i + 3] = 255; + } + } + + static void ByteExpandAlpha(ref byte[] bytes) + { + var rgba = new byte[bytes.Length + (bytes.Length / 3)]; + int j = 0; + for (int i = 0; i < bytes.Length; i += 3) + { + rgba[j] = bytes[i]; + rgba[j + 1] = bytes[i + 1]; + rgba[j + 2] = bytes[i + 2]; + rgba[j + 3] = 255; + j += 4; + } + bytes = rgba; + } + + static void ByteSwapRGB(byte[] bytes) + { + for (int i = 0; i < bytes.Length; i += 3) + { + byte r = bytes[i]; + bytes[i] = bytes[i + 2]; + bytes[i + 2] = r; + } + } + + static void ByteSwapRGBX(byte[] bytes) + { + for (int i = 0; i < bytes.Length; i += 4) + { + byte r = bytes[i]; + bytes[i] = bytes[i + 2]; + bytes[i + 2] = r; + } + } + + static void ByteSwapBGRA4444(byte[] bytes) + { + for (int i = 0; i < bytes.Length; i += 2) + { + var r = bytes[i] & 0xF0; + var b = bytes[i + 1] & 0xF0; + bytes[i] = (byte)((bytes[i] & 0x0F) | b); + bytes[i + 1] = (byte)((bytes[i + 1] & 0x0F) | r); + } + } + + static void ByteSwapBGRA5551(byte[] bytes) + { + for (int i = 0; i < bytes.Length; i += 2) + { + var r = (bytes[i] & 0xF8) >> 3; + var b = (bytes[i + 1] & 0x3E) >> 1; + bytes[i] = (byte)((bytes[i] & 0x07) | (b << 3)); + bytes[i + 1] = (byte)((bytes[i + 1] & 0xC1) | (r << 1)); + } + } + + static void ByteSwapBGR565(byte[] bytes) + { + for (int i = 0; i < bytes.Length; i += 2) + { + var r = (bytes[i] & 0xF8) >> 3; + var b = bytes[i + 1] & 0x1F; + bytes[i] = (byte)((bytes[i] & 0x07) | (b << 3)); + bytes[i + 1] = (byte)((bytes[i + 1] & 0xE0) | r); + } + } + + internal static void WriteUncompressed(string filename, BitmapContent bitmapContent) + { + using (var writer = new BinaryWriter(new FileStream(filename, FileMode.Create, FileAccess.Write))) + { + // Write signature ("DDS ") + writer.Write((byte)0x44); + writer.Write((byte)0x44); + writer.Write((byte)0x53); + writer.Write((byte)0x20); + + var header = new DdsHeader(); + header.dwSize = 124; + header.dwFlags = Ddsd.Caps | Ddsd.Width | Ddsd.Height | Ddsd.Pitch | Ddsd.PixelFormat; + header.dwWidth = (uint)bitmapContent.Width; + header.dwHeight = (uint)bitmapContent.Height; + header.dwPitchOrLinearSize = (uint)(bitmapContent.Width * 4); + header.dwDepth = (uint)0; + header.dwMipMapCount = (uint)0; + + writer.Write((uint)header.dwSize); + writer.Write((uint)header.dwFlags); + writer.Write((uint)header.dwHeight); + writer.Write((uint)header.dwWidth); + writer.Write((uint)header.dwPitchOrLinearSize); + writer.Write((uint)header.dwDepth); + writer.Write((uint)header.dwMipMapCount); + + // 11 unsed and reserved DWORDS. + writer.Write((uint)0); + writer.Write((uint)0); + writer.Write((uint)0); + writer.Write((uint)0); + writer.Write((uint)0); + writer.Write((uint)0); + writer.Write((uint)0); + writer.Write((uint)0); + writer.Write((uint)0); + writer.Write((uint)0); + writer.Write((uint)0); + + SurfaceFormat format; + if (!bitmapContent.TryGetFormat(out format) || format != SurfaceFormat.Color) + throw new NotSupportedException("Unsupported bitmap content!"); + + header.ddspf.dwSize = 32; + header.ddspf.dwFlags = Ddpf.AlphaPixels | Ddpf.Rgb; + header.ddspf.dwFourCC = 0; + header.ddspf.dwRgbBitCount = 32; + header.ddspf.dwRBitMask = 0x000000ff; + header.ddspf.dwGBitMask = 0x0000ff00; + header.ddspf.dwBBitMask = 0x00ff0000; + header.ddspf.dwABitMask = 0xff000000; + + // Write the DDS_PIXELFORMAT + writer.Write((uint)header.ddspf.dwSize); + writer.Write((uint)header.ddspf.dwFlags); + writer.Write((uint)header.ddspf.dwFourCC); + writer.Write((uint)header.ddspf.dwRgbBitCount); + writer.Write((uint)header.ddspf.dwRBitMask); + writer.Write((uint)header.ddspf.dwGBitMask); + writer.Write((uint)header.ddspf.dwBBitMask); + writer.Write((uint)header.ddspf.dwABitMask); + + header.dwCaps = DdsCaps.Texture; + header.dwCaps2 = 0; + + // Continue reading DDS_HEADER + writer.Write((uint)header.dwCaps); + writer.Write((uint)header.dwCaps2); + + // More reserved unused DWORDs. + writer.Write((uint)0); + writer.Write((uint)0); + writer.Write((uint)0); + + // Write out the face data. + writer.Write(bitmapContent.GetPixelData()); + } + } } } diff --git a/MonoGame.Framework.Content.Pipeline/ExternalReference.cs b/MonoGame.Framework.Content.Pipeline/ExternalReference.cs index 29764bf3aa0..d40d3ce1e09 100644 --- a/MonoGame.Framework.Content.Pipeline/ExternalReference.cs +++ b/MonoGame.Framework.Content.Pipeline/ExternalReference.cs @@ -60,7 +60,7 @@ public ExternalReference(string filename, ContentIdentity relativeToContent) // that from here, so we'll work with the absolute path and let the // higher level process sort out any relative paths they need. var basePath = Path.GetDirectoryName(relativeToContent.SourceFilename); - Filename = PathHelper.NormalizeOS(Path.GetFullPath(Path.Combine(basePath, filename))); + Filename = PathHelper.Normalize(Path.GetFullPath(Path.Combine(basePath, filename))); } } } diff --git a/MonoGame.Framework.Content.Pipeline/ExternalTool.cs b/MonoGame.Framework.Content.Pipeline/ExternalTool.cs index d9ba2015e6e..0cc8062b331 100644 --- a/MonoGame.Framework.Content.Pipeline/ExternalTool.cs +++ b/MonoGame.Framework.Content.Pipeline/ExternalTool.cs @@ -114,6 +114,9 @@ public static int Run(string command, string arguments, out string stdout, out s /// private static string FindCommand(string command) { + // Expand any environment variables. + command = Environment.ExpandEnvironmentVariables(command); + // If we have a full path just pass it through. if (File.Exists(command)) return command; @@ -139,5 +142,20 @@ private static string FindCommand(string command) return null; } + + /// + /// Safely deletes the file if it exists. + /// + /// The path to the file to delete. + public static void DeleteFile(string filePath) + { + try + { + File.Delete(filePath); + } + catch (Exception) + { + } + } } } diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/DefaultTextureProfile.cs b/MonoGame.Framework.Content.Pipeline/Graphics/DefaultTextureProfile.cs new file mode 100644 index 00000000000..c7d9743a82f --- /dev/null +++ b/MonoGame.Framework.Content.Pipeline/Graphics/DefaultTextureProfile.cs @@ -0,0 +1,142 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using Microsoft.Xna.Framework.Content.Pipeline.Processors; +using Microsoft.Xna.Framework.Graphics; + + +namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics +{ + internal class DefaultTextureProfile : TextureProfile + { + public override bool Supports(TargetPlatform platform) + { + return platform == TargetPlatform.Android || + platform == TargetPlatform.DesktopGL || + platform == TargetPlatform.MacOSX || + platform == TargetPlatform.NativeClient || + platform == TargetPlatform.RaspberryPi || + platform == TargetPlatform.Windows || + platform == TargetPlatform.WindowsPhone8 || + platform == TargetPlatform.WindowsStoreApp || + platform == TargetPlatform.iOS; + } + + private static bool IsCompressedTextureFormat(TextureProcessorOutputFormat format) + { + switch (format) + { + case TextureProcessorOutputFormat.AtcCompressed: + case TextureProcessorOutputFormat.DxtCompressed: + case TextureProcessorOutputFormat.Etc1Compressed: + case TextureProcessorOutputFormat.PvrCompressed: + return true; + } + return false; + } + + private static TextureProcessorOutputFormat GetTextureFormatForPlatform(TextureProcessorOutputFormat format, TargetPlatform platform) + { + // Select the default texture compression format for the target platform + if (format == TextureProcessorOutputFormat.Compressed) + { + if (platform == TargetPlatform.iOS) + format = TextureProcessorOutputFormat.PvrCompressed; + else if (platform == TargetPlatform.Android) + format = TextureProcessorOutputFormat.Etc1Compressed; + else + format = TextureProcessorOutputFormat.DxtCompressed; + } + + if (IsCompressedTextureFormat(format)) + { + // Make sure the target platform supports the selected texture compression format + if (platform == TargetPlatform.iOS) + { + if (format != TextureProcessorOutputFormat.PvrCompressed) + throw new PlatformNotSupportedException("iOS platform only supports PVR texture compression"); + } + else if (platform == TargetPlatform.Windows || + platform == TargetPlatform.WindowsPhone8 || + platform == TargetPlatform.WindowsStoreApp || + platform == TargetPlatform.DesktopGL || + platform == TargetPlatform.MacOSX || + platform == TargetPlatform.NativeClient) + { + if (format != TextureProcessorOutputFormat.DxtCompressed) + throw new PlatformNotSupportedException(format + " platform only supports DXT texture compression"); + } + } + + return format; + } + + public override void Requirements(ContentProcessorContext context, TextureProcessorOutputFormat format, out bool requiresPowerOfTwo, out bool requiresSquare) + { + if (format == TextureProcessorOutputFormat.Compressed) + format = GetTextureFormatForPlatform(format, context.TargetPlatform); + + // Does it require POT textures? + switch (format) + { + default: + requiresPowerOfTwo = false; + break; + + case TextureProcessorOutputFormat.DxtCompressed: + requiresPowerOfTwo = context.TargetProfile == GraphicsProfile.Reach; + break; + + case TextureProcessorOutputFormat.PvrCompressed: + case TextureProcessorOutputFormat.Etc1Compressed: + requiresPowerOfTwo = true; + break; + } + + // Does it require square textures? + switch (format) + { + default: + requiresSquare = false; + break; + + case TextureProcessorOutputFormat.PvrCompressed: + requiresSquare = true; + break; + } + } + + protected override void PlatformCompressTexture(ContentProcessorContext context, TextureContent content, TextureProcessorOutputFormat format, bool generateMipmaps, bool isSpriteFont) + { + format = GetTextureFormatForPlatform(format, context.TargetPlatform); + + // Make sure we're in a floating point format + content.ConvertBitmapType(typeof(PixelBitmapContent)); + + switch (format) + { + case TextureProcessorOutputFormat.AtcCompressed: + GraphicsUtil.CompressAti(content, generateMipmaps); + break; + + case TextureProcessorOutputFormat.Color16Bit: + GraphicsUtil.CompressColor16Bit(content, generateMipmaps); + break; + + case TextureProcessorOutputFormat.DxtCompressed: + GraphicsUtil.CompressDxt(context.TargetProfile, content, generateMipmaps, isSpriteFont); + break; + + case TextureProcessorOutputFormat.Etc1Compressed: + GraphicsUtil.CompressEtc1(content, generateMipmaps); + break; + + case TextureProcessorOutputFormat.PvrCompressed: + GraphicsUtil.CompressPvrtc(content, generateMipmaps, isSpriteFont); + break; + } + } + } +} diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/DxtBitmapContent.cs b/MonoGame.Framework.Content.Pipeline/Graphics/DxtBitmapContent.cs index 655395ae885..845c90c1c6a 100644 --- a/MonoGame.Framework.Content.Pipeline/Graphics/DxtBitmapContent.cs +++ b/MonoGame.Framework.Content.Pipeline/Graphics/DxtBitmapContent.cs @@ -11,21 +11,35 @@ namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics { - class DxtDataHandler + class DxtDataHandler: IDisposable { private BitmapContent _content; byte[] _buffer; int _offset; + + GCHandle delegateHandleBeginImage; + GCHandle delegateHandleWriteData; public OutputOptions.WriteDataDelegate WriteData { get; private set; } public OutputOptions.ImageDelegate BeginImage { get; private set; } - public DxtDataHandler(BitmapContent content) + public DxtDataHandler(BitmapContent content, OutputOptions outputOptions) { _content = content; WriteData = new OutputOptions.WriteDataDelegate(WriteDataInternal); BeginImage = new OutputOptions.ImageDelegate(BeginImageInternal); + + // Keep the delegate from being re-located or collected by the garbage collector. + delegateHandleBeginImage = GCHandle.Alloc(BeginImage); + delegateHandleWriteData = GCHandle.Alloc(WriteData); + + outputOptions.SetOutputHandler(BeginImage, WriteData); + } + + ~DxtDataHandler() + { + Dispose(false); } void BeginImageInternal(int size, int width, int height, int depth, int face, int miplevel) @@ -42,6 +56,36 @@ bool WriteDataInternal(IntPtr data, int length) _content.SetPixelData(_buffer); return true; } + + #region IDisposable Support + private bool disposed = false; + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + // Release managed objects + // ... + } + + // Release native objects + delegateHandleBeginImage.Free(); + delegateHandleWriteData.Free(); + + disposed = true; + } + } + + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion } public abstract class DxtBitmapContent : BitmapContent @@ -168,18 +212,18 @@ protected override bool TryCopyFrom(BitmapContent sourceBitmap, Rectangle source inputOptions.SetMipmapData(dataPtr, colorBitmap.Width, colorBitmap.Height, 1, 0, 0); inputOptions.SetMipmapGeneration(false); inputOptions.SetGamma(1.0f, 1.0f); - - var outputOptions = new OutputOptions(); - outputOptions.SetOutputHeader(false); - - var handler = new DxtDataHandler(this); - outputOptions.SetOutputHandler(handler.BeginImage, handler.WriteData); - + var compressionOptions = new CompressionOptions(); compressionOptions.SetFormat(outputFormat); compressionOptions.SetQuality(Quality.Normal); - dxtCompressor.Compress(inputOptions, compressionOptions, outputOptions); + var outputOptions = new OutputOptions(); + outputOptions.SetOutputHeader(false); + + using (var handler = new DxtDataHandler(this, outputOptions)) + { + dxtCompressor.Compress(inputOptions, compressionOptions, outputOptions); + } } finally { diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/Font/GlyphPacker.cs b/MonoGame.Framework.Content.Pipeline/Graphics/Font/GlyphPacker.cs index 309de263eb6..26617de58a3 100644 --- a/MonoGame.Framework.Content.Pipeline/Graphics/Font/GlyphPacker.cs +++ b/MonoGame.Framework.Content.Pipeline/Graphics/Font/GlyphPacker.cs @@ -68,9 +68,6 @@ static BitmapContent CopyGlyphsToOutput(List glyphs, int width, i BitmapContent.Copy(sourceGlyph.Bitmap, sourceRegion, output, destinationRegion); - // TODO: This causes artifacts around borders. - //BitmapUtils.PadBorderPixels(output, destinationRegion); - sourceGlyph.Bitmap = output; sourceGlyph.Subrect = destinationRegion; } diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/FontHelper.cs b/MonoGame.Framework.Content.Pipeline/Graphics/FontHelper.cs deleted file mode 100644 index f4c230a3ea3..00000000000 --- a/MonoGame.Framework.Content.Pipeline/Graphics/FontHelper.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Runtime.InteropServices; -using System.Drawing; - -#if MACOS -using MonoMac.CoreGraphics; -using MonoMac.AppKit; -using MonoMac.Foundation; -using MonoMac.CoreText; -using MonoMac.ImageIO; -#endif - -namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics -{ - - static class FontHelper - { - [StructLayout(LayoutKind.Sequential)] - public struct ABC - { - public int abcA; - public uint abcB; - public int abcC; - } - -#if WINDOWS - [DllImport("gdi32.dll", ExactSpelling = true)] - public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObj); - - [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)] - public static extern int DeleteObject(IntPtr hObj); - - [DllImport("gdi32.dll", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] - public static extern bool GetCharABCWidthsW(IntPtr hdc, uint uFirstChar, uint uLastChar, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStruct, SizeConst = 1)] ABC[] lpabc); - - public static ABC GetCharWidthABC(char ch, Font font, System.Drawing.Graphics gr) - { - ABC[] _temp = new ABC[1]; - IntPtr hDC = gr.GetHdc(); - Font ft = (Font)font.Clone(); - IntPtr hFt = ft.ToHfont(); - SelectObject(hDC, hFt); - GetCharABCWidthsW(hDC, ch, ch, _temp); - DeleteObject(hFt); - gr.ReleaseHdc(); - - return _temp[0]; - } - -#elif MACOS - - static CTFont nativeFont; - - public static ABC GetCharWidthABC(char ch, Font font, System.Drawing.Graphics gr) - { - ABC[] _temp = new ABC[1]; - var nativFont = CreateFont (font.Name, font.Size, font.Style, font.GdiCharSet, font.GdiVerticalFont); - var atts = buildAttributedString(ch.ToString(), nativFont); - // for now just a line not sure if this is going to work - CTLine line = new CTLine(atts); - - float ascent; - float descent; - float leading; - _temp[0].abcB = (uint)line.GetTypographicBounds(out ascent, out descent, out leading); - - - return _temp[0]; - } - - const byte DefaultCharSet = 1; - static bool underLine = false; - static bool strikeThrough = false; - - static float dpiScale = 96f / 72f; - - - static internal CTFont CreateFont (string familyName, float emSize) - { - return CreateFont (familyName, emSize, FontStyle.Regular, DefaultCharSet, false); - } - - static internal CTFont CreateFont (string familyName, float emSize, FontStyle style) - { - return CreateFont (familyName, emSize, style, DefaultCharSet, false); - } - - static internal CTFont CreateFont (string familyName, float emSize, FontStyle style, byte gdiCharSet) - { - return CreateFont (familyName, emSize, style, gdiCharSet, false); - } - - static internal CTFont CreateFont (string familyName, float emSize, FontStyle style, - byte gdiCharSet, bool gdiVerticalFont ) - { - if (emSize <= 0) - throw new ArgumentException("emSize is less than or equal to 0, evaluates to infinity, or is not a valid number.","emSize"); - - CTFont nativeFont; - - // convert to 96 Dpi to be consistent with Windows - var dpiSize = emSize * dpiScale; - - try { - nativeFont = new CTFont(familyName,dpiSize); - } - catch - { - nativeFont = new CTFont("Helvetica",dpiSize); - } - - CTFontSymbolicTraits tMask = CTFontSymbolicTraits.None; - - if ((style & FontStyle.Bold) == FontStyle.Bold) - tMask |= CTFontSymbolicTraits.Bold; - if ((style & FontStyle.Italic) == FontStyle.Italic) - tMask |= CTFontSymbolicTraits.Italic; - strikeThrough = (style & FontStyle.Strikeout) == FontStyle.Strikeout; - underLine = (style & FontStyle.Underline) == FontStyle.Underline; - - var nativeFont2 = nativeFont.WithSymbolicTraits(dpiSize,tMask,tMask); - - if (nativeFont2 != null) - nativeFont = nativeFont2; - - return nativeFont; - } - - static NSString FontAttributedName = (NSString)"NSFont"; - static NSString ForegroundColorAttributedName = (NSString)"NSColor"; - static NSString UnderlineStyleAttributeName = (NSString)"NSUnderline"; - static NSString ParagraphStyleAttributeName = (NSString)"NSParagraphStyle"; - //NSAttributedString.ParagraphStyleAttributeName - static NSString StrikethroughStyleAttributeName = (NSString)"NSStrikethrough"; - - private static NSMutableAttributedString buildAttributedString(string text, CTFont font, - Color? fontColor=null) - { - - - // Create a new attributed string from text - NSMutableAttributedString atts = - new NSMutableAttributedString(text); - - var attRange = new NSRange(0, atts.Length); - var attsDic = new NSMutableDictionary(); - - // Font attribute - NSObject fontObject = new NSObject(font.Handle); - attsDic.Add(FontAttributedName, fontObject); - // -- end font - - if (fontColor.HasValue) { - - // Font color - var fc = fontColor.Value; - NSColor color = NSColor.FromDeviceRgba(fc.R / 255f, - fc.G / 255f, - fc.B / 255f, - fc.A / 255f); - NSObject colorObject = new NSObject(color.Handle); - attsDic.Add(ForegroundColorAttributedName, colorObject); - // -- end font Color - } - - if (underLine) { - // Underline - int single = (int)MonoMac.AppKit.NSUnderlineStyle.Single; - int solid = (int)MonoMac.AppKit.NSUnderlinePattern.Solid; - var attss = single | solid; - var underlineObject = NSNumber.FromInt32(attss); - //var under = NSAttributedString.UnderlineStyleAttributeName.ToString(); - attsDic.Add(UnderlineStyleAttributeName, underlineObject); - // --- end underline - } - - - if (strikeThrough) { - // StrikeThrough - // NSColor bcolor = NSColor.Blue; - // NSObject bcolorObject = new NSObject(bcolor.Handle); - // attsDic.Add(NSAttributedString.StrikethroughColorAttributeName, bcolorObject); - int stsingle = (int)MonoMac.AppKit.NSUnderlineStyle.Single; - int stsolid = (int)MonoMac.AppKit.NSUnderlinePattern.Solid; - var stattss = stsingle | stsolid; - var stunderlineObject = NSNumber.FromInt32(stattss); - - attsDic.Add(StrikethroughStyleAttributeName, stunderlineObject); - // --- end underline - } - - - // Text alignment - var alignment = CTTextAlignment.Left; - var alignmentSettings = new CTParagraphStyleSettings(); - alignmentSettings.Alignment = alignment; - var paragraphStyle = new CTParagraphStyle(alignmentSettings); - NSObject psObject = new NSObject(paragraphStyle.Handle); - - // end text alignment - - attsDic.Add(ParagraphStyleAttributeName, psObject); - - atts.SetAttributes(attsDic, attRange); - - return atts; - - } - -#elif LINUX - public static ABC GetCharWidthABC(char ch, Font font, System.Drawing.Graphics gr) - { - var sf = StringFormat.GenericTypographic; - sf.Trimming = StringTrimming.None; - sf.FormatFlags = StringFormatFlags.MeasureTrailingSpaces; - return new ABC - { - abcA = 0, - abcB = (uint)gr.MeasureString(ch.ToString(), font, new PointF(0, 0), sf).Width, - abcC = 0 - }; - } -#endif - } -} diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/GraphicsUtil.cs b/MonoGame.Framework.Content.Pipeline/Graphics/GraphicsUtil.cs index 0efe0bea41f..0d3a2c66f10 100644 --- a/MonoGame.Framework.Content.Pipeline/Graphics/GraphicsUtil.cs +++ b/MonoGame.Framework.Content.Pipeline/Graphics/GraphicsUtil.cs @@ -3,9 +3,7 @@ // file 'LICENSE.txt', which is part of this source code package. using System; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Microsoft.Xna.Framework.Content.Pipeline.Processors; +using System.IO; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics.PackedVector; using FreeImageAPI; @@ -81,69 +79,6 @@ public static int GetNextPowerOfTwo(int value) return nearestPower; } - /// - /// Returns true if the format is a compressed format. - /// - /// The texture processor output format. - /// True if the format is a compressed format. - public static bool IsCompressedTextureFormat(TextureProcessorOutputFormat format) - { - switch (format) - { - case TextureProcessorOutputFormat.AtcCompressed: - case TextureProcessorOutputFormat.DxtCompressed: - case TextureProcessorOutputFormat.Etc1Compressed: - case TextureProcessorOutputFormat.PvrCompressed: - return true; - } - return false; - } - - /// - /// Determines if the texture format requires power-of-two dimensions on the target platform. - /// - /// The texture format. - /// The target platform. - /// The targeted graphics profile. - /// True if the texture format requires power-of-two dimensions on the target platform. - public static bool RequiresPowerOfTwo(TextureProcessorOutputFormat format, TargetPlatform platform, GraphicsProfile profile) - { - if (format == TextureProcessorOutputFormat.Compressed) - format = GetTextureFormatForPlatform(format, platform); - - switch (format) - { - case TextureProcessorOutputFormat.DxtCompressed: - return profile == GraphicsProfile.Reach; - - case TextureProcessorOutputFormat.PvrCompressed: - case TextureProcessorOutputFormat.Etc1Compressed: - return true; - } - - return false; - } - - /// - /// Determines if the given texture format requires equal width and height on the target platform. - /// - /// The texture format. - /// The target platform. - /// True if the texture format requires equal width and height on the target platform. - public static bool RequiresSquare(TextureProcessorOutputFormat format, TargetPlatform platform) - { - if (format == TextureProcessorOutputFormat.Compressed) - format = GetTextureFormatForPlatform(format, platform); - - switch (format) - { - case TextureProcessorOutputFormat.PvrCompressed: - return true; - } - - return false; - } - enum AlphaRange { /// @@ -188,95 +123,15 @@ static AlphaRange CalculateAlphaRange(BitmapContent bitmap) return result; } - /// - /// If format is TextureProcessorOutputFormat.Compressed, the appropriate compressed texture format for the target - /// platform is returned. Otherwise the format is returned unchanged. - /// - /// The supplied texture format. - /// The target platform. - /// The texture format. - public static TextureProcessorOutputFormat GetTextureFormatForPlatform(TextureProcessorOutputFormat format, TargetPlatform platform) + public static void CompressPvrtc(TextureContent content, bool generateMipMaps, bool isSpriteFont) { - // Select the default texture compression format for the target platform - if (format == TextureProcessorOutputFormat.Compressed) - { - switch (platform) - { - case TargetPlatform.iOS: - format = TextureProcessorOutputFormat.PvrCompressed; - break; - - case TargetPlatform.Android: - format = TextureProcessorOutputFormat.Etc1Compressed; - break; - - default: - format = TextureProcessorOutputFormat.DxtCompressed; - break; - } - } - - if (IsCompressedTextureFormat(format)) + // If sharp alpha is required (for a font texture page), use 16-bit color instead of PVR + if (isSpriteFont) { - // Make sure the target platform supports the selected texture compression format - switch (platform) - { - case TargetPlatform.iOS: - if (format != TextureProcessorOutputFormat.PvrCompressed) - throw new PlatformNotSupportedException("iOS platform only supports PVR texture compression"); - break; - - case TargetPlatform.Windows: - case TargetPlatform.WindowsPhone8: - case TargetPlatform.WindowsStoreApp: - case TargetPlatform.DesktopGL: - case TargetPlatform.MacOSX: - case TargetPlatform.NativeClient: - if (format != TextureProcessorOutputFormat.DxtCompressed) - throw new PlatformNotSupportedException(platform.ToString() + " platform only supports DXT texture compression"); - break; - } + CompressColor16Bit(content, generateMipMaps); + return; } - return format; - } - - /// - /// Compresses TextureContent in a format appropriate to the platform - /// - public static void CompressTexture(GraphicsProfile profile, TextureContent content, TextureProcessorOutputFormat format, ContentProcessorContext context, bool generateMipmaps, bool sharpAlpha) - { - format = GetTextureFormatForPlatform(format, context.TargetPlatform); - - // Make sure we're in a floating point format - content.ConvertBitmapType(typeof(PixelBitmapContent)); - - switch (format) - { - case TextureProcessorOutputFormat.AtcCompressed: - CompressAti(content, generateMipmaps); - break; - - case TextureProcessorOutputFormat.Color16Bit: - CompressColor16Bit(content, generateMipmaps); - break; - - case TextureProcessorOutputFormat.DxtCompressed: - CompressDxt(profile, content, generateMipmaps, sharpAlpha); - break; - - case TextureProcessorOutputFormat.Etc1Compressed: - CompressEtc1(content, generateMipmaps); - break; - - case TextureProcessorOutputFormat.PvrCompressed: - CompressPvrtc(content, generateMipmaps); - break; - } - } - - private static void CompressPvrtc(TextureContent content, bool generateMipMaps) - { // Calculate number of mip levels var width = content.Faces[0][0].Height; var height = content.Faces[0][0].Width; @@ -297,7 +152,7 @@ private static void CompressPvrtc(TextureContent content, bool generateMipMaps) Compress(typeof(PvrtcRgba4BitmapContent), content, generateMipMaps); } - private static void CompressDxt(GraphicsProfile profile, TextureContent content, bool generateMipMaps, bool sharpAlpha) + public static void CompressDxt(GraphicsProfile profile, TextureContent content, bool generateMipMaps, bool isSpriteFont) { var face = content.Faces[0][0]; @@ -310,15 +165,17 @@ private static void CompressDxt(GraphicsProfile profile, TextureContent content, // Test the alpha channel to figure out if we have alpha. var alphaRange = CalculateAlphaRange(face); - if (alphaRange == AlphaRange.Opaque) + if (isSpriteFont) + CompressFontDXT3(content, generateMipMaps); + else if (alphaRange == AlphaRange.Opaque) Compress(typeof(Dxt1BitmapContent), content, generateMipMaps); - else if (alphaRange == AlphaRange.Cutout || sharpAlpha) + else if (alphaRange == AlphaRange.Cutout) Compress(typeof(Dxt3BitmapContent), content, generateMipMaps); else Compress(typeof(Dxt5BitmapContent), content, generateMipMaps); } - - static void CompressAti(TextureContent content, bool generateMipMaps) + + static public void CompressAti(TextureContent content, bool generateMipMaps) { var face = content.Faces[0][0]; var alphaRange = CalculateAlphaRange(face); @@ -329,7 +186,7 @@ static void CompressAti(TextureContent content, bool generateMipMaps) Compress(typeof(AtcInterpolatedBitmapContent), content, generateMipMaps); } - static void CompressEtc1(TextureContent content, bool generateMipMaps) + static public void CompressEtc1(TextureContent content, bool generateMipMaps) { var face = content.Faces[0][0]; var alphaRange = CalculateAlphaRange(face); @@ -348,7 +205,7 @@ static void CompressEtc1(TextureContent content, bool generateMipMaps) } } - static void CompressColor16Bit(TextureContent content, bool generateMipMaps) + static public void CompressColor16Bit(TextureContent content, bool generateMipMaps) { var face = content.Faces[0][0]; var alphaRange = CalculateAlphaRange(face); @@ -416,5 +273,241 @@ static void Compress(Type targetType, TextureContent content, bool generateMipMa content.ConvertBitmapType(targetType); } } + + // Compress the greyscale font texture page using a specially-formulated DXT3 mode + static public unsafe void CompressFontDXT3(TextureContent content, bool generateMipmaps) + { + if (content.Faces.Count > 1) + throw new PipelineException("Font textures should only have one face"); + + var block = new Vector4[16]; + for (int i = 0; i < content.Faces[0].Count; ++i) + { + var face = content.Faces[0][i]; + var xBlocks = (face.Width + 3) / 4; + var yBlocks = (face.Height + 3) / 4; + var dxt3Size = xBlocks * yBlocks * 16; + var buffer = new byte[dxt3Size]; + + var bytes = face.GetPixelData(); + fixed (byte* b = bytes) + { + Vector4* colors = (Vector4*)b; + + int w = 0; + int h = 0; + int x = 0; + int y = 0; + while (h < (face.Height & ~3)) + { + w = 0; + x = 0; + + var h0 = h * face.Width; + var h1 = h0 + face.Width; + var h2 = h1 + face.Width; + var h3 = h2 + face.Width; + + while (w < (face.Width & ~3)) + { + block[0] = colors[w + h0]; + block[1] = colors[w + h0 + 1]; + block[2] = colors[w + h0 + 2]; + block[3] = colors[w + h0 + 3]; + block[4] = colors[w + h1]; + block[5] = colors[w + h1 + 1]; + block[6] = colors[w + h1 + 2]; + block[7] = colors[w + h1 + 3]; + block[8] = colors[w + h2]; + block[9] = colors[w + h2 + 1]; + block[10] = colors[w + h2 + 2]; + block[11] = colors[w + h2 + 3]; + block[12] = colors[w + h3]; + block[13] = colors[w + h3 + 1]; + block[14] = colors[w + h3 + 2]; + block[15] = colors[w + h3 + 3]; + + int offset = (x + y * xBlocks) * 16; + CompressFontDXT3Block(block, buffer, offset); + + w += 4; + ++x; + } + + // Do partial block at end of row + if (w < face.Width) + { + var cols = face.Width - w; + Array.Clear(block, 0, 16); + for (int r = 0; r < 4; ++r) + { + h0 = (h + r) * face.Width; + for (int c = 0; c < cols; ++c) + block[(r * 4) + c] = colors[w + h0 + c]; + } + + int offset = (x + y * xBlocks) * 16; + CompressFontDXT3Block(block, buffer, offset); + } + + h += 4; + ++y; + } + + // Do last partial row + if (h < face.Height) + { + var rows = face.Height - h; + w = 0; + x = 0; + while (w < (face.Width & ~3)) + { + Array.Clear(block, 0, 16); + for (int r = 0; r < rows; ++r) + { + var h0 = (h + r) * face.Width; + block[(r * 4) + 0] = colors[w + h0 + 0]; + block[(r * 4) + 1] = colors[w + h0 + 1]; + block[(r * 4) + 2] = colors[w + h0 + 2]; + block[(r * 4) + 3] = colors[w + h0 + 3]; + } + + int offset = (x + y * xBlocks) * 16; + CompressFontDXT3Block(block, buffer, offset); + + w += 4; + ++x; + } + + // Do last partial block + if (w < face.Width) + { + var cols = face.Width - w; + Array.Clear(block, 0, 16); + for (int r = 0; r < rows; ++r) + { + var h0 = (h + r) * face.Width; + for (int c = 0; c < cols; ++c) + block[(r * 4) + c] = colors[w + h0 + c]; + } + + int offset = (x + y * xBlocks) * 16; + CompressFontDXT3Block(block, buffer, offset); + } + } + } + + var dxt3 = new Dxt3BitmapContent(face.Width, face.Height); + dxt3.SetPixelData(buffer); + content.Faces[0][i] = dxt3; + } + } + + // Maps a 2-bit greyscale to the index required for DXT3 + // 00 = color0 + // 01 = color1 + // 10 = 2/3 * color0 + 1/3 * color1 + // 11 = 1/3 * color0 + 2/3 * color1 + static byte[] dxt3Map = new byte[] { 0, 2, 3, 1 }; + + // Compress a single 4x4 block from colors into buffer at the given offset + static void CompressFontDXT3Block(Vector4[] colors, byte[] buffer, int offset) + { + // Get the alpha into a 0-15 range + int a0 = (int)(colors[0].W * 15.0); + int a1 = (int)(colors[1].W * 15.0); + int a2 = (int)(colors[2].W * 15.0); + int a3 = (int)(colors[3].W * 15.0); + int a4 = (int)(colors[4].W * 15.0); + int a5 = (int)(colors[5].W * 15.0); + int a6 = (int)(colors[6].W * 15.0); + int a7 = (int)(colors[7].W * 15.0); + int a8 = (int)(colors[8].W * 15.0); + int a9 = (int)(colors[9].W * 15.0); + int a10 = (int)(colors[10].W * 15.0); + int a11 = (int)(colors[11].W * 15.0); + int a12 = (int)(colors[12].W * 15.0); + int a13 = (int)(colors[13].W * 15.0); + int a14 = (int)(colors[14].W * 15.0); + int a15 = (int)(colors[15].W * 15.0); + + // Duplicate the top two bits into the bottom two bits so we get one of four values: b0000, b0101, b1010, b1111 + a0 = (a0 & 0xC) | (a0 >> 2); + a1 = (a1 & 0xC) | (a1 >> 2); + a2 = (a2 & 0xC) | (a2 >> 2); + a3 = (a3 & 0xC) | (a3 >> 2); + a4 = (a4 & 0xC) | (a4 >> 2); + a5 = (a5 & 0xC) | (a5 >> 2); + a6 = (a6 & 0xC) | (a6 >> 2); + a7 = (a7 & 0xC) | (a7 >> 2); + a8 = (a8 & 0xC) | (a8 >> 2); + a9 = (a9 & 0xC) | (a9 >> 2); + a10 = (a10 & 0xC) | (a10 >> 2); + a11 = (a11 & 0xC) | (a11 >> 2); + a12 = (a12 & 0xC) | (a12 >> 2); + a13 = (a13 & 0xC) | (a13 >> 2); + a14 = (a14 & 0xC) | (a14 >> 2); + a15 = (a15 & 0xC) | (a15 >> 2); + + // 4-bit alpha + buffer[offset + 0] = (byte)((a1 << 4) | a0); + buffer[offset + 1] = (byte)((a3 << 4) | a2); + buffer[offset + 2] = (byte)((a5 << 4) | a4); + buffer[offset + 3] = (byte)((a7 << 4) | a6); + buffer[offset + 4] = (byte)((a9 << 4) | a8); + buffer[offset + 5] = (byte)((a11 << 4) | a10); + buffer[offset + 6] = (byte)((a13 << 4) | a12); + buffer[offset + 7] = (byte)((a15 << 4) | a14); + + // color0 (transparent) + buffer[offset + 8] = 0; + buffer[offset + 9] = 0; + + // color1 (white) + buffer[offset + 10] = 255; + buffer[offset + 11] = 255; + + // Get the red (to be used for green and blue channels as well) into a 0-15 range + a0 = (int)(colors[0].X * 15.0); + a1 = (int)(colors[1].X * 15.0); + a2 = (int)(colors[2].X * 15.0); + a3 = (int)(colors[3].X * 15.0); + a4 = (int)(colors[4].X * 15.0); + a5 = (int)(colors[5].X * 15.0); + a6 = (int)(colors[6].X * 15.0); + a7 = (int)(colors[7].X * 15.0); + a8 = (int)(colors[8].X * 15.0); + a9 = (int)(colors[9].X * 15.0); + a10 = (int)(colors[10].X * 15.0); + a11 = (int)(colors[11].X * 15.0); + a12 = (int)(colors[12].X * 15.0); + a13 = (int)(colors[13].X * 15.0); + a14 = (int)(colors[14].X * 15.0); + a15 = (int)(colors[15].X * 15.0); + + // Duplicate the top two bits into the bottom two bits so we get one of four values: b0000, b0101, b1010, b1111 + a0 = (a0 & 0xC) | (a0 >> 2); + a1 = (a1 & 0xC) | (a1 >> 2); + a2 = (a2 & 0xC) | (a2 >> 2); + a3 = (a3 & 0xC) | (a3 >> 2); + a4 = (a4 & 0xC) | (a4 >> 2); + a5 = (a5 & 0xC) | (a5 >> 2); + a6 = (a6 & 0xC) | (a6 >> 2); + a7 = (a7 & 0xC) | (a7 >> 2); + a8 = (a8 & 0xC) | (a8 >> 2); + a9 = (a9 & 0xC) | (a9 >> 2); + a10 = (a10 & 0xC) | (a10 >> 2); + a11 = (a11 & 0xC) | (a11 >> 2); + a12 = (a12 & 0xC) | (a12 >> 2); + a13 = (a13 & 0xC) | (a13 >> 2); + a14 = (a14 & 0xC) | (a14 >> 2); + a15 = (a15 & 0xC) | (a15 >> 2); + + // Color indices (00 = color0, 01 = color1, 10 = 2/3 * color0 + 1/3 * color1, 11 = 1/3 * color0 + 2/3 * color1) + buffer[offset + 12] = (byte)((dxt3Map[a3 >> 2] << 6) | (dxt3Map[a2 >> 2] << 4) | (dxt3Map[a1 >> 2] << 2) | dxt3Map[a0 >> 2]); + buffer[offset + 13] = (byte)((dxt3Map[a7 >> 2] << 6) | (dxt3Map[a6 >> 2] << 4) | (dxt3Map[a5 >> 2] << 2) | dxt3Map[a4 >> 2]); + buffer[offset + 14] = (byte)((dxt3Map[a11 >> 2] << 6) | (dxt3Map[a10 >> 2] << 4) | (dxt3Map[a9 >> 2] << 2) | dxt3Map[a8 >> 2]); + buffer[offset + 15] = (byte)((dxt3Map[a15 >> 2] << 6) | (dxt3Map[a14 >> 2] << 4) | (dxt3Map[a13 >> 2] << 2) | dxt3Map[a12 >> 2]); + } } } diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/IndirectPositionCollection.cs b/MonoGame.Framework.Content.Pipeline/Graphics/IndirectPositionCollection.cs index 9fda9c6f376..63b96f4cf5f 100644 --- a/MonoGame.Framework.Content.Pipeline/Graphics/IndirectPositionCollection.cs +++ b/MonoGame.Framework.Content.Pipeline/Graphics/IndirectPositionCollection.cs @@ -18,7 +18,7 @@ namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics /// public sealed class IndirectPositionCollection : IList { - private readonly VertexChannel _positionIndicies; + private readonly VertexChannel _positionIndices; private readonly GeometryContent _geometry; /// @@ -27,7 +27,7 @@ public sealed class IndirectPositionCollection : IList /// Number of positions. public int Count { - get { return _positionIndicies.Count; } + get { return _positionIndices.Count; } } /// @@ -38,10 +38,9 @@ public Vector3 this[int index] { get { - var remap = _positionIndicies[index]; + var remap = _positionIndices[index]; return _geometry.Parent.Positions[remap]; } - set { throw Readonly(); @@ -60,10 +59,10 @@ bool ICollection.IsReadOnly /// /// Initializes a new instance of IndirectPositionCollection. /// - internal IndirectPositionCollection(GeometryContent geom, VertexChannel positionIndicies) + internal IndirectPositionCollection(GeometryContent geom, VertexChannel positionIndices) { _geometry = geom; - _positionIndicies = positionIndicies; + _positionIndices = positionIndices; } /// diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/MeshBuilder.cs b/MonoGame.Framework.Content.Pipeline/Graphics/MeshBuilder.cs new file mode 100644 index 00000000000..6513360435d --- /dev/null +++ b/MonoGame.Framework.Content.Pipeline/Graphics/MeshBuilder.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics +{ + public sealed class MeshBuilder + { + private readonly MeshContent _meshContent; + + private MaterialContent _currentMaterial; + private OpaqueDataDictionary _currentOpaqueData; + private bool _geometryDirty; + private GeometryContent _currentGeometryContent; + + private readonly List _vertexChannels; + private readonly List _vertexChannelData; + + private bool _finishedCreation; + private bool _finishedMesh; + + /// + /// Gets or sets the current value for position merging of the mesh. + /// + public bool MergeDuplicatePositions { get; set; } + + /// + /// Gets or sets the tolerance for . + /// + public float MergePositionTolerance { get; set; } + + /// + /// Gets or sets the name of the current object being processed. + /// + public string Name + { + get + { + return _meshContent.Name; + } + set + { + _meshContent.Name = value; + } + } + + /// + /// Reverses the triangle winding order of the specified mesh. + /// + public bool SwapWindingOrder { get; set; } + + + private MeshBuilder(string name) + { + _meshContent = new MeshContent(); + _vertexChannels = new List(); + _vertexChannelData = new List(); + _currentGeometryContent = new GeometryContent(); + _currentOpaqueData = new OpaqueDataDictionary(); + _geometryDirty = true; + Name = name; + } + + /// + /// Adds a vertex into the index collection. + /// + /// Index of the inserted vertex, in the collection. + /// This corresponds to the value returned by . + public void AddTriangleVertex(int indexIntoVertexCollection) + { + if (_finishedMesh) + throw new InvalidOperationException( "This MeshBuilder can no longer be used because FinishMesh has been called."); + + _finishedCreation = true; + + if (_geometryDirty) + { + _currentGeometryContent = new GeometryContent(); + _currentGeometryContent.Material = _currentMaterial; + foreach (var kvp in _currentOpaqueData) + _currentGeometryContent.OpaqueData.Add(kvp.Key, kvp.Value); + + // we have to copy our vertex channels to the new geometry + foreach (var channel in _vertexChannels) + { + _currentGeometryContent.Vertices.Channels.Add(channel.Name, channel.ElementType, null); + } + _meshContent.Geometry.Add(_currentGeometryContent); + _geometryDirty = false; + + } + // Add the vertex to the mesh and then add the vertex position to the indices list + var pos = _currentGeometryContent.Vertices.Add(indexIntoVertexCollection); + + // Then add the data for the other channels + for (var i = 0; i < _vertexChannels.Count; i++) + { + var channel = _currentGeometryContent.Vertices.Channels[i]; + var data = _vertexChannelData[i]; + if (data == null) + throw new InvalidOperationException(string.Format("Missing vertex channel data for channel {0}", channel.Name)); + + channel.Items.Add(data); + } + + _currentGeometryContent.Indices.Add(pos); + } + + public int CreateVertexChannel(string usage) + { + if (_finishedMesh) + throw new InvalidOperationException("This MeshBuilder can no longer be used because FinishMesh has been called."); + if (_finishedCreation) + throw new InvalidOperationException("Functions starting with 'Create' must be called before calling AddTriangleVertex"); + + var channel = new VertexChannel(usage); + _vertexChannels.Add(channel); + _vertexChannelData.Add(default(T)); + + _currentGeometryContent.Vertices.Channels.Add(usage, null); + + return _vertexChannels.Count - 1; + } + + /// + /// Inserts the specified vertex position into the vertex channel. + /// + /// Value of the x component of the vector. + /// Value of the y component of the vector. + /// Value of the z component of the vector. + /// Index of the inserted vertex. + public int CreatePosition(float x, float y, float z) + { + return CreatePosition(new Vector3(x, y, z)); + } + + /// + /// Inserts the specified vertex position into the vertex channel at the specified index. + /// + /// Value of the vertex being inserted. + /// Index of the vertex being inserted. + public int CreatePosition(Vector3 pos) + { + if (_finishedMesh) + throw new InvalidOperationException( "This MeshBuilder can no longer be used because FinishMesh has been called."); + if (_finishedCreation) + throw new InvalidOperationException("Functions starting with 'Create' must be called before calling AddTriangleVertex"); + + _meshContent.Positions.Add(pos); + return _meshContent.Positions.Count - 1; + } + + /// + /// Ends the creation of a mesh. + /// + /// Resultant mesh. + public MeshContent FinishMesh() + { + if (_finishedMesh) + return _meshContent; + + if (MergeDuplicatePositions) + MeshHelper.MergeDuplicatePositions(_meshContent, MergePositionTolerance); + + MeshHelper.MergeDuplicateVertices(_meshContent); + + MeshHelper.CalculateNormals(_meshContent, false); + if (SwapWindingOrder) + MeshHelper.SwapWindingOrder(_meshContent); + + _finishedMesh = true; + return _meshContent; + } + + /// + /// Sets the material for the next triangles. + /// + /// Material for the next triangles. + /// + /// Sets the material for the triangles being defined next. This material + /// and the opaque data dictionary, set with + /// define the object containing the next + /// triangles. When you set a new material or opaque data dictionary the + /// triangles you add afterwards will belong to a new + /// object. + /// + public void SetMaterial(MaterialContent material) + { + if (_currentMaterial == material) + return; + + _currentMaterial = material; + _geometryDirty = true; + } + + /// + /// Sets the opaque data for the next triangles. + /// + /// Opaque data dictionary for the next triangles. + /// + /// Sets the opaque data dictionary for the triangles being defined next. This dictionary + /// and the material, set with , define the + /// object containing the next triangles. When you set a new material or opaque data dictionary + /// the triangles you add afterwards will belong to a new object. + /// + public void SetOpaqueData(OpaqueDataDictionary opaqueData) + { + if (_currentOpaqueData == opaqueData) + return; + + _currentOpaqueData = opaqueData; + _geometryDirty = true; + } + + /// + /// Sets the specified vertex data with new data. + /// + /// Index of the vertex data channel being set. This should match the index returned by CreateVertexChannel. + /// New data values for the vertex data. The data type being set must match the data type for the vertex channel specified by vertexDataIndex. + public void SetVertexChannelData(int vertexDataIndex, object channelData) + { + if (_currentGeometryContent.Vertices.Channels[vertexDataIndex].ElementType != channelData.GetType()) + throw new InvalidOperationException(string.Format("Channel {0} data has a different type from input. Expected: {1}. Actual: {2}", + vertexDataIndex, _currentGeometryContent.Vertices.Channels[vertexDataIndex].ElementType, channelData.GetType())); + + _vertexChannelData[vertexDataIndex] = channelData; + } + + /// + /// Initializes the creation of a mesh. + /// + /// Name of the mesh. + /// Object used when building the mesh. + public static MeshBuilder StartMesh(string name) + { + return new MeshBuilder(name); + } + } +} diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/MeshHelper.cs b/MonoGame.Framework.Content.Pipeline/Graphics/MeshHelper.cs index a3851a4fcc2..42a021f619b 100644 --- a/MonoGame.Framework.Content.Pipeline/Graphics/MeshHelper.cs +++ b/MonoGame.Framework.Content.Pipeline/Graphics/MeshHelper.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; +using System.Linq; namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics { @@ -41,11 +43,12 @@ public static void CalculateNormals(MeshContent mesh, bool overwriteExistingNorm /// public static void CalculateNormals(GeometryContent geom, bool overwriteExistingNormals) { + VertexChannel channel; // Look for an existing normals channel. if (!geom.Vertices.Channels.Contains(VertexChannelNames.Normal())) { // We don't have existing normals, so add a new channel. - geom.Vertices.Channels.Add(VertexChannelNames.Normal(), null); + channel = geom.Vertices.Channels.Add(VertexChannelNames.Normal(), null); } else { @@ -53,9 +56,10 @@ public static void CalculateNormals(GeometryContent geom, bool overwriteExisting // normals then we're done here. if (!overwriteExistingNormals) return; + + channel = geom.Vertices.Channels.Get(VertexChannelNames.Normal()); } - var channel = geom.Vertices.Channels.Get(VertexChannelNames.Normal()); var positionIndices = geom.Vertices.PositionIndices; Debug.Assert(positionIndices.Count == channel.Count, "The position and channel sizes were different!"); @@ -123,7 +127,7 @@ public static void CalculateNormals(GeometryContent geom, bool overwriteExisting // Set the new normals on the vertex channel. for (var i = 0; i < channel.Count; i++) - channel[i] = normals[positionIndices[i]]; + channel[i] = normals[geom.Indices[i]]; } /// @@ -373,18 +377,122 @@ public static IList FlattenSkeleton(BoneContent skeleton) return results; } + /// + /// Merge any positions in the of the + /// specified mesh that are at a distance less than the specified tolerance + /// from each other. + /// + /// Mesh to be processed. + /// Tolerance value that determines how close + /// positions must be to each other to be merged. + /// + /// This method will also update the + /// in the of the specified mesh. + /// public static void MergeDuplicatePositions(MeshContent mesh, float tolerance) { - throw new NotImplementedException(); + if (mesh == null) + throw new ArgumentNullException("mesh"); + + // TODO Improve performance with spatial partitioning scheme + var indexLists = new List(); + foreach (var geom in mesh.Geometry) + { + var list = new IndexUpdateList(geom.Vertices.PositionIndices); + indexLists.Add(list); + } + + for (var i = mesh.Positions.Count - 1; i >= 1; i--) + { + var pi = mesh.Positions[i]; + for (var j = i - 1; j >= 0; j--) + { + var pj = mesh.Positions[j]; + if (Vector3.Distance(pi, pj) <= tolerance) + { + foreach (var list in indexLists) + list.Update(i, j); + mesh.Positions.RemoveAt(i); + } + } + } } + /// + /// Merge vertices with the same and + /// data within the specified + /// . + /// + /// Geometry to be processed. public static void MergeDuplicateVertices(GeometryContent geometry) { - throw new NotImplementedException(); + if (geometry == null) + throw new ArgumentNullException("geometry"); + + var verts = geometry.Vertices; + var hashMap = new Dictionary>(); + + var indices = new IndexUpdateList(geometry.Indices); + var vIndex = 0; + + for (var i = 0; i < geometry.Indices.Count; i++) + { + var iIndex = geometry.Indices[i]; + var iData = new VertexData + { + Index = iIndex, + PositionIndex = verts.PositionIndices[vIndex], + ChannelData = new object[verts.Channels.Count] + }; + + for (var channel = 0; channel < verts.Channels.Count; channel++) + iData.ChannelData[channel] = verts.Channels[channel][vIndex]; + + var hash = iData.ComputeHash(); + + var merged = false; + List candidates; + if (hashMap.TryGetValue(hash, out candidates)) + { + for (var candidateIndex = 0; candidateIndex < candidates.Count; candidateIndex++) + { + var c = candidates[candidateIndex]; + if (!iData.ContentEquals(c)) + continue; + + // Match! Update the corresponding indices and remove the vertex + indices.Update(iIndex, c.Index); + verts.RemoveAt(vIndex); + merged = true; + } + if (!merged) + candidates.Add(iData); + } + else + { + // no vertices with the same hash yet, create a new list for the data + hashMap.Add(hash, new List { iData }); + } + + if (!merged) + vIndex++; + } + + // update the indices because of the vertices we removed + indices.Pack(); } + /// + /// Merge vertices with the same and + /// data within the + /// of this mesh. If you want to merge positions too, call + /// on your mesh before this function. + /// + /// Mesh to be processed public static void MergeDuplicateVertices(MeshContent mesh) { + if (mesh == null) + throw new ArgumentNullException("mesh"); foreach (var geom in mesh.Geometry) MergeDuplicateVertices(geom); } @@ -436,7 +544,7 @@ public static void TransformScene(NodeContent scene, Matrix transform) if (transform == Matrix.Identity) return; - Matrix inverseTransform = Matrix.Invert(transform); + var inverseTransform = Matrix.Invert(transform); var work = new Stack(); work.Push(scene); @@ -483,5 +591,122 @@ internal static bool IsLeftHanded(ref Matrix xform) float d = Vector3.Dot(xform.Right, Vector3.Cross(xform.Forward, xform.Up)); return d < 0.0f; } + + #region Private helpers + + private static void UpdatePositionIndices(MeshContent mesh, int from, int to) + { + foreach (var geom in mesh.Geometry) + { + for (var i = 0; i < geom.Vertices.PositionIndices.Count; i++) + { + var index = geom.Vertices.PositionIndices[i]; + if (index == from) + geom.Vertices.PositionIndices[i] = to; + } + } + } + + private class VertexData + { + public int Index; + public int PositionIndex; + public object[] ChannelData; + + // Compute a hash based on PositionIndex and ChannelData + public int ComputeHash() + { + var hash = PositionIndex; + foreach (var channel in ChannelData) + hash ^= channel.GetHashCode(); + + return hash; + } + + // Check equality on PositionIndex and ChannelData + public bool ContentEquals(VertexData other) + { + if (PositionIndex != other.PositionIndex) + return false; + + if (ChannelData.Length != other.ChannelData.Length) + return false; + + for (var i = 0; i < ChannelData.Length; i++) + { + if (!Equals(ChannelData[i], other.ChannelData[i])) + return false; + } + + return true; + } + } + + // takes an IndexCollection and can efficiently update index values + private class IndexUpdateList + { + private readonly IList _collectionToUpdate; + private readonly Dictionary> _indexPositions; + + // create the list, presort the values and compute the start positions of each value + public IndexUpdateList(IList collectionToUpdate) + { + _collectionToUpdate = collectionToUpdate; + _indexPositions = new Dictionary>(); + Initialize(); + } + + private void Initialize() + { + for (var pos = 0; pos < _collectionToUpdate.Count; pos++) + { + var v = _collectionToUpdate[pos]; + if (_indexPositions.ContainsKey(v)) + _indexPositions[v].Add(pos); + else + _indexPositions.Add(v, new List {pos}); + } + } + + public void Update(int from, int to) + { + if (from == to || !_indexPositions.ContainsKey(from)) + return; + + foreach (var pos in _indexPositions[from]) + _collectionToUpdate[pos] = to; + + if (_indexPositions.ContainsKey(to)) + _indexPositions[to].AddRange(_indexPositions[from]); + else + _indexPositions.Add(to, _indexPositions[from]); + + _indexPositions.Remove(from); + } + + // Pack all indices together starting from zero + // E.g. [5, 5, 3, 5, 21, 3] -> [1, 1, 0, 1, 2, 0] + // note that the order must be kept + public void Pack() + { + if (_collectionToUpdate.Count == 0) + return; + + var sorted = new SortedSet(_collectionToUpdate); + + var newIndex = 0; + foreach (var value in sorted) + { + foreach (var pos in _indexPositions[value]) + _collectionToUpdate[pos] = newIndex; + + newIndex++; + } + + } + + } + + #endregion } } diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/NodeContent.cs b/MonoGame.Framework.Content.Pipeline/Graphics/NodeContent.cs index 9efd1a4ddc1..92e16d63283 100644 --- a/MonoGame.Framework.Content.Pipeline/Graphics/NodeContent.cs +++ b/MonoGame.Framework.Content.Pipeline/Graphics/NodeContent.cs @@ -88,6 +88,7 @@ public NodeContent() { children = new NodeContentCollection(this); animations = new AnimationContentDictionary(); + Transform = Matrix.Identity; } } } diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/TextureProfile.cs b/MonoGame.Framework.Content.Pipeline/Graphics/TextureProfile.cs new file mode 100644 index 00000000000..1b67fb65f3c --- /dev/null +++ b/MonoGame.Framework.Content.Pipeline/Graphics/TextureProfile.cs @@ -0,0 +1,99 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework.Content.Pipeline.Processors; + +namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics +{ + public abstract class TextureProfile + { + private static readonly LoadedTypeCollection _profiles = new LoadedTypeCollection(); + + /// + /// Find the profile for this target platform. + /// + /// The platform target for textures. + /// + public static TextureProfile ForPlatform(TargetPlatform platform) + { + var profile = _profiles.FirstOrDefault(h => h.Supports(platform)); + if (profile != null) + return profile; + + throw new PipelineException("There is no supported texture profile for the '" + platform + "' platform!"); + } + + /// + /// Returns true if this profile supports texture processing for this platform. + /// + public abstract bool Supports(TargetPlatform platform); + + /// + /// Determines if the texture format will require power-of-two dimensions and/or equal width and height. + /// + /// The processor context. + /// The desired texture format. + /// True if the texture format requires power-of-two dimensions. + /// True if the texture format requires equal width and height. + /// True if the texture format requires power-of-two dimensions. + public abstract void Requirements(ContentProcessorContext context, TextureProcessorOutputFormat format, out bool requiresPowerOfTwo, out bool requiresSquare); + + /// + /// Performs conversion of the texture content to the correct format. + /// + /// The processor context. + /// The content to be compressed. + /// The user requested format for compression. + /// If mipmap generation is required. + /// If the texture has represents a sprite font, i.e. is greyscale and has sharp black/white contrast. + public void ConvertTexture(ContentProcessorContext context, TextureContent content, TextureProcessorOutputFormat format, bool generateMipmaps, bool isSpriteFont) + { + // We do nothing in this case. + if (format == TextureProcessorOutputFormat.NoChange) + return; + + // If this is color just make sure the format is right and return it. + if (format == TextureProcessorOutputFormat.Color) + { + content.ConvertBitmapType(typeof(PixelBitmapContent)); + if (generateMipmaps) + content.GenerateMipmaps(false); + return; + } + + // Handle this common compression format. + if (format == TextureProcessorOutputFormat.Color16Bit) + { + GraphicsUtil.CompressColor16Bit(content, generateMipmaps); + return; + } + + try + { + // All other formats require platform specific choices. + PlatformCompressTexture(context, content, format, generateMipmaps, isSpriteFont); + } + catch (EntryPointNotFoundException ex) + { + context.Logger.LogImportantMessage("Could not find the entry point to compress the texture. " + ex.ToString()); + throw ex; + } + catch (DllNotFoundException ex) + { + context.Logger.LogImportantMessage("Could not compress texture. Required shared lib is missing. " + ex.ToString()); + throw ex; + } + catch (Exception ex) + { + context.Logger.LogImportantMessage("Could not convert texture. " + ex.ToString()); + throw ex; + } + } + + protected abstract void PlatformCompressTexture(ContentProcessorContext context, TextureContent content, TextureProcessorOutputFormat format, bool generateMipmaps, bool isSpriteFont); + } +} diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/VertexChannelCollection.cs b/MonoGame.Framework.Content.Pipeline/Graphics/VertexChannelCollection.cs index 68f48c6db4d..4aaaba22bea 100644 --- a/MonoGame.Framework.Content.Pipeline/Graphics/VertexChannelCollection.cs +++ b/MonoGame.Framework.Content.Pipeline/Graphics/VertexChannelCollection.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using System.Collections; +using System.Reflection; namespace Microsoft.Xna.Framework.Content.Pipeline.Graphics { @@ -51,14 +52,14 @@ public VertexChannel this[string name] { get { - int index = IndexOf(name); + var index = IndexOf(name); if (index < 0) throw new ArgumentException("name"); return channels[index]; } set { - int index = IndexOf(name); + var index = IndexOf(name); if (index < 0) throw new ArgumentException("name"); channels[index] = value; @@ -84,6 +85,7 @@ internal VertexChannelCollection(VertexContent vertexContent) { this.vertexContent = vertexContent; channels = new List(); + _insertOverload = GetType().GetMethods().First(m => m.Name == "Insert" && m.IsGenericMethodDefinition); } /// @@ -150,7 +152,7 @@ public VertexChannel ConvertChannelContent(int index) throw new ArgumentOutOfRangeException("index"); // Get the channel at that index - VertexChannel channel = this[index]; + var channel = this[index]; // Remove it because we cannot add a new channel with the same name RemoveAt(index); VertexChannel result = null; @@ -178,7 +180,7 @@ public VertexChannel ConvertChannelContent(int index) /// New channel in the specified format. public VertexChannel ConvertChannelContent(string name) { - int index = IndexOf(name); + var index = IndexOf(name); if (index < 0) throw new ArgumentException("name"); return ConvertChannelContent(index); @@ -194,7 +196,7 @@ public VertexChannel Get(int index) { if (index < 0 || index >= channels.Count) throw new ArgumentOutOfRangeException("index"); - VertexChannel channel = this[index]; + var channel = this[index]; // Make sure the channel type is as expected if (channel.ElementType != typeof(T)) throw new InvalidOperationException("Mismatched channel type"); @@ -209,7 +211,7 @@ public VertexChannel Get(int index) /// The vertex channel. public VertexChannel Get(string name) { - int index = IndexOf(name); + var index = IndexOf(name); if (index < 0) throw new ArgumentException("name"); return Get(index); @@ -265,7 +267,7 @@ public VertexChannel Insert(int index, string name, IE // Don't insert a channel with the same name if (IndexOf(name) >= 0) throw new ArgumentException("Vertex channel with name " + name + " already exists"); - VertexChannel channel = new VertexChannel(name); + var channel = new VertexChannel(name); if (channelData != null) { // Insert the values from the enumerable into the channel @@ -283,6 +285,9 @@ public VertexChannel Insert(int index, string name, IE return channel; } + // this reference the above Insert method and is initialized in the constructor + private readonly MethodInfo _insertOverload; + /// /// Inserts a new vertex channel at the specified position. /// @@ -294,7 +299,7 @@ public VertexChannel Insert(int index, string name, IE public VertexChannel Insert(int index, string name, Type elementType, IEnumerable channelData) { // Call the generic version of this method - return (VertexChannel)GetType().GetMethod("Insert").MakeGenericMethod(elementType).Invoke(this, new object[] { index, name, channelData }); + return (VertexChannel) _insertOverload.MakeGenericMethod(elementType).Invoke(this, new object[] { index, name, channelData }); } /// @@ -304,7 +309,7 @@ public VertexChannel Insert(int index, string name, Type elementType, IEnumerabl /// true if the channel was removed; false otherwise. public bool Remove(string name) { - int index = IndexOf(name); + var index = IndexOf(name); if (index >= 0) { channels.RemoveAt(index); diff --git a/MonoGame.Framework.Content.Pipeline/Graphics/VertexChannelNames.cs b/MonoGame.Framework.Content.Pipeline/Graphics/VertexChannelNames.cs index 79fe84fa75d..83f21a84048 100644 --- a/MonoGame.Framework.Content.Pipeline/Graphics/VertexChannelNames.cs +++ b/MonoGame.Framework.Content.Pipeline/Graphics/VertexChannelNames.cs @@ -76,8 +76,14 @@ public static int DecodeUsageIndex(string encodedName) string baseName = DecodeBaseName(encodedName); if (string.IsNullOrEmpty(baseName)) throw new InvalidOperationException("encodedName"); - // Subtract the base name from the string and convert the remainder to an integer - return Int32.Parse(encodedName.Substring(baseName.Length), CultureInfo.InvariantCulture); + + // Subtract the base name from the string and convert the remainder to an integer. + // TryParse solves the problem when name is just 'BlendIndicies' for example, in + // which case we default to index 0, assuming only 1 index. + int index = 0; + int.TryParse(encodedName.Substring(baseName.Length), NumberStyles.Integer, CultureInfo.InvariantCulture, out index); + + return index; } /// diff --git a/MonoGame.Framework.Content.Pipeline/LoadedTypeCollection.cs b/MonoGame.Framework.Content.Pipeline/LoadedTypeCollection.cs new file mode 100644 index 00000000000..c1ef063dcb0 --- /dev/null +++ b/MonoGame.Framework.Content.Pipeline/LoadedTypeCollection.cs @@ -0,0 +1,74 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + + +namespace Microsoft.Xna.Framework.Content.Pipeline +{ + /// + /// A helper for collecting instances of a particular type + /// by scanning the types in loaded assemblies. + /// + public class LoadedTypeCollection : IEnumerable + { + private static List _all; + + public LoadedTypeCollection() + { + // Scan the already loaded assemblies. + if (_all == null) + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var ass in assemblies) + ScanAssembly(ass); + } + + // Hook into assembly loading events to gather any new + // enumeration types that are found. + AppDomain.CurrentDomain.AssemblyLoad += (sender, args) => ScanAssembly(args.LoadedAssembly); + } + + private static void ScanAssembly(Assembly ass) + { + // Initialize the list on first use. + if (_all == null) + _all = new List(24); + + var thisAss = typeof(T).Assembly; + + // If the assembly doesn't reference our assembly then it + // cannot contain this type... so skip scanning it. + var refAss = ass.GetReferencedAssemblies(); + if (thisAss.FullName != ass.FullName && refAss.All(r => r.FullName != thisAss.FullName)) + return; + + var definedTypes = ass.DefinedTypes; + + foreach (var type in definedTypes) + { + if (!type.IsSubclassOf(typeof(T)) || type.IsAbstract) + continue; + + // Create an instance of the type and add it to our list. + var ttype = (T)Activator.CreateInstance(type); + _all.Add(ttype); + } + } + + public IEnumerator GetEnumerator() + { + return _all.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/MonoGame.Framework.Content.Pipeline/MonoGame.Content.Builder.targets b/MonoGame.Framework.Content.Pipeline/MonoGame.Content.Builder.targets index 9252a6b770f..e342b783e8d 100644 --- a/MonoGame.Framework.Content.Pipeline/MonoGame.Content.Builder.targets +++ b/MonoGame.Framework.Content.Pipeline/MonoGame.Content.Builder.targets @@ -47,7 +47,7 @@ - Resulting game asset. public override AudioContent Import(string filename, ContentImporterContext context) { + if (string.IsNullOrEmpty(filename)) + throw new ArgumentNullException("filename"); + if (context == null) + throw new ArgumentNullException("context"); + + if (!File.Exists(filename)) + throw new FileNotFoundException(string.Format("Could not locate audio file {0}.", Path.GetFileName(filename))); + var content = new AudioContent(filename, AudioFileType.Mp3); return content; } diff --git a/MonoGame.Framework.Content.Pipeline/OggImporter.cs b/MonoGame.Framework.Content.Pipeline/OggImporter.cs index d48ab1973f6..506caad12e3 100644 --- a/MonoGame.Framework.Content.Pipeline/OggImporter.cs +++ b/MonoGame.Framework.Content.Pipeline/OggImporter.cs @@ -3,6 +3,7 @@ // file 'LICENSE.txt', which is part of this source code package. using System; +using System.IO; using Microsoft.Xna.Framework.Content.Pipeline.Audio; namespace Microsoft.Xna.Framework.Content.Pipeline @@ -21,7 +22,15 @@ public class OggImporter : ContentImporter /// Resulting game asset. public override AudioContent Import(string filename, ContentImporterContext context) { - var content = new AudioContent(filename, AudioFileType.Wav); + if (string.IsNullOrEmpty(filename)) + throw new ArgumentNullException("filename"); + if (context == null) + throw new ArgumentNullException("context"); + + if (!File.Exists(filename)) + throw new FileNotFoundException(string.Format("Could not locate audio file {0}.", Path.GetFileName(filename))); + + var content = new AudioContent(filename, AudioFileType.Ogg); return content; } } diff --git a/MonoGame.Framework.Content.Pipeline/OpenAssetImporter.cs b/MonoGame.Framework.Content.Pipeline/OpenAssetImporter.cs index 9ec47e1848d..6f51756a1f3 100644 --- a/MonoGame.Framework.Content.Pipeline/OpenAssetImporter.cs +++ b/MonoGame.Framework.Content.Pipeline/OpenAssetImporter.cs @@ -14,7 +14,45 @@ namespace Microsoft.Xna.Framework.Content.Pipeline { - [ContentImporter(".dae", ".fbx", ".x", DisplayName = "Open Asset Import Library - MonoGame", DefaultProcessor = "ModelProcessor")] + [ContentImporter( + ".dae", // Collada + ".gltf", "glb", // glTF + ".blend", // Blender 3D + ".3ds", // 3ds Max 3DS + ".ase", // 3ds Max ASE + ".obj", // Wavefront Object + ".ifc", // Industry Foundation Classes (IFC/Step) + ".xgl", ".zgl", // XGL + ".ply", // Stanford Polygon Library + ".dxf", // AutoCAD DXF + ".lwo", // LightWave + ".lws", // LightWave Scene + ".lxo", // Modo + ".stl", // Stereolithography + ".ac", // AC3D + ".ms3d", // Milkshape 3D + ".cob", ".scn", // TrueSpace + ".bvh", // Biovision BVH + ".csm", // CharacterStudio Motion + ".irrmesh", // Irrlicht Mesh + ".irr", // Irrlicht Scene + ".mdl", // Quake I, 3D GameStudio (3DGS) + ".md2", // Quake II + ".md3", // Quake III Mesh + ".pk3", // Quake III Map/BSP + ".mdc", // Return to Castle Wolfenstein + ".md5", // Doom 3 + ".smd", ".vta", // Valve Model + ".ogex", // Open Game Engine Exchange + ".3d", // Unreal + ".b3d", // BlitzBasic 3D + ".q3d", ".q3s", // Quick3D + ".nff", // Neutral File Format, Sense8 WorldToolKit + ".off", // Object File Format + ".ter", // Terragen Terrain + ".hmp", // 3D GameStudio (3DGS) Terrain + ".ndo", // Izware Nendo + DisplayName = "Open Asset Import Library - MonoGame", DefaultProcessor = "ModelProcessor")] public class OpenAssetImporter : ContentImporter { // Assimp has a few limitations (not all FBX files are supported): @@ -442,6 +480,16 @@ private NodeContent ImportNodes(Node aiNode, Node aiParent, NodeContent parent) // For the children, this is the new parent. aiParent = aiNode; parent = node; + + if (_scene.HasAnimations) + { + foreach (var animation in _scene.Animations) + { + var animationContent = ImportAnimation(animation, node.Name); + if (animationContent.Channels.Count > 0) + node.Animations.Add(animationContent.Name, animationContent); + } + } } Debug.Assert(parent != null); @@ -739,8 +787,9 @@ private NodeContent ImportBones(Node aiNode, Node aiParent, NodeContent parent) /// Converts the specified animation to XNA. /// /// The animation. + /// An optional filter. /// The animation converted to XNA. - private AnimationContent ImportAnimation(Animation aiAnimation) + private AnimationContent ImportAnimation(Animation aiAnimation, string nodeName = null) { var animation = new AnimationContent { @@ -754,8 +803,18 @@ private AnimationContent ImportAnimation(Animation aiAnimation) // "nodeXyz_$AssimpFbx$_Rotation", // "nodeXyz_$AssimpFbx$_Scaling" // Group animation channels by name (strip the "_$AssimpFbx$" part). - var channelGroups = aiAnimation.NodeAnimationChannels + IEnumerable < IGrouping < string,NodeAnimationChannel >> channelGroups; + if (nodeName != null) + { + channelGroups = aiAnimation.NodeAnimationChannels + .Where(channel => nodeName == GetNodeName(channel.NodeName)) .GroupBy(channel => GetNodeName(channel.NodeName)); + } + else + { + channelGroups = aiAnimation.NodeAnimationChannels + .GroupBy(channel => GetNodeName(channel.NodeName)); + } foreach (var channelGroup in channelGroups) { diff --git a/MonoGame.Framework.Content.Pipeline/Processors/EffectProcessor.cs b/MonoGame.Framework.Content.Pipeline/Processors/EffectProcessor.cs index 695c5594ead..f04d9a55732 100644 --- a/MonoGame.Framework.Content.Pipeline/Processors/EffectProcessor.cs +++ b/MonoGame.Framework.Content.Pipeline/Processors/EffectProcessor.cs @@ -54,23 +54,9 @@ public override CompiledEffectContent Process(EffectContent input, ContentProces var options = new Options(); options.SourceFile = input.Identity.SourceFilename; - switch (context.TargetPlatform) - { - case TargetPlatform.Windows: - case TargetPlatform.WindowsPhone8: - case TargetPlatform.WindowsStoreApp: - options.Profile = ShaderProfile.DirectX_11; - break; - case TargetPlatform.iOS: - case TargetPlatform.Android: - case TargetPlatform.DesktopGL: - case TargetPlatform.MacOSX: - case TargetPlatform.RaspberryPi: - options.Profile = ShaderProfile.OpenGL; - break; - default: - throw new InvalidContentException(string.Format("{0} effects are not supported.", context.TargetPlatform), input.Identity); - } + options.Profile = ShaderProfile.ForPlatform(context.TargetPlatform.ToString()); + if (options.Profile == null) + throw new InvalidContentException(string.Format("{0} effects are not supported.", context.TargetPlatform), input.Identity); options.Debug = DebugMode == EffectProcessorDebugMode.Debug; options.Defines = Defines; @@ -110,7 +96,7 @@ public override CompiledEffectContent Process(EffectContent input, ContentProces foreach (var outfile in shaderInfo.AdditionalOutputFiles) context.AddOutputFile(outfile); } - catch (ShaderCompilerException ex) + catch (ShaderCompilerException) { // This will log any warnings and errors and throw. ProcessErrorsAndWarnings(true, shaderErrorsAndWarnings, input, context); @@ -214,7 +200,7 @@ private static void ProcessErrorsAndWarnings(bool buildFailed, string shaderErro if (identity == null) { identity = new ContentIdentity(fileName, input.Identity.SourceTool, lineAndColumn); - allErrorsAndWarnings = message + Environment.NewLine; + allErrorsAndWarnings = errorsAndWarningArray[i] + Environment.NewLine; } else allErrorsAndWarnings += errorsAndWarningArray[i] + Environment.NewLine; diff --git a/MonoGame.Framework.Content.Pipeline/Processors/FontDescriptionProcessor.cs b/MonoGame.Framework.Content.Pipeline/Processors/FontDescriptionProcessor.cs index 61612884c47..a3851589b8e 100644 --- a/MonoGame.Framework.Content.Pipeline/Processors/FontDescriptionProcessor.cs +++ b/MonoGame.Framework.Content.Pipeline/Processors/FontDescriptionProcessor.cs @@ -21,12 +21,16 @@ namespace Microsoft.Xna.Framework.Content.Pipeline.Processors [ContentProcessor(DisplayName = "Sprite Font Description - MonoGame")] public class FontDescriptionProcessor : ContentProcessor { + [DefaultValue(true)] + public virtual bool PremultiplyAlpha { get; set; } + [DefaultValue(typeof(TextureProcessorOutputFormat), "Compressed")] public virtual TextureProcessorOutputFormat TextureFormat { get; set; } public FontDescriptionProcessor() { - this.TextureFormat = TextureProcessorOutputFormat.Compressed; + PremultiplyAlpha = true; + TextureFormat = TextureProcessorOutputFormat.Compressed; } public override SpriteFontContent Process(FontDescription input, @@ -82,7 +86,11 @@ public override SpriteFontContent Process(FontDescription input, #endif context.Logger.LogMessage ("Building Font {0}", fontName); - try { + + // Get the platform specific texture profile. + var texProfile = TextureProfile.ForPlatform(context.TargetPlatform); + + { if (!File.Exists(fontName)) { throw new Exception(string.Format("Could not load {0}", fontName)); } @@ -96,11 +104,11 @@ public override SpriteFontContent Process(FontDescription input, GlyphCropper.Crop(glyph); } - var format = GraphicsUtil.GetTextureFormatForPlatform(TextureFormat, context.TargetPlatform); - var requiresPOT = GraphicsUtil.RequiresPowerOfTwo(format, context.TargetPlatform, context.TargetProfile); - var requiresSquare = GraphicsUtil.RequiresSquare(format, context.TargetPlatform); + // We need to know how to pack the glyphs. + bool requiresPot, requiresSquare; + texProfile.Requirements(context, TextureFormat, out requiresPot, out requiresSquare); - var face = GlyphPacker.ArrangeGlyphs(glyphs, requiresPOT, requiresSquare); + var face = GlyphPacker.ArrangeGlyphs(glyphs, requiresPot, requiresSquare); // Adjust line and character spacing. lineSpacing += input.Spacing; @@ -123,16 +131,52 @@ public override SpriteFontContent Process(FontDescription input, output.Kerning.Add(new Vector3(0, texRect.Width, 0)); } - output.Texture.Faces[0].Add(face); + output.Texture.Faces[0].Add(face); + } - if (GraphicsUtil.IsCompressedTextureFormat(format)) + if (PremultiplyAlpha) + { + var bmp = output.Texture.Faces[0][0]; + var data = bmp.GetPixelData(); + var idx = 0; + for (; idx < data.Length; ) { - GraphicsUtil.CompressTexture(context.TargetProfile, output.Texture, format, context, false, true); + var r = data[idx]; + + // Special case of simply copying the R component into the A, since R is the value of white alpha we want + data[idx + 0] = r; + data[idx + 1] = r; + data[idx + 2] = r; + data[idx + 3] = r; + + idx += 4; } - } - catch(Exception ex) { - context.Logger.LogImportantMessage("{0}", ex.ToString()); - } + + bmp.SetPixelData(data); + } + else + { + var bmp = output.Texture.Faces[0][0]; + var data = bmp.GetPixelData(); + var idx = 0; + for (; idx < data.Length; ) + { + var r = data[idx]; + + // Special case of simply moving the R component into the A and setting RGB to solid white, since R is the value of white alpha we want + data[idx + 0] = 255; + data[idx + 1] = 255; + data[idx + 2] = 255; + data[idx + 3] = r; + + idx += 4; + } + + bmp.SetPixelData(data); + } + + // Perform the final texture conversion. + texProfile.ConvertTexture(context, output.Texture, TextureFormat, false, true); return output; } @@ -220,6 +264,9 @@ string FindFontFileFromFontName(string fontname, string style) s = s.Trim(); var split = s.Split (':'); + if (split.Length < 2) + return String.Empty; + //check font family, fontconfig might return a fallback if (split [1].Contains (",")) { //this file defines multiple family names var families = split [1].Split (','); diff --git a/MonoGame.Framework.Content.Pipeline/Processors/FontTextureProcessor.cs b/MonoGame.Framework.Content.Pipeline/Processors/FontTextureProcessor.cs index 2619ed8a187..a2776c9f136 100644 --- a/MonoGame.Framework.Content.Pipeline/Processors/FontTextureProcessor.cs +++ b/MonoGame.Framework.Content.Pipeline/Processors/FontTextureProcessor.cs @@ -111,10 +111,14 @@ public override SpriteFontContent Process(Texture2DContent input, ContentProcess output.VerticalLineSpacing = Math.Max(output.VerticalLineSpacing, glyph.Subrect.Height); } - var format = GraphicsUtil.GetTextureFormatForPlatform(TextureFormat, context.TargetPlatform); - var requiresPOT = GraphicsUtil.RequiresPowerOfTwo(format, context.TargetPlatform, context.TargetProfile); - var requiresSquare = GraphicsUtil.RequiresSquare(format, context.TargetPlatform); - face = GlyphPacker.ArrangeGlyphs(glyphs.ToArray(), requiresPOT, requiresSquare); + // Get the platform specific texture profile. + var texProfile = TextureProfile.ForPlatform(context.TargetPlatform); + + // We need to know how to pack the glyphs. + bool requiresPot, requiresSquare; + texProfile.Requirements(context, TextureFormat, out requiresPot, out requiresSquare); + + face = GlyphPacker.ArrangeGlyphs(glyphs.ToArray(), requiresPot, requiresSquare); foreach (var glyph in glyphs) { @@ -151,17 +155,8 @@ public override SpriteFontContent Process(Texture2DContent input, ContentProcess bmp.SetPixelData(data); } - if (GraphicsUtil.IsCompressedTextureFormat(format)) - { - try - { - GraphicsUtil.CompressTexture(context.TargetProfile, output.Texture, format, context, false, true); - } - catch(Exception ex) - { - context.Logger.LogImportantMessage("{0}", ex.ToString()); - } - } + // Perform the final texture conversion. + texProfile.ConvertTexture(context, output.Texture, TextureFormat, false, true); return output; } diff --git a/MonoGame.Framework.Content.Pipeline/Processors/MaterialProcessor.cs b/MonoGame.Framework.Content.Pipeline/Processors/MaterialProcessor.cs index 1507a067c09..a5a6349d887 100644 --- a/MonoGame.Framework.Content.Pipeline/Processors/MaterialProcessor.cs +++ b/MonoGame.Framework.Content.Pipeline/Processors/MaterialProcessor.cs @@ -77,7 +77,7 @@ public class MaterialProcessor : ContentProcessor /// Specifies the texture format of output materials. Materials can either be left unchanged from the source asset, converted to a corresponding Color, or compressed using the appropriate DxtCompressed format. @@ -86,7 +86,7 @@ public class MaterialProcessor : ContentProcessor /// Initializes a new instance of the MaterialProcessor class. @@ -173,8 +173,10 @@ public override MaterialContent Process(MaterialContent input, ContentProcessorC // Build custom effects var effectMaterial = input as EffectMaterialContent; - if (effectMaterial != null) + if (effectMaterial != null && effectMaterial.CompiledEffect == null) { + if (effectMaterial.Effect == null) + throw new PipelineException("EffectMaterialContent.Effect or EffectMaterialContent.CompiledEffect should be set for materials with a custom effect."); effectMaterial.CompiledEffect = BuildEffect(effectMaterial.Effect, context); // TODO: Docs say to validate OpaqueData for SetValue/SetValueTranspose // Does that mean to match up with effect param names?? diff --git a/MonoGame.Framework.Content.Pipeline/Processors/ModelProcessor.cs b/MonoGame.Framework.Content.Pipeline/Processors/ModelProcessor.cs index 883ae0416e1..2ed7cf71c05 100644 --- a/MonoGame.Framework.Content.Pipeline/Processors/ModelProcessor.cs +++ b/MonoGame.Framework.Content.Pipeline/Processors/ModelProcessor.cs @@ -24,7 +24,7 @@ public class ModelProcessor : ContentProcessor private bool _premultiplyTextureAlpha = true; private bool _premultiplyVertexColors = true; private float _scale = 1.0f; - private TextureProcessorOutputFormat _textureFormat = TextureProcessorOutputFormat.DxtCompressed; + private TextureProcessorOutputFormat _textureFormat = TextureProcessorOutputFormat.Compressed; #endregion @@ -83,7 +83,7 @@ public virtual float Scale public virtual bool SwapWindingOrder { get; set; } - [DefaultValue(typeof(TextureProcessorOutputFormat), "DxtCompressed")] + [DefaultValue(typeof(TextureProcessorOutputFormat), "Compressed")] public virtual TextureProcessorOutputFormat TextureFormat { get { return _textureFormat; } diff --git a/MonoGame.Framework.Content.Pipeline/Processors/SongProcessor.cs b/MonoGame.Framework.Content.Pipeline/Processors/SongProcessor.cs index babbe5e9b8e..0e1ce4715eb 100644 --- a/MonoGame.Framework.Content.Pipeline/Processors/SongProcessor.cs +++ b/MonoGame.Framework.Content.Pipeline/Processors/SongProcessor.cs @@ -15,13 +15,17 @@ namespace Microsoft.Xna.Framework.Content.Pipeline.Processors [ContentProcessor(DisplayName = "Song - MonoGame")] public class SongProcessor : ContentProcessor { - ConversionQuality quality = ConversionQuality.Best; + ConversionQuality _quality = ConversionQuality.Best; /// /// Gets or sets the target format quality of the audio content. /// /// The ConversionQuality of this audio data. - public ConversionQuality Quality { get { return quality; } set { quality = value; } } + public ConversionQuality Quality + { + get { return _quality; } + set { _quality = value; } + } /// /// Initializes a new instance of SongProcessor. @@ -38,33 +42,17 @@ public SongProcessor() /// The built audio. public override SongContent Process(AudioContent input, ContentProcessorContext context) { - // Most platforms will use AAC ("mp4") by default - var targetFormat = ConversionFormat.Aac; - - switch (context.TargetPlatform) - { - case TargetPlatform.Windows: - case TargetPlatform.WindowsPhone8: - case TargetPlatform.WindowsStoreApp: - targetFormat = ConversionFormat.WindowsMedia; - break; - - case TargetPlatform.DesktopGL: - targetFormat = ConversionFormat.Vorbis; - break; - } - - // Get the song output path with the target format extension. - var songFileName = Path.ChangeExtension(context.OutputFilename, AudioHelper.GetExtension(targetFormat)); - - // Make sure the output folder for the song exists. - Directory.CreateDirectory(Path.GetDirectoryName(songFileName)); + // The xnb name is the basis for the final song filename. + var songFileName = context.OutputFilename; // Convert and write out the song media file. - input.ConvertFormat(targetFormat, quality, songFileName); + var profile = AudioProfile.ForPlatform(context.TargetPlatform); + var finalQuality = profile.ConvertStreamingAudio(context.TargetPlatform, _quality, input, ref songFileName); // Let the pipeline know about the song file so it can clean things up. context.AddOutputFile(songFileName); + if (_quality != finalQuality) + context.Logger.LogMessage("Failed to convert using \"{0}\" quality, used \"{1}\" quality", _quality, finalQuality); // Return the XNB song content. return new SongContent(PathHelper.GetRelativePath(Path.GetDirectoryName(context.OutputFilename) + Path.DirectorySeparatorChar, songFileName), input.Duration); diff --git a/MonoGame.Framework.Content.Pipeline/Processors/SoundEffectContent.cs b/MonoGame.Framework.Content.Pipeline/Processors/SoundEffectContent.cs index 254cb0144e3..bd52d890b89 100644 --- a/MonoGame.Framework.Content.Pipeline/Processors/SoundEffectContent.cs +++ b/MonoGame.Framework.Content.Pipeline/Processors/SoundEffectContent.cs @@ -5,6 +5,7 @@ using System; using System.Collections.ObjectModel; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Xna.Framework.Content.Pipeline.Processors { @@ -13,8 +14,8 @@ namespace Microsoft.Xna.Framework.Content.Pipeline.Processors /// public sealed class SoundEffectContent { - internal List format; - internal List data; + internal byte[] format; + internal byte[] data; internal int loopStart; internal int loopLength; internal int duration; @@ -27,10 +28,10 @@ public sealed class SoundEffectContent /// The start of the loop segment (must be block aligned). /// The length of the loop segment (must be block aligned). /// The duration of the wave file in milliseconds. - internal SoundEffectContent(ReadOnlyCollection format, ReadOnlyCollection data, int loopStart, int loopLength, int duration) + internal SoundEffectContent(IEnumerable format, IEnumerable data, int loopStart, int loopLength, int duration) { - this.format = new List(format); - this.data = new List(data); + this.format = format.ToArray(); + this.data = data.ToArray(); this.loopStart = loopStart; this.loopLength = loopLength; this.duration = duration; diff --git a/MonoGame.Framework.Content.Pipeline/Processors/SoundEffectProcessor.cs b/MonoGame.Framework.Content.Pipeline/Processors/SoundEffectProcessor.cs index 335410b9d1a..a2180da6829 100644 --- a/MonoGame.Framework.Content.Pipeline/Processors/SoundEffectProcessor.cs +++ b/MonoGame.Framework.Content.Pipeline/Processors/SoundEffectProcessor.cs @@ -38,25 +38,15 @@ public SoundEffectProcessor() /// The built audio. public override SoundEffectContent Process(AudioContent input, ContentProcessorContext context) { - var targetFormat = ConversionFormat.Pcm; - - switch (quality) - { - case ConversionQuality.Medium: - case ConversionQuality.Low: - if ((context.TargetPlatform == TargetPlatform.iOS) || (context.TargetPlatform == TargetPlatform.MacOSX)) - targetFormat = ConversionFormat.ImaAdpcm; - else - { - // TODO: For some reason this doesn't work on Windows - // so we fallback to plain PCM and depend on the - // bitrate reduction only. - //targetFormat = ConversionFormat.Adpcm; - } - break; - } - - input.ConvertFormat(targetFormat, quality, null); + if (input == null) + throw new ArgumentNullException("input"); + if (context == null) + throw new ArgumentNullException("context"); + + var profile = AudioProfile.ForPlatform(context.TargetPlatform); + var finalQuality = profile.ConvertAudio(context.TargetPlatform, quality, input); + if (quality != finalQuality) + context.Logger.LogMessage("Failed to convert using \"{0}\" quality, used \"{1}\" quality", quality, finalQuality); return new SoundEffectContent(input.Format.NativeWaveFormat, input.Data, input.LoopStart, input.LoopLength, (int)input.Duration.TotalMilliseconds); } diff --git a/MonoGame.Framework.Content.Pipeline/Processors/TextureProcessor.cs b/MonoGame.Framework.Content.Pipeline/Processors/TextureProcessor.cs index 65a3775a1a6..f172978ba48 100644 --- a/MonoGame.Framework.Content.Pipeline/Processors/TextureProcessor.cs +++ b/MonoGame.Framework.Content.Pipeline/Processors/TextureProcessor.cs @@ -111,38 +111,9 @@ public override TextureContent Process(TextureContent input, ContentProcessorCon input.ConvertBitmapType(originalType); } - if (TextureFormat == TextureProcessorOutputFormat.NoChange) - return input; - - try - { - if (TextureFormat != TextureProcessorOutputFormat.Color) - { - input.ConvertBitmapType(typeof(PixelBitmapContent)); - GraphicsUtil.CompressTexture(context.TargetProfile, input, TextureFormat, context, GenerateMipmaps, false); - } - else - { - input.ConvertBitmapType(typeof(PixelBitmapContent)); - if (GenerateMipmaps) - input.GenerateMipmaps(false); - } - } - catch (EntryPointNotFoundException ex) - { - context.Logger.LogImportantMessage ("Could not find the entry point to compress the texture. " + ex.ToString()); - throw ex; - } - catch (DllNotFoundException ex) - { - context.Logger.LogImportantMessage ("Could not compress texture. Required shared lib is missing. " + ex.ToString()); - throw ex; - } - catch (Exception ex) - { - context.Logger.LogImportantMessage ("Could not convert texture. " + ex.ToString()); - throw ex; - } + // Get the texture profile for the platform and let it convert the texture. + var texProfile = TextureProfile.ForPlatform(context.TargetPlatform); + texProfile.ConvertTexture(context, input, TextureFormat, GenerateMipmaps, false); return input; } diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ContentWriter.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ContentWriter.cs index 678e12518a8..8d431fba41e 100644 --- a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ContentWriter.cs +++ b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/ContentWriter.cs @@ -43,7 +43,6 @@ public sealed class ContentWriter : BinaryWriter { 'w', // Windows (DirectX) 'x', // Xbox360 - 'm', // WindowsPhone 'i', // iOS 'a', // Android 'd', // DesktopGL @@ -54,6 +53,8 @@ public sealed class ContentWriter : BinaryWriter 'M', // WindowsPhone8 'r', // RaspberryPi 'P', // PlayStation4 + 'v', // PSVita + 'O', // XboxOne }; /// diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/EffectMaterialWriter.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/EffectMaterialWriter.cs new file mode 100644 index 00000000000..79da149ef6c --- /dev/null +++ b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/EffectMaterialWriter.cs @@ -0,0 +1,29 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System.Collections.Generic; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; + +namespace Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler +{ + [ContentTypeWriter] + class EffectMaterialWriter : BuiltInContentWriter + { + protected internal override void Write(ContentWriter output, EffectMaterialContent value) + { + output.WriteExternalReference(value.CompiledEffect); + var dict = new Dictionary(); + foreach (var item in value.Textures) + { + dict.Add(item.Key, item.Value); + } + foreach (var item in value.OpaqueData) + { + if (item.Key != EffectMaterialContent.EffectKey && item.Key != EffectMaterialContent.CompiledEffectKey) + dict.Add(item.Key, item.Value); + } + output.WriteObject(dict); + } + } +} \ No newline at end of file diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/IndexBufferWriter.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/IndexBufferWriter.cs index 47ff00d499b..cf09f372060 100644 --- a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/IndexBufferWriter.cs +++ b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/IndexBufferWriter.cs @@ -11,7 +11,17 @@ class IndexBufferWriter : BuiltInContentWriter { protected internal override void Write(ContentWriter output, IndexCollection value) { - var shortIndices = value.Count < ushort.MaxValue; + // Check if the buffer and can be saved as Int16. + var shortIndices = true; + foreach(var index in value) + { + if(index > ushort.MaxValue) + { + shortIndices = false; + break; + } + } + output.Write(shortIndices); var byteCount = shortIndices diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/SoundEffectContentWriter.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/SoundEffectContentWriter.cs index 8e29031d338..9348df3aae7 100644 --- a/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/SoundEffectContentWriter.cs +++ b/MonoGame.Framework.Content.Pipeline/Serialization/Compiler/SoundEffectContentWriter.cs @@ -17,11 +17,11 @@ class SoundEffectWriter : BuiltInContentWriter /// The value to write to the output. protected internal override void Write(ContentWriter output, SoundEffectContent value) { - // The WaveFormat provided by NAudio already contains the size - output.Write(value.format.ToArray()); + output.Write(value.format.Length); + output.Write(value.format); - output.Write(value.data.Count); - output.Write(value.data.ToArray()); + output.Write(value.data.Length); + output.Write(value.data); output.Write(value.loopStart); output.Write(value.loopLength); diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/CurveKeyCollectionSerializer.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/CurveKeyCollectionSerializer.cs new file mode 100644 index 00000000000..38fcd0f4407 --- /dev/null +++ b/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/CurveKeyCollectionSerializer.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate +{ + [ContentTypeSerializer] + class CurveKeyCollectionSerializer : ContentTypeSerializer + { + public CurveKeyCollectionSerializer() : + base("Keys") + { } + + public override bool CanDeserializeIntoExistingObject + { get { return true; } } + + protected internal override CurveKeyCollection Deserialize( + IntermediateReader input, + ContentSerializerAttribute format, + CurveKeyCollection existingInstance) + { + var result = existingInstance ?? new CurveKeyCollection(); + + if (input.Xml.HasValue) + { + var elements = PackedElementsHelper.ReadElements(input); + if (elements.Length > 0) + { + // Each CurveKey consists of 5 elements + if (elements.Length % 5 != 0) + throw new InvalidContentException( + "Elements count in CurveKeyCollection is inncorect!"); + try + { + // Parse all CurveKeys + for (int i = 0; i < elements.Length; i += 5) + { + // Order: Position, Value, TangentIn, TangentOut and Continuity + var curveKey = new CurveKey + (XmlConvert.ToSingle(elements[i]), + XmlConvert.ToSingle(elements[i + 1]), + XmlConvert.ToSingle(elements[i + 2]), + XmlConvert.ToSingle(elements[i + 3]), + (CurveContinuity)Enum.Parse( + typeof(CurveContinuity), + elements[i + 4], + true)); + result.Add(curveKey); + } + } + catch (Exception e) + { + throw new InvalidContentException + ("Error parsing CurveKey", e); + } + } + } + return result; + } + + + protected internal override void Serialize( + IntermediateWriter output, + CurveKeyCollection value, + ContentSerializerAttribute format) + { + var elements = new List(); + foreach (var curveKey in value) + { + // Order: Position, Value, TangentIn, TangentOut and Continuity + elements.Add(XmlConvert.ToString(curveKey.Position)); + elements.Add(XmlConvert.ToString(curveKey.Value)); + elements.Add(XmlConvert.ToString(curveKey.TangentIn)); + elements.Add(XmlConvert.ToString(curveKey.TangentOut)); + elements.Add(curveKey.Continuity.ToString()); + } + var str = PackedElementsHelper.JoinElements(elements); + output.Xml.WriteString(str); + } + } +} \ No newline at end of file diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/ElementSerializerT.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/ElementSerializerT.cs index bd994f49d24..4b9a81a7be8 100644 --- a/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/ElementSerializerT.cs +++ b/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/ElementSerializerT.cs @@ -11,10 +11,6 @@ namespace Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate { abstract class ElementSerializer : ContentTypeSerializer { - private static readonly char [] _seperators = { ' ', '\t', '\n' }; - - private const string _writeSeperator = " "; - private readonly int _elementCount; protected ElementSerializer(string xmlTypeName, int elementCount) : @@ -32,34 +28,11 @@ protected void ThrowElementCountException() protected internal abstract void Serialize(T value, List results); - private static string[] ReadElements(IntermediateReader input) - { - if (input.Xml.IsEmptyElement) - return new string[0]; - - string str = string.Empty; - while (input.Xml.NodeType != XmlNodeType.EndElement) - { - if (input.Xml.NodeType == XmlNodeType.Comment) - input.Xml.Read(); - else - str += input.Xml.ReadString(); - } - - // Special case for char ' ' - if (str.Length > 0 && str.Trim() == string.Empty) - return new string[] { str }; - - var elements = str.Split(_seperators, StringSplitOptions.RemoveEmptyEntries); - if (elements.Length == 1 && string.IsNullOrEmpty(elements[0])) - return new string[0]; - - return elements; - } + protected internal void Deserialize(IntermediateReader input, List results) { - var elements = ReadElements(input); + var elements = PackedElementsHelper.ReadElements(input); for (var index = 0; index < elements.Length;) { @@ -73,7 +46,7 @@ protected internal void Deserialize(IntermediateReader input, List results) protected internal override T Deserialize(IntermediateReader input, ContentSerializerAttribute format, T existingInstance) { - var elements = ReadElements(input); + var elements = PackedElementsHelper.ReadElements(input); if (elements.Length < _elementCount) ThrowElementCountException(); @@ -87,7 +60,7 @@ protected internal void Serialize(IntermediateWriter output, List values) var elements = new List(); for (var i = 0; i < values.Count; i++) Serialize(values[i], elements); - var str = string.Join(_writeSeperator, elements); + var str = PackedElementsHelper.JoinElements(elements); output.Xml.WriteString(str); } @@ -95,7 +68,7 @@ protected internal override void Serialize(IntermediateWriter output, T value, C { var elements = new List(); Serialize(value, elements); - var str = string.Join(_writeSeperator, elements); + var str = PackedElementsHelper.JoinElements(elements); output.Xml.WriteString(str); } } diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/NamespaceAliasHelper.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/NamespaceAliasHelper.cs index fc7dae29ce6..2d7b3e65c30 100644 --- a/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/NamespaceAliasHelper.cs +++ b/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/NamespaceAliasHelper.cs @@ -136,7 +136,7 @@ private static AliasedNamespace FindAlias(Dictionary a } /// - /// Returns just the portion relative to . + /// Returns just the portion relative to . /// For example, given namespaceParent=Foo.Bar and @namespace=Foo.Bar.Baz, will return Baz. /// private static string GetRelativeNamespace(string namespaceParent, string @namespace) diff --git a/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/PackedElementsHelper.cs b/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/PackedElementsHelper.cs new file mode 100644 index 00000000000..9e3117543d6 --- /dev/null +++ b/MonoGame.Framework.Content.Pipeline/Serialization/Intermediate/PackedElementsHelper.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate +{ + internal static class PackedElementsHelper + { + private static readonly char[] _seperators = { ' ', '\t', '\n' }; + + private const string _writeSeperator = " "; + + internal static string[] ReadElements(IntermediateReader input) + { + if (input.Xml.IsEmptyElement) + return new string[0]; + + string str = string.Empty; + while (input.Xml.NodeType != XmlNodeType.EndElement) + { + if (input.Xml.NodeType == XmlNodeType.Comment) + input.Xml.Read(); + else + str += input.Xml.ReadString(); + } + + // Special case for char ' ' + if (str.Length > 0 && str.Trim() == string.Empty) + return new string[] { str }; + + var elements = str.Split(_seperators, StringSplitOptions.RemoveEmptyEntries); + if (elements.Length == 1 && string.IsNullOrEmpty(elements[0])) + return new string[0]; + + return elements; + } + + public static string JoinElements(IEnumerable elements) + { + return string.Join(_writeSeperator, elements); + } + } +} \ No newline at end of file diff --git a/MonoGame.Framework.Content.Pipeline/TargetPlatform.cs b/MonoGame.Framework.Content.Pipeline/TargetPlatform.cs index 60bfd09df4b..df5f061c7e6 100644 --- a/MonoGame.Framework.Content.Pipeline/TargetPlatform.cs +++ b/MonoGame.Framework.Content.Pipeline/TargetPlatform.cs @@ -24,11 +24,6 @@ public enum TargetPlatform /// Xbox360, - /// - /// Windows Phone - /// - WindowsPhone, - // MonoGame-specific platforms listed below /// @@ -90,6 +85,16 @@ public enum TargetPlatform /// Sony PlayStation4 /// PlayStation4, + + /// + /// PlayStation Vita + /// + PSVita, + + /// + /// Xbox One + /// + XboxOne, } diff --git a/MonoGame.Framework.Content.Pipeline/Utilities/Vector4Converter.cs b/MonoGame.Framework.Content.Pipeline/Utilities/Vector4Converter.cs index a87c541c56f..40651188115 100644 --- a/MonoGame.Framework.Content.Pipeline/Utilities/Vector4Converter.cs +++ b/MonoGame.Framework.Content.Pipeline/Utilities/Vector4Converter.cs @@ -18,29 +18,30 @@ class Vector4Converter : IVector4Converter, IVector4Converter, IVector4Converter, - IVector4Converter + IVector4Converter, + IVector4Converter { Vector4 IVector4Converter.ToVector4(byte value) { var f = (float)value / (float)byte.MaxValue; - return new Vector4(f, f, f, f); + return new Vector4(f, 0f, 0f, 1f); } Vector4 IVector4Converter.ToVector4(short value) { var f = (float)value / (float)short.MaxValue; - return new Vector4(f, f, f, f); + return new Vector4(f, 0f, 0f, 1f); } Vector4 IVector4Converter.ToVector4(int value) { var f = (float)value / (float)int.MaxValue; - return new Vector4(f, f, f, f); + return new Vector4(f, 0f, 0f, 1f); } Vector4 IVector4Converter.ToVector4(float value) { - return new Vector4(value, value, value, value); + return new Vector4(value, 0f, 0f, 1f); } Vector4 IVector4Converter.ToVector4(Color value) @@ -48,6 +49,11 @@ Vector4 IVector4Converter.ToVector4(Color value) return value.ToVector4(); } + Vector4 IVector4Converter.ToVector4(Vector4 value) + { + return value; + } + byte IVector4Converter.FromVector4(Vector4 value) { return (byte)(value.X * (float)byte.MaxValue); @@ -72,5 +78,10 @@ Color IVector4Converter.FromVector4(Vector4 value) { return new Color(value); } + + Vector4 IVector4Converter.FromVector4(Vector4 value) + { + return value; + } } } diff --git a/MonoGame.Framework.Content.Pipeline/WavImporter.cs b/MonoGame.Framework.Content.Pipeline/WavImporter.cs index c369db20709..d6e730ac99a 100644 --- a/MonoGame.Framework.Content.Pipeline/WavImporter.cs +++ b/MonoGame.Framework.Content.Pipeline/WavImporter.cs @@ -3,6 +3,7 @@ // file 'LICENSE.txt', which is part of this source code package. using System; +using System.IO; using Microsoft.Xna.Framework.Content.Pipeline.Audio; namespace Microsoft.Xna.Framework.Content.Pipeline @@ -19,9 +20,25 @@ public class WavImporter : ContentImporter /// Name of a game asset file. /// Contains information for importing a game asset, such as a logger interface. /// Resulting game asset. + /// This importer only supports 8bit and 16bit depths with sample rates from 8KHz up to 48KHz. public override AudioContent Import(string filename, ContentImporterContext context) { + if (string.IsNullOrEmpty(filename)) + throw new ArgumentNullException("filename"); + if (context == null) + throw new ArgumentNullException("context"); + + if (!File.Exists(filename)) + throw new FileNotFoundException(string.Format("Could not locate audio file {0}.", Path.GetFileName(filename))); + var content = new AudioContent(filename, AudioFileType.Wav); + + // Validate the format of the input. + if (content.Format.SampleRate < 8000 || content.Format.SampleRate > 48000) + throw new InvalidContentException(string.Format("Audio file {0} contains audio data with unsupported sample rate of {1}KHz. Supported sample rates are from 8KHz up to 48KHz.", Path.GetFileName(filename), content.Format.SampleRate)); + if (content.Format.BitsPerSample != 8 && content.Format.BitsPerSample != 16) + throw new InvalidContentException(string.Format("Audio file {0} contains audio data with unsupported bit depth of {1}. Supported bit depths are from 8bit and 16bit.", Path.GetFileName(filename), content.Format.BitsPerSample)); + return content; } } diff --git a/MonoGame.Framework.Content.Pipeline/WmaImporter.cs b/MonoGame.Framework.Content.Pipeline/WmaImporter.cs index 5d945b49666..de0f5144848 100644 --- a/MonoGame.Framework.Content.Pipeline/WmaImporter.cs +++ b/MonoGame.Framework.Content.Pipeline/WmaImporter.cs @@ -3,6 +3,7 @@ // file 'LICENSE.txt', which is part of this source code package. using System; +using System.IO; using Microsoft.Xna.Framework.Content.Pipeline.Audio; namespace Microsoft.Xna.Framework.Content.Pipeline @@ -28,6 +29,14 @@ public WmaImporter() /// Resulting game asset. public override AudioContent Import(string filename, ContentImporterContext context) { + if (string.IsNullOrEmpty(filename)) + throw new ArgumentNullException("filename"); + if (context == null) + throw new ArgumentNullException("context"); + + if (!File.Exists(filename)) + throw new FileNotFoundException(string.Format("Could not locate audio file {0}.", Path.GetFileName(filename))); + var content = new AudioContent(filename, AudioFileType.Wma); return content; } diff --git a/MonoGame.Framework/Android/AndroidGameActivity.cs b/MonoGame.Framework/Android/AndroidGameActivity.cs index 4892e73e270..23efe410542 100644 --- a/MonoGame.Framework/Android/AndroidGameActivity.cs +++ b/MonoGame.Framework/Android/AndroidGameActivity.cs @@ -11,11 +11,7 @@ namespace Microsoft.Xna.Framework { [CLSCompliant(false)] -#if OUYA - public class AndroidGameActivity : Ouya.Console.Api.OuyaActivity -#else public class AndroidGameActivity : Activity -#endif { internal Game Game { private get; set; } diff --git a/MonoGame.Framework/Android/AndroidGamePlatform.cs b/MonoGame.Framework/Android/AndroidGamePlatform.cs index 6f8e94b4675..adafe49bc8b 100644 --- a/MonoGame.Framework/Android/AndroidGamePlatform.cs +++ b/MonoGame.Framework/Android/AndroidGamePlatform.cs @@ -11,8 +11,6 @@ namespace Microsoft.Xna.Framework { class AndroidGamePlatform : GamePlatform { - OpenALSoundController soundControllerInstance = null; - public AndroidGamePlatform(Game game) : base(game) { @@ -27,7 +25,7 @@ public AndroidGamePlatform(Game game) MediaLibrary.Context = Game.Activity; try { - soundControllerInstance = OpenALSoundController.GetInstance; + OpenALSoundController soundControllerInstance = OpenALSoundController.GetInstance; } catch (DllNotFoundException ex) { diff --git a/MonoGame.Framework/Android/AndroidGameWindow.cs b/MonoGame.Framework/Android/AndroidGameWindow.cs index 9a737bdccab..8800dbb2638 100644 --- a/MonoGame.Framework/Android/AndroidGameWindow.cs +++ b/MonoGame.Framework/Android/AndroidGameWindow.cs @@ -9,10 +9,6 @@ using Microsoft.Xna.Framework.Input.Touch; using OpenTK; -#if OUYA -using Microsoft.Xna.Framework.Input; -#endif - namespace Microsoft.Xna.Framework { [CLSCompliant(false)] @@ -53,10 +49,6 @@ private void Initialize(Context context) GameView.RequestFocus(); GameView.FocusableInTouchMode = true; - -#if OUYA - GamePad.Initialize(); -#endif } #region AndroidGameView Methods @@ -300,8 +292,6 @@ private void SetDisplayOrientation(DisplayOrientation value) TouchPanelState.ReleaseAllTouches(); } - Game.Activity.RequestedOrientation = requestedOrientation; - OnOrientationChanged(); } } diff --git a/MonoGame.Framework/Android/GamerServices/Guide.cs b/MonoGame.Framework/Android/GamerServices/Guide.cs index 9e23c5e6077..2ecb9983ac3 100644 --- a/MonoGame.Framework/Android/GamerServices/Guide.cs +++ b/MonoGame.Framework/Android/GamerServices/Guide.cs @@ -50,7 +50,6 @@ purpose and non-infringement. using Android.Views; using Android.Widget; using Microsoft.Xna.Framework.Net; -using Microsoft.Xna.Framework.Storage; #endregion Using clause @@ -342,16 +341,6 @@ public static void ShowMatchMaker() } } - public static IAsyncResult BeginShowStorageDeviceSelector( AsyncCallback callback, object state ) - { - return null; - } - - public static StorageDevice EndShowStorageDeviceSelector( IAsyncResult result ) - { - return null; - } - #region Properties public static bool IsScreenSaverEnabled { @@ -410,4 +399,4 @@ public static AndroidGameWindow Window #endregion } -} \ No newline at end of file +} diff --git a/MonoGame.Framework/Android/MonoGameAndroidGameView.cs b/MonoGame.Framework/Android/MonoGameAndroidGameView.cs index 22d03aeae4c..4b7b8093b70 100644 --- a/MonoGame.Framework/Android/MonoGameAndroidGameView.cs +++ b/MonoGame.Framework/Android/MonoGameAndroidGameView.cs @@ -27,9 +27,7 @@ internal class MonoGameAndroidGameView : AndroidGameView, View.IOnTouchListener, public bool IsResuming { get; private set; } private bool _lostContext; -#if !OUYA private bool backPressed; -#endif public MonoGameAndroidGameView(Context context, AndroidGameWindow androidGameWindow, Game game) : base(context) @@ -290,25 +288,22 @@ protected override void CreateFrameBuffer() #endregion - #region Key and Motion + #region Key and Motion and Gamepad public override bool OnKeyDown(Keycode keyCode, KeyEvent e) { -#if OUYA - if (GamePad.OnKeyDown(keyCode, e)) - return true; -#endif + // Handle gamepad inputs in Android + if ((e.Source & InputSourceType.Gamepad) == InputSourceType.Gamepad) + return GamePad.OnKeyDown(keyCode, e); Keyboard.KeyDown(keyCode); // we need to handle the Back key here because it doesnt work any other way -#if !OUYA if (keyCode == Keycode.Back && !this.backPressed) { this.backPressed = true; GamePad.Back = true; return true; } -#endif if (keyCode == Keycode.VolumeUp) { @@ -329,29 +324,25 @@ public override bool OnKeyDown(Keycode keyCode, KeyEvent e) public override bool OnKeyUp(Keycode keyCode, KeyEvent e) { -#if OUYA - if (GamePad.OnKeyUp(keyCode, e)) - return true; -#endif + if ((e.Source & InputSourceType.Gamepad) == InputSourceType.Gamepad) + return GamePad.OnKeyUp(keyCode, e); + Keyboard.KeyUp(keyCode); -#if !OUYA + // we need to handle the Back key here because it doesnt work any other way if (keyCode == Keycode.Back) this.backPressed = false; -#endif return true; } -#if OUYA public override bool OnGenericMotionEvent(MotionEvent e) { - if (GamePad.OnGenericMotionEvent(e)) - return true; + if ((e.Source & InputSourceType.Gamepad) == InputSourceType.Gamepad || (e.Source & InputSourceType.Joystick) == InputSourceType.Joystick) + return GamePad.OnGenericMotionEvent(e); return base.OnGenericMotionEvent(e); } -#endif #endregion } diff --git a/MonoGame.Framework/Audio/AudioLoader.cs b/MonoGame.Framework/Audio/AudioLoader.cs index dd708a792a6..d063f488a0d 100644 --- a/MonoGame.Framework/Audio/AudioLoader.cs +++ b/MonoGame.Framework/Audio/AudioLoader.cs @@ -1,6 +1,10 @@ using System; using System.IO; +#if GLES using OpenTK.Audio.OpenAL; +#else +using OpenAL; +#endif namespace Microsoft.Xna.Framework.Audio { @@ -14,8 +18,8 @@ private static ALFormat GetSoundFormat(int channels, int bits) { switch (channels) { - case 1: return bits == 8 ? OpenTK.Audio.OpenAL.ALFormat.Mono8 : OpenTK.Audio.OpenAL.ALFormat.Mono16; - case 2: return bits == 8 ? OpenTK.Audio.OpenAL.ALFormat.Stereo8 : OpenTK.Audio.OpenAL.ALFormat.Stereo16; + case 1: return bits == 8 ? ALFormat.Mono8 : ALFormat.Mono16; + case 2: return bits == 8 ? ALFormat.Stereo8 : ALFormat.Stereo16; default: throw new NotSupportedException("The specified sound format is not supported."); } } @@ -49,7 +53,7 @@ private static byte[] LoadWave(BinaryReader reader, out ALFormat format, out int string signature = new string(reader.ReadChars(4)); if (signature != "RIFF") { - throw new NotSupportedException("Specified stream is not a wave file."); + throw new ArgumentException("Specified stream is not a wave file."); } reader.ReadInt32(); // riff_chunck_size @@ -57,7 +61,7 @@ private static byte[] LoadWave(BinaryReader reader, out ALFormat format, out int string wformat = new string(reader.ReadChars(4)); if (wformat != "WAVE") { - throw new NotSupportedException("Specified stream is not a wave file."); + throw new ArgumentException("Specified stream is not a wave file."); } // WAVE header @@ -79,7 +83,7 @@ private static byte[] LoadWave(BinaryReader reader, out ALFormat format, out int if (audio_format != 1) { - throw new NotSupportedException("Wave compression is not supported."); + throw new ArgumentException("Wave compression is not supported."); } // reads residual bytes @@ -96,7 +100,7 @@ private static byte[] LoadWave(BinaryReader reader, out ALFormat format, out int if (data_signature != "data") { - throw new NotSupportedException("Specified wave file is not supported."); + throw new ArgumentException("Specified wave file is not supported."); } int data_chunk_size = reader.ReadInt32(); diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs new file mode 100644 index 00000000000..620438e0b48 --- /dev/null +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.OpenAL.cs @@ -0,0 +1,166 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using System.Collections.Generic; +#if MONOMAC && PLATFORM_MACOS_LEGACY +using MonoMac.OpenAL; +#endif +#if MONOMAC && !PLATFORM_MACOS_LEGACY +using OpenTK.Audio.OpenAL; +#endif +#if GLES +using OpenTK.Audio.OpenAL; +#endif +#if DESKTOPGL +using OpenAL; +#endif + +namespace Microsoft.Xna.Framework.Audio +{ + public sealed partial class DynamicSoundEffectInstance : SoundEffectInstance + { + private Queue _queuedBuffers; + private ALFormat _format; + + private void PlatformCreate() + { + _format = _channels == AudioChannels.Mono ? ALFormat.Mono16 : ALFormat.Stereo16; + InitializeSound(); + + SourceId = controller.ReserveSource(); + HasSourceId = true; + + _queuedBuffers = new Queue(); + } + + private int PlatformGetPendingBufferCount() + { + return _queuedBuffers.Count; + } + + private void PlatformPlay() + { + AL.GetError(); + + // Ensure that the source is not looped (due to source recycling) + AL.Source(SourceId, ALSourceb.Looping, false); + ALHelper.CheckError("Failed to set source loop state."); + + AL.SourcePlay(SourceId); + ALHelper.CheckError("Failed to play the source."); + } + + private void PlatformPause() + { + AL.GetError(); + AL.SourcePause(SourceId); + ALHelper.CheckError("Failed to pause the source."); + } + + private void PlatformResume() + { + AL.GetError(); + AL.SourcePlay(SourceId); + ALHelper.CheckError("Failed to play the source."); + } + + private void PlatformStop() + { + AL.GetError(); + AL.SourceStop(SourceId); + ALHelper.CheckError("Failed to stop the source."); + + // Remove all queued buffers + AL.Source(SourceId, ALSourcei.Buffer, 0); + while (_queuedBuffers.Count > 0) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Dispose(); + } + } + + private void PlatformSubmitBuffer(byte[] buffer, int offset, int count) + { + // Get a buffer + OALSoundBuffer oalBuffer = new OALSoundBuffer(); + + // Bind the data + if (offset == 0) + { + oalBuffer.BindDataBuffer(buffer, _format, count, _sampleRate); + } + else + { + // BindDataBuffer does not support offset + var offsetBuffer = new byte[count]; + Array.Copy(buffer, offset, offsetBuffer, 0, count); + oalBuffer.BindDataBuffer(offsetBuffer, _format, count, _sampleRate); + } + + // Queue the buffer + AL.SourceQueueBuffer(SourceId, oalBuffer.OpenALDataBuffer); + ALHelper.CheckError(); + _queuedBuffers.Enqueue(oalBuffer); + + + // If the source has run out of buffers, restart it + var sourceState = AL.GetSourceState(SourceId); + if (_state == SoundState.Playing && sourceState == ALSourceState.Stopped) + { + AL.SourcePlay(SourceId); + ALHelper.CheckError("Failed to resume source playback."); + } + } + + private void PlatformDispose(bool disposing) + { + // Stop the source and bind null buffer so that it can be recycled + AL.GetError(); + if (AL.IsSource(SourceId)) + { + AL.SourceStop(SourceId); + AL.Source(SourceId, ALSourcei.Buffer, 0); + ALHelper.CheckError("Failed to stop the source."); + controller.RecycleSource(SourceId); + } + + if (disposing) + { + while (_queuedBuffers.Count > 0) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Dispose(); + } + + DynamicSoundEffectInstanceManager.RemoveInstance(this); + } + } + + private void PlatformUpdateQueue() + { + // Get the completed buffers + AL.GetError(); + int numBuffers; + AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out numBuffers); + ALHelper.CheckError("Failed to get processed buffer count."); + + // Unqueue them + if (numBuffers > 0) + { + AL.SourceUnqueueBuffers(SourceId, numBuffers); + ALHelper.CheckError("Failed to unqueue buffers."); + for (int i = 0; i < numBuffers; i++) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Dispose(); + } + } + + // Raise the event for each removed buffer, if needed + for (int i = 0; i < numBuffers; i++) + CheckBufferCount(); + } + } +} diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.Web.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.Web.cs new file mode 100644 index 00000000000..f72119f068b --- /dev/null +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.Web.cs @@ -0,0 +1,48 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; + +namespace Microsoft.Xna.Framework.Audio +{ + public sealed partial class DynamicSoundEffectInstance : SoundEffectInstance + { + private void PlatformCreate() + { + } + + private int PlatformGetPendingBufferCount() + { + return 0; + } + + private void PlatformPlay() + { + } + + private void PlatformPause() + { + } + + private void PlatformResume() + { + } + + private void PlatformStop() + { + } + + private void PlatformSubmitBuffer(byte[] buffer, int offset, int count) + { + } + + private void PlatformDispose(bool disposing) + { + } + + private void PlatformUpdateQueue() + { + } + } +} diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs new file mode 100644 index 00000000000..d973274a92f --- /dev/null +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.XAudio.cs @@ -0,0 +1,113 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework.Utilities; +using SharpDX; +using SharpDX.Multimedia; +using SharpDX.XAudio2; + +namespace Microsoft.Xna.Framework.Audio +{ + public sealed partial class DynamicSoundEffectInstance : SoundEffectInstance + { + private Queue _queuedBuffers; + private Queue _pooledBuffers; + private static ByteBufferPool _bufferPool = new ByteBufferPool(); + + private void PlatformCreate() + { + _format = new WaveFormat(_sampleRate, (int)_channels); + _voice = new SourceVoice(SoundEffect.Device, _format, true); + _voice.BufferEnd += OnBufferEnd; + _queuedBuffers = new Queue(); + _pooledBuffers = new Queue(); + } + + private int PlatformGetPendingBufferCount() + { + return _queuedBuffers.Count; + } + + private void PlatformPlay() + { + _voice.Start(); + } + + private void PlatformPause() + { + _voice.Stop(); + } + + private void PlatformResume() + { + _voice.Start(); + } + + private void PlatformStop() + { + _voice.Stop(); + + // Dequeue all the submitted buffers + _voice.FlushSourceBuffers(); + + while (_queuedBuffers.Count > 0) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Stream.Dispose(); + _bufferPool.Return(_pooledBuffers.Dequeue()); + } + } + + private void PlatformSubmitBuffer(byte[] buffer, int offset, int count) + { + // we need to copy so datastream does not pin the buffer that the user might modify later + byte[] pooledBuffer; + pooledBuffer = _bufferPool.Get(count); + _pooledBuffers.Enqueue(pooledBuffer); + Buffer.BlockCopy(buffer, offset, pooledBuffer, 0, count); + + var stream = DataStream.Create(pooledBuffer, true, false, offset, true); + var audioBuffer = new AudioBuffer(stream); + audioBuffer.AudioBytes = count; + + _voice.SubmitSourceBuffer(audioBuffer, null); + _queuedBuffers.Enqueue(audioBuffer); + } + + private void PlatformUpdateQueue() + { + // The XAudio implementation utilizes callbacks, so no work here. + } + + private void PlatformDispose(bool disposing) + { + if (disposing) + { + while (_queuedBuffers.Count > 0) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Stream.Dispose(); + _bufferPool.Return(_pooledBuffers.Dequeue()); + } + } + // _voice is disposed by SoundEffectInstance.PlatformDispose + } + + private void OnBufferEnd(IntPtr obj) + { + // Release the buffer + if (_queuedBuffers.Count > 0) + { + var buffer = _queuedBuffers.Dequeue(); + buffer.Stream.Dispose(); + _bufferPool.Return(_pooledBuffers.Dequeue()); + } + + CheckBufferCount(); + } + + } +} diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs index e0e435beef3..6e6fc582c77 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs @@ -1,102 +1,306 @@ -using System; +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -#if MONOMAC -using MonoMac.OpenAL; -#else -using OpenTK.Audio.OpenAL; -#endif namespace Microsoft.Xna.Framework.Audio { - public sealed class DynamicSoundEffectInstance : SoundEffectInstance + /// + /// A for which the audio buffer is provided by the game at run time. + /// + public sealed partial class DynamicSoundEffectInstance : SoundEffectInstance { - private AudioChannels channels; - private int sampleRate; - private ALFormat format; - private bool looped; - private int pendingBufferCount; - // Events - public event EventHandler BufferNeeded; + #region Public Properties - internal void OnBufferNeeded(EventArgs args) + /// + /// This value has no effect on DynamicSoundEffectInstance. + /// It may not be set. + /// + public override bool IsLooped { - if (BufferNeeded != null) + get { - BufferNeeded(this, args); + return false; + } + + set + { + AssertNotDisposed(); + if (value == true) + throw new InvalidOperationException("IsLooped cannot be set true. Submit looped audio data to implement looping."); } } - public DynamicSoundEffectInstance(int sampleRate, AudioChannels channels) + public override SoundState State { - this.sampleRate = sampleRate; - this.channels = channels; - switch (channels) + get { - case AudioChannels.Mono: - this.format = ALFormat.Mono16; - break; - case AudioChannels.Stereo: - this.format = ALFormat.Stereo16; - break; - default: - break; - } - } - - /* - public TimeSpan GetSampleDuration(int sizeInBytes) - { - throw new NotImplementedException(); + AssertNotDisposed(); + return _state; + } } - public int GetSampleSizeInBytes(TimeSpan duration) + /// + /// Returns the number of audio buffers queued for playback. + /// + public int PendingBufferCount { - throw new NotImplementedException(); + get + { + AssertNotDisposed(); + return PlatformGetPendingBufferCount(); + } } - */ - public override void Play() + /// + /// The event that occurs when the number of queued audio buffers is less than or equal to 2. + /// + /// + /// This event may occur when is called or during playback when a buffer is completed. + /// + public event EventHandler BufferNeeded; + + #endregion + + private const int TargetPendingBufferCount = 3; + private int _buffersNeeded; + private int _sampleRate; + private AudioChannels _channels; + private SoundState _state; + + #region Public Constructor + + /// Sample rate, in Hertz (Hz). + /// Number of channels (mono or stereo). + public DynamicSoundEffectInstance(int sampleRate, AudioChannels channels) { - throw new NotImplementedException(); + if ((sampleRate < 8000) || (sampleRate > 48000)) + throw new ArgumentOutOfRangeException("sampleRate"); + if ((channels != AudioChannels.Mono) && (channels != AudioChannels.Stereo)) + throw new ArgumentOutOfRangeException("channels"); + + _sampleRate = sampleRate; + _channels = channels; + _state = SoundState.Stopped; + PlatformCreate(); + + // This instance is added to the pool so that its volume reflects master volume changes + // and it contributes to the playing instances limit, but the source/voice is not owned by the pool. + _isPooled = false; + _isDynamic = true; } - public void SubmitBuffer(byte[] buffer) + #endregion + + #region Public Functions + + /// + /// Returns the duration of an audio buffer of the specified size, based on the settings of this instance. + /// + /// Size of the buffer, in bytes. + /// The playback length of the buffer. + public TimeSpan GetSampleDuration(int sizeInBytes) { - this.SubmitBuffer(buffer, 0, buffer.Length); + AssertNotDisposed(); + return SoundEffect.GetSampleDuration(sizeInBytes, _sampleRate, _channels); } - public void SubmitBuffer(byte[] buffer, int offset, int count) + /// + /// Returns the size, in bytes, of a buffer of the specified duration, based on the settings of this instance. + /// + /// The playback length of the buffer. + /// The data size of the buffer, in bytes. + public int GetSampleSizeInBytes(TimeSpan duration) { - BindDataBuffer(buffer, format, count, sampleRate); + AssertNotDisposed(); + return SoundEffect.GetSampleSizeInBytes(duration, _sampleRate, _channels); } - public override bool IsLooped + /// + /// Plays or resumes the DynamicSoundEffectInstance. + /// + public override void Play() { - get + AssertNotDisposed(); + + if (_state != SoundState.Playing) { - return looped; + // Ensure that the volume reflects master volume, which is done by the setter. + Volume = Volume; + + // Add the instance to the pool + if (!SoundEffectInstancePool.SoundsAvailable) + throw new InstancePlayLimitException(); + SoundEffectInstancePool.Remove(this); + + PlatformPlay(); + _state = SoundState.Playing; + + CheckBufferCount(); + DynamicSoundEffectInstanceManager.AddInstance(this); } + } - set + /// + /// Pauses playback of the DynamicSoundEffectInstance. + /// + public override void Pause() + { + AssertNotDisposed(); + PlatformPause(); + _state = SoundState.Paused; + } + + /// + /// Resumes playback of the DynamicSoundEffectInstance. + /// + public override void Resume() + { + AssertNotDisposed(); + + if (_state != SoundState.Playing) { - looped = value; + Volume = Volume; + + // Add the instance to the pool + if (!SoundEffectInstancePool.SoundsAvailable) + throw new InstancePlayLimitException(); + SoundEffectInstancePool.Remove(this); } + + PlatformResume(); + _state = SoundState.Playing; } - public int PendingBufferCount + /// + /// Immediately stops playing the DynamicSoundEffectInstance. + /// + /// + /// Calling this also releases all queued buffers. + /// + public override void Stop() { - get + Stop(true); + } + + /// + /// Stops playing the DynamicSoundEffectInstance. + /// If the parameter is false, this call has no effect. + /// + /// + /// Calling this also releases all queued buffers. + /// + /// When set to false, this call has no effect. + public override void Stop(bool immediate) + { + AssertNotDisposed(); + + if (immediate) { - return pendingBufferCount; + DynamicSoundEffectInstanceManager.RemoveInstance(this); + + PlatformStop(); + _state = SoundState.Stopped; + + SoundEffectInstancePool.Add(this); } - private set + } + + /// + /// Queues an audio buffer for playback. + /// + /// + /// The buffer length must conform to alignment requirements for the audio format. + /// + /// The buffer containing PCM audio data. + public void SubmitBuffer(byte[] buffer) + { + AssertNotDisposed(); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer may not be empty."); + + // Ensure that the buffer length matches alignment. + // The data must be 16-bit, so the length is a multiple of 2 (mono) or 4 (stereo). + var sampleSize = 2 * (int)_channels; + if (buffer.Length % sampleSize != 0) + throw new ArgumentException("Buffer length does not match format alignment."); + + SubmitBuffer(buffer, 0, buffer.Length); + } + + /// + /// Queues an audio buffer for playback. + /// + /// + /// The buffer length must conform to alignment requirements for the audio format. + /// + /// The buffer containing PCM audio data. + /// The starting position of audio data. + /// The amount of bytes to use. + public void SubmitBuffer(byte[] buffer, int offset, int count) + { + AssertNotDisposed(); + + if ((buffer == null) || (buffer.Length == 0)) + throw new ArgumentException("Buffer may not be null or empty."); + if (count <= 0) + throw new ArgumentException("Number of bytes must be greater than zero."); + if ((offset + count) > buffer.Length) + throw new ArgumentException("Buffer is shorter than the specified number of bytes from the offset."); + + // Ensure that the buffer length and start position match alignment. + var sampleSize = 2 * (int)_channels; + if (count % sampleSize != 0) + throw new ArgumentException("Number of bytes does not match format alignment."); + if (offset % sampleSize != 0) + throw new ArgumentException("Offset into the buffer does not match format alignment."); + + PlatformSubmitBuffer(buffer, offset, count); + } + + #endregion + + #region Nonpublic Functions + + private void AssertNotDisposed() + { + if (IsDisposed) + throw new ObjectDisposedException(null); + } + + protected override void Dispose(bool disposing) + { + PlatformDispose(disposing); + base.Dispose(disposing); + } + + private void CheckBufferCount() + { + if ((PendingBufferCount < TargetPendingBufferCount) && (_state == SoundState.Playing)) + _buffersNeeded++; + } + + internal void UpdateQueue() + { + // Update the buffers + PlatformUpdateQueue(); + + // Raise the event + if (BufferNeeded != null) { - pendingBufferCount = value; + var eventCount = (_buffersNeeded < 3) ? _buffersNeeded : 3; + for (var i = 0; i < eventCount; i++) + { + BufferNeeded(this, EventArgs.Empty); + } } - } - } + _buffersNeeded = 0; + } + #endregion + } } diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstanceManager.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstanceManager.cs new file mode 100644 index 00000000000..89d8f38371b --- /dev/null +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstanceManager.cs @@ -0,0 +1,64 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Xna.Framework.Audio +{ + /// + /// Handles the buffer events of all DynamicSoundEffectInstance instances. + /// + internal static class DynamicSoundEffectInstanceManager + { + private static readonly List _playingInstances; + + static DynamicSoundEffectInstanceManager() + { + _playingInstances = new List(); + } + + public static void AddInstance(DynamicSoundEffectInstance instance) + { + var weakRef = new WeakReference(instance); + _playingInstances.Add(weakRef); + } + + public static void RemoveInstance(DynamicSoundEffectInstance instance) + { + for (int i = _playingInstances.Count - 1; i >= 0; i--) + { + if (_playingInstances[i].Target == instance) + { + _playingInstances.RemoveAt(i); + return; + } + } + } + + /// + /// Updates buffer queues of the currently playing instances. + /// + /// + /// XNA posts events always on the main thread. + /// + public static void UpdatePlayingInstances() + { + for (int i = _playingInstances.Count - 1; i >= 0; i--) + { + var target = _playingInstances[i].Target as DynamicSoundEffectInstance; + if (target != null) + { + if (!target.IsDisposed) + target.UpdateQueue(); + } + else + { + // The instance has been disposed. + _playingInstances.RemoveAt(i); + } + } + } + } +} diff --git a/MonoGame.Framework/Audio/OALSoundBuffer.cs b/MonoGame.Framework/Audio/OALSoundBuffer.cs index 45d5598c0b3..9a206cd42c2 100644 --- a/MonoGame.Framework/Audio/OALSoundBuffer.cs +++ b/MonoGame.Framework/Audio/OALSoundBuffer.cs @@ -6,10 +6,20 @@ #if MONOMAC && PLATFORM_MACOS_LEGACY using MonoMac.OpenAL; -#else +#endif +#if MONOMAC && !PLATFORM_MACOS_LEGACY using OpenTK.Audio.OpenAL; #endif +#if GLES +using OpenTK.Audio.OpenAL; +#endif + +#if DESKTOPGL +using OpenAL; +#endif + + namespace Microsoft.Xna.Framework.Audio { internal class OALSoundBuffer : IDisposable @@ -49,12 +59,20 @@ public double Duration { set; } - public void BindDataBuffer(byte[] dataBuffer, ALFormat format, int size, int sampleRate) + public void BindDataBuffer(byte[] dataBuffer, ALFormat format, int size, int sampleRate, int alignment = 0) { openALFormat = format; dataSize = size; this.sampleRate = sampleRate; - AL.BufferData(openALDataBuffer, openALFormat, dataBuffer, dataSize, this.sampleRate); + int unpackedSize = 0; +#if DESKTOPGL + if (alignment > 0) { + AL.Bufferi (openALDataBuffer, ALBufferi.UnpackBlockAlignmentSoft, alignment); + ALHelper.CheckError ("Failed to fill buffer."); + } +#endif + + AL.BufferData(openALDataBuffer, openALFormat, dataBuffer, size, this.sampleRate); ALHelper.CheckError("Failed to fill buffer."); int bits, channels; @@ -73,12 +91,19 @@ public void BindDataBuffer(byte[] dataBuffer, ALFormat format, int size, int sam alError = AL.GetError(); if (alError != ALError.NoError) { - Console.WriteLine("Failed to get buffer bits: {0}, format={1}, size={2}, sampleRate={3}", AL.GetErrorString(alError), format, size, sampleRate); + Console.WriteLine("Failed to get buffer channels: {0}, format={1}, size={2}, sampleRate={3}", AL.GetErrorString(alError), format, size, sampleRate); Duration = -1; } else { - Duration = (float)(size / ((bits / 8) * channels)) / (float)sampleRate; + AL.GetBuffer (openALDataBuffer, ALGetBufferi.Size, out unpackedSize); + alError = AL.GetError (); + if (alError != ALError.NoError) { + Console.WriteLine ("Failed to get buffer size: {0}, format={1}, size={2}, sampleRate={3}", AL.GetErrorString (alError), format, size, sampleRate); + Duration = -1; + } else { + Duration = (float)(unpackedSize / ((bits / 8) * channels)) / (float)sampleRate; + } } } //Console.WriteLine("Duration: " + Duration + " / size: " + size + " bits: " + bits + " channels: " + channels + " rate: " + sampleRate); diff --git a/MonoGame.Framework/Audio/OggStream.cs b/MonoGame.Framework/Audio/OggStream.cs index 85d95f0753c..b2f86b4793b 100644 --- a/MonoGame.Framework/Audio/OggStream.cs +++ b/MonoGame.Framework/Audio/OggStream.cs @@ -13,7 +13,7 @@ using System.Linq; using System.Threading; using NVorbis; -using OpenTK.Audio.OpenAL; +using OpenAL; namespace Microsoft.Xna.Framework.Audio { @@ -320,7 +320,7 @@ internal void Close() internal class OggStreamer : IDisposable { public readonly XRamExtension XRam = new XRamExtension(); - public readonly EffectsExtension Efx = new EffectsExtension(); + public readonly EffectsExtension Efx = OpenALSoundController.Efx; const float DefaultUpdateRate = 10; const int DefaultBufferSize = 44100; @@ -405,10 +405,8 @@ internal bool RemoveStream(OggStream stream) public bool FillBuffer(OggStream stream, int bufferId) { int readSamples; - long readerPosition = 0; lock (readMutex) { - readerPosition = stream.Reader.DecodedPosition; readSamples = stream.Reader.ReadSamples(readSampleBuffer, 0, BufferSize); CastBuffer(readSampleBuffer, castBuffer, readSamples); } diff --git a/MonoGame.Framework/Audio/OpenAL.cs b/MonoGame.Framework/Audio/OpenAL.cs new file mode 100644 index 00000000000..b72ec2440d6 --- /dev/null +++ b/MonoGame.Framework/Audio/OpenAL.cs @@ -0,0 +1,701 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using System.Runtime.InteropServices; +using Microsoft.Xna.Framework.Audio; + +namespace OpenAL +{ + public enum ALFormat + { + Mono8 = 0x1100, + Mono16 = 0x1101, + Stereo8 = 0x1102, + Stereo16 = 0x1103, + MonoMSADPCM =0x1302, + StereoMSADPCM =0x1303, + } + + public enum ALError + { + NoError = 0, + InvalidName = 0xA001, + InvalidEnum = 0xA002, + InvalidValue = 0xA003, + InvalidOperation = 0xA004, + OutOfMemory = 0xA005, + } + + public enum ALGetString + { + Extensions = 0xB004, + } + + public enum ALBufferi + { + UnpackBlockAlignmentSoft = 0x200C, + LoopSoftPointsExt = 0x2015, + } + + public enum ALGetBufferi + { + Bits = 0x2002, + Channels = 0x2003, + Size = 0x2004, + } + + public enum ALSourceb + { + Looping = 0x1007, + } + + public enum ALSourcei + { + Buffer = 0x1009, + EfxDirectFilter = 0x20005, + EfxAuxilarySendFilter = 0x20006, + } + + public enum ALSourcef + { + Pitch = 0x1003, + Gain = 0x100A, + } + + public enum ALGetSourcei + { + SampleOffset = 0x1025, + SourceState = 0x1010, + BuffersQueued = 0x1015, + BuffersProcessed = 0x1016, + } + + public enum ALSourceState + { + Initial = 0x1011, + Playing = 0x1012, + Paused = 0x1013, + Stopped = 0x1014, + } + + public enum ALListener3f + { + Position = 0x1004, + } + + public enum ALSource3f + { + Position = 0x1004, + Velocity = 0x1006, + } + + public enum ALDistanceModel + { + InverseDistanceClamped = 0xD002, + } + + public enum AlcError + { + NoError = 0, + } + + public enum AlcGetString + { + Extensions = 0x1006, + } + + public enum EfxFilteri + { + FilterType = 0x8001, + } + + public enum EfxFilterf + { + LowpassGain = 0x0001, + LowpassGainHF = 0x0002, + HighpassGain = 0x0001, + HighpassGainLF = 0x0002, + BandpassGain = 0x0001, + BandpassGainLF = 0x0002, + BandpassGainHF = 0x0003, + } + + public enum EfxFilterType + { + None = 0x0000, + Lowpass = 0x0001, + Highpass = 0x0002, + Bandpass = 0x0003, + } + + public enum EfxEffecti + { + EffectType = 0x8001, + SlotEffect = 0x0001, + } + + public enum EfxEffectSlotf + { + EffectSlotGain = 0x0002, + } + + public enum EfxEffectf + { + EaxReverbDensity = 0x0001, + EaxReverbDiffusion = 0x0002, + EaxReverbGain = 0x0003, + EaxReverbGainHF = 0x0004, + EaxReverbGainLF = 0x0005, + DecayTime = 0x0006, + DecayHighFrequencyRatio = 0x0007, + DecayLowFrequencyRation = 0x0008, + EaxReverbReflectionsGain = 0x0009, + EaxReverbReflectionsDelay = 0x000A, + ReflectionsPain = 0x000B, + LateReverbGain = 0x000C, + LateReverbDelay = 0x000D, + LateRevertPain = 0x000E, + EchoTime = 0x000F, + EchoDepth = 0x0010, + ModulationTime = 0x0011, + ModulationDepth = 0x0012, + AirAbsorbsionHighFrequency = 0x0013, + EaxReverbHFReference = 0x0014, + EaxReverbLFReference = 0x0015, + RoomRolloffFactor = 0x0016, + DecayHighFrequencyLimit = 0x0017, + } + + public enum EfxEffectType + { + Reverb = 0x8000, + } + + public class AL + { + public const string NativeLibName = "soft_oal.dll"; + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alEnable")] + public static extern void Enable (int cap); + + [CLSCompliant(false)] + [DllImport(NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alBufferData")] + public static extern void BufferData(uint bid, int format, IntPtr data, int size, int freq); + + public static void BufferData(int bid, ALFormat format, byte[] data, int size, int freq) + { + var handle = GCHandle.Alloc(data, GCHandleType.Pinned); + BufferData((uint)bid, (int)format, handle.AddrOfPinnedObject(), size, freq); + handle.Free(); + } + + public static void BufferData(int bid, ALFormat format, short[] data, int size, int freq) + { + var handle = GCHandle.Alloc(data, GCHandleType.Pinned); + BufferData((uint)bid, (int)format, handle.AddrOfPinnedObject(), size, freq); + handle.Free(); + } + + [CLSCompliant (false)] + [DllImport(NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alDeleteBuffers")] + public static unsafe extern void DeleteBuffers(int n, int* buffers); + + public static void DeleteBuffers(int[] buffers) + { + DeleteBuffers (buffers.Length, ref buffers [0]); + } + + public unsafe static void DeleteBuffers(int n, ref int buffers) + { + fixed (int* pbuffers = &buffers) + { + DeleteBuffers (n, pbuffers); + } + } + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alBufferi")] + public static extern void Bufferi (int buffer, ALBufferi param, int value); + + [DllImport(NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetBufferi")] + public static extern void GetBufferi(int bid, ALGetBufferi param, out int value); + + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alBufferiv")] + public static extern void Bufferiv (int bid, ALBufferi param, int[] values); + + public static void GetBuffer(int bid, ALGetBufferi param, out int value) + { + GetBufferi(bid, param, out value); + } + + [CLSCompliant(false)] + [DllImport(NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGenBuffers")] + public static unsafe extern void GenBuffers(int count, int* buffers); + + internal unsafe static void GenBuffers (int count,out int[] buffers) + { + buffers = new int[count]; + fixed (int* ptr = &buffers[0]) + { + GenBuffers (count, ptr); + } + } + + public static void GenBuffers(int count, out int buffer) + { + int[] ret; + GenBuffers(count, out ret); + buffer = ret[0]; + } + + public static int[] GenBuffers(int count) + { + int[] ret; + GenBuffers(count, out ret); + return ret; + } + + [CLSCompliant(false)] + [DllImport(NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGenSources")] + public static extern void GenSources(int n, uint[] sources); + + + public static void GenSources(int[] sources) + { + uint[] temp = new uint[sources.Length]; + GenSources(temp.Length, temp); + for (int i = 0; i < temp.Length; i++) + { + sources[i] = (int)temp[i]; + } + } + + [DllImport(NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetError")] + public static extern ALError GetError(); + + [CLSCompliant(false)] + [DllImport(NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alIsBuffer")] + public static extern bool IsBuffer(uint buffer); + + public static bool IsBuffer(int buffer) + { + return IsBuffer((uint)buffer); + } + + [CLSCompliant(false)] + [DllImport(NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcePause")] + public static extern void SourcePause(uint source); + + public static void SourcePause(int source) + { + SourcePause((uint)source); + } + + [CLSCompliant(false)] + [DllImport(NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcePlay")] + public static extern void SourcePlay(uint source); + + public static void SourcePlay(int source) + { + SourcePlay((uint)source); + } + + public static string GetErrorString(ALError errorCode) + { + return errorCode.ToString (); + } + + [CLSCompliant(false)] + [DllImport(NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alIsSource")] + public static extern bool IsSource(int source); + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alDeleteSources")] + public static extern void DeleteSources(int n, ref int sources); + + public static void DeleteSource(int source) + { + DeleteSources (1, ref source); + } + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceStop")] + public static extern void SourceStop (int sourceId); + + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcei")] + internal static extern void Source (int sourceId, int i, int a); + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSource3i")] + public static extern void Source (int sourceId, ALSourcei i, int a, int b, int c); + + public static void Source (int sourceId, ALSourcei i, int a) + { + Source (sourceId, (int)i, a); + } + + public static void Source (int sourceId, ALSourceb i, bool a) { + Source (sourceId, (int)i, a ? 1 : 0); + } + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourcef")] + public static extern void Source (int sourceId, ALSourcef i, float a); + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSource3f")] + public static extern void Source (int sourceId, ALSource3f i, float x, float y, float z); + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetSourcei")] + public static extern void GetSource (int sourceId, ALGetSourcei i, out int state); + + public static ALSourceState GetSourceState(int sourceId) { + int state = (int)ALSourceState.Stopped; + GetSource (sourceId, ALGetSourcei.SourceState, out state); + return (ALSourceState)state; + } + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetListener3f")] + public static extern void GetListener (ALListener3f param, out float value1, out float value2, out float value3); + + public static void DistanceModel(ALDistanceModel model) { } + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceQueueBuffers")] + public unsafe static extern void SourceQueueBuffers (int sourceId, int numEntries, [In] int* buffers); + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceUnqueueBuffers")] + public unsafe static extern void SourceUnqueueBuffers (int sourceId, int numEntries, [In] int* salvaged); + + [CLSCompliant (false)] + public unsafe static void SourceQueueBuffers (int sourceId, int numEntries, int [] buffers) + { + fixed (int* ptr = &buffers[0]) { + AL.SourceQueueBuffers (sourceId, numEntries, ptr); + } + } + + public unsafe static void SourceQueueBuffer (int sourceId, int buffer) + { + AL.SourceQueueBuffers (sourceId, 1, &buffer); + } + + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alSourceUnqueueBuffers")] + public static extern void SourceUnqueueBuffers (int sid, int numEntries, [Out] int [] bids); + + public static unsafe int [] SourceUnqueueBuffers (int sourceId, int numEntries) + { + if (numEntries <= 0) { + throw new ArgumentOutOfRangeException ("numEntries", "Must be greater than zero."); + } + int [] array = new int [numEntries]; + fixed (int* ptr = &array [0]) + { + AL.SourceUnqueueBuffers (sourceId, numEntries, ptr); + } + return array; + } + + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetEnumValue")] + public static extern int GetEnumValue (string enumName); + + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alIsExtensionPresent")] + public static extern bool IsExtensionPresent (string extensionName); + + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetProcAddress")] + public static extern IntPtr GetProcAddress (string functionName); + + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alGetString")] + private static extern IntPtr alGetString (int p); + + public static string GetString (int p) + { + return Marshal.PtrToStringAnsi (alGetString (p)); + } + + public static string Get (ALGetString p) + { + return GetString ((int)p); + } + } + + public partial class Alc + { + public const string NativeLibName = "soft_oal.dll"; + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCreateContext")] + public static extern IntPtr CreateContext (IntPtr device, int [] attributes); + + public static AlcError GetError() + { + return GetError (IntPtr.Zero); + } + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetError")] + public static extern AlcError GetError (IntPtr device); + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetCurrentContext")] + public static extern IntPtr GetCurrentContext (); + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcMakeContextCurrent")] + public static extern void MakeContextCurrent (IntPtr context); + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcDestroyContext")] + public static extern void DestroyContext (IntPtr context); + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcCloseDevice")] + public static extern void CloseDevice (IntPtr device); + + [CLSCompliant (false)] + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcOpenDevice")] + public static extern IntPtr OpenDevice ([MarshalAs (UnmanagedType.LPStr)] string device); + + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcIsExtensionPresent")] + public static extern bool IsExtensionPresent (IntPtr device, [MarshalAs (UnmanagedType.LPStr)] string extensionName); + + [DllImport (NativeLibName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "alcGetString")] + internal static extern IntPtr alGetString (IntPtr device, int p); + + public static string GetString (IntPtr device, int p) + { + return Marshal.PtrToStringAnsi (alGetString (device, p)); + } + + public static string GetString (IntPtr device, AlcGetString p) + { + return GetString (device, (int)p); + } + } + + public class XRamExtension + { + public enum XRamStorage + { + Automatic, + Hardware, + Accessible + } + + private int RamSize; + private int RamFree; + private int StorageAuto; + private int StorageHardware; + private int StorageAccessible; + + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate bool SetBufferModeDelegate (int n, ref int buffers, int value); + + private SetBufferModeDelegate setBufferMode; + + public XRamExtension () + { + IsInitialized = false; + if (!AL.IsExtensionPresent ("EAX-RAM")) { + return; + } + RamSize = AL.GetEnumValue ("AL_EAX_RAM_SIZE"); + RamFree = AL.GetEnumValue ("AL_EAX_RAM_FREE"); + StorageAuto = AL.GetEnumValue ("AL_STORAGE_AUTOMATIC"); + StorageHardware = AL.GetEnumValue ("AL_STORAGE_HARDWARE"); + StorageAccessible = AL.GetEnumValue ("AL_STORAGE_ACCESSIBLE"); + if (RamSize == 0 || RamFree == 0 || StorageAuto == 0 || StorageHardware == 0 || StorageAccessible == 0) { + return; + } + try { + setBufferMode = (XRamExtension.SetBufferModeDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("EAXSetBufferMode"), typeof (XRamExtension.SetBufferModeDelegate)); + } catch (Exception) { + return; + } + IsInitialized = true; + } + + public bool IsInitialized { get; private set; } + + public bool SetBufferMode(int i, ref int id, XRamStorage storage) { + if (storage == XRamExtension.XRamStorage.Accessible) { + return setBufferMode (i, ref id, StorageAccessible); + } + if (storage != XRamExtension.XRamStorage.Hardware) { + return setBufferMode (i, ref id, StorageAuto); + } + return setBufferMode (i, ref id, StorageHardware); + } + } + + [CLSCompliant (false)] + public class EffectsExtension + { + /* Effect API */ + + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate void alGenEffectsDelegate (int n, out uint effect); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate void alDeleteEffectsDelegate (int n, ref int effect); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate bool alIsEffectDelegate (uint effect); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate void alEffectfDelegate (uint effect, EfxEffectf param, float value); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate void alEffectiDelegate (uint effect, EfxEffecti param, int value); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate void alGenAuxiliaryEffectSlotsDelegate (int n, out uint effectslots); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate void alDeleteAuxiliaryEffectSlotsDelegate (int n, ref int effectslots); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate void alAuxiliaryEffectSlotiDelegate (uint slot, EfxEffecti type, uint effect); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate void alAuxiliaryEffectSlotfDelegate (uint slot, EfxEffectSlotf param, float value); + + /* Filter API */ + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private unsafe delegate void alGenFiltersDelegate (int n, [Out] uint* filters); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate void alFilteriDelegate (uint fid, EfxFilteri param, int value); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private delegate void alFilterfDelegate (uint fid, EfxFilterf param, float value); + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + private unsafe delegate void alDeleteFiltersDelegate (int n, [In] uint* filters); + + + private alGenEffectsDelegate alGenEffects; + private alDeleteEffectsDelegate alDeleteEffects; + private alIsEffectDelegate alIsEffect; + private alEffectfDelegate alEffectf; + private alEffectiDelegate alEffecti; + private alGenAuxiliaryEffectSlotsDelegate alGenAuxiliaryEffectSlots; + private alDeleteAuxiliaryEffectSlotsDelegate alDeleteAuxiliaryEffectSlots; + private alAuxiliaryEffectSlotiDelegate alAuxiliaryEffectSloti; + private alAuxiliaryEffectSlotfDelegate alAuxiliaryEffectSlotf; + private alGenFiltersDelegate alGenFilters; + private alFilteriDelegate alFilteri; + private alFilterfDelegate alFilterf; + private alDeleteFiltersDelegate alDeleteFilters; + + internal static IntPtr device; + static EffectsExtension _instance; + public static EffectsExtension Instance { + get { + if (_instance == null) + _instance = new EffectsExtension (); + return _instance; + } + } + + public EffectsExtension () + { + IsInitialized = false; + if (!Alc.IsExtensionPresent (device, "ALC_EXT_EFX")) { + return; + } + + alGenEffects = (alGenEffectsDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alGenEffects"), typeof (alGenEffectsDelegate)); + alDeleteEffects = (alDeleteEffectsDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alDeleteEffects"), typeof (alDeleteEffectsDelegate)); + alEffectf = (alEffectfDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alEffectf"), typeof (alEffectfDelegate)); + alEffecti = (alEffectiDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alEffecti"), typeof (alEffectiDelegate)); + alGenAuxiliaryEffectSlots = (alGenAuxiliaryEffectSlotsDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alGenAuxiliaryEffectSlots"), typeof (alGenAuxiliaryEffectSlotsDelegate)); + alDeleteAuxiliaryEffectSlots = (alDeleteAuxiliaryEffectSlotsDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alDeleteAuxiliaryEffectSlots"), typeof (alDeleteAuxiliaryEffectSlotsDelegate)); + alAuxiliaryEffectSloti = (alAuxiliaryEffectSlotiDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alAuxiliaryEffectSloti"), typeof (alAuxiliaryEffectSlotiDelegate)); + alAuxiliaryEffectSlotf = (alAuxiliaryEffectSlotfDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alAuxiliaryEffectSlotf"), typeof (alAuxiliaryEffectSlotfDelegate)); + + alGenFilters = (alGenFiltersDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alGenFilters"), typeof (alGenFiltersDelegate)); + alFilteri = (alFilteriDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alFilteri"), typeof (alFilteriDelegate)); + alFilterf = (alFilterfDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alFilterf"), typeof (alFilterfDelegate)); + alDeleteFilters = (alDeleteFiltersDelegate)Marshal.GetDelegateForFunctionPointer (AL.GetProcAddress ("alDeleteFilters"), typeof (alDeleteFiltersDelegate)); + + IsInitialized = true; + } + + public bool IsInitialized { get; private set; } + + /* + + +alEffecti (effect, EfxEffecti.FilterType, (int)EfxEffectType.Reverb); + ALHelper.CheckError ("Failed to set Filter Type."); + + */ + + public void GenAuxiliaryEffectSlots (int count, out uint slot) + { + this.alGenAuxiliaryEffectSlots (count, out slot); + ALHelper.CheckError ("Failed to Genereate Aux slot"); + } + + public void GenEffect (out uint effect) + { + this.alGenEffects (1, out effect); + ALHelper.CheckError ("Failed to Generate Effect."); + } + + public void DeleteAuxiliaryEffectSlot (int slot) + { + alDeleteAuxiliaryEffectSlots (1, ref slot); + } + + public void DeleteEffect (int effect) + { + alDeleteEffects (1, ref effect); + } + + public void BindEffectToAuxiliarySlot (uint slot, uint effect) + { + alAuxiliaryEffectSloti (slot,EfxEffecti.SlotEffect, effect); + ALHelper.CheckError ("Failed to bind Effect"); + } + + public void AuxiliaryEffectSlot (uint slot, EfxEffectSlotf param, float value) + { + alAuxiliaryEffectSlotf (slot, param, value); + ALHelper.CheckError ("Failes to set " + param + " " + value); + } + + public void BindSourceToAuxiliarySlot (int SounceId, int slot, int slotnumber, int filter) + { + AL.Source (SounceId, ALSourcei.EfxAuxilarySendFilter, slot, slotnumber, filter); + } + + public void Effect (uint effect, EfxEffectf param, float value) + { + alEffectf (effect, param, value); + ALHelper.CheckError ("Failed to set " + param + " " + value); + } + + public void Effect (uint effect, EfxEffecti param, int value) + { + alEffecti (effect, param, value); + ALHelper.CheckError ("Failed to set " + param + " " + value); + } + + public unsafe int GenFilter() { + uint filter = 0; + this.alGenFilters (1, &filter); + return (int)filter; + } + public void Filter(int sourceId, EfxFilteri filter, int EfxFilterType) { + this.alFilteri ((uint)sourceId, filter, EfxFilterType); + } + public void Filter(int sourceId, EfxFilterf filter, float EfxFilterType) { + this.alFilterf ((uint)sourceId, filter, EfxFilterType); + } + public void BindFilterToSource(int sourceId, int filterId) { + AL.Source (sourceId, ALSourcei.EfxDirectFilter, filterId); + } + public unsafe void DeleteFilter (int filterId) + { + alDeleteFilters (1, (uint*)&filterId); + } + } +} + diff --git a/MonoGame.Framework/Audio/OpenALSoundController.cs b/MonoGame.Framework/Audio/OpenALSoundController.cs index dfcccd9fd91..a980464af0a 100644 --- a/MonoGame.Framework/Audio/OpenALSoundController.cs +++ b/MonoGame.Framework/Audio/OpenALSoundController.cs @@ -7,12 +7,22 @@ #if MONOMAC && PLATFORM_MACOS_LEGACY using MonoMac.OpenAL; -#else +#endif +#if MONOMAC && !PLATFORM_MACOS_LEGACY using OpenTK.Audio.OpenAL; using OpenTK.Audio; +#endif + +#if GLES +using OpenTK.Audio.OpenAL; using OpenTK; #endif +#if DESKTOPGL +using OpenAL; +#endif +using OpenGL; + #if ANDROID using System.Globalization; using Android.Content.PM; @@ -47,10 +57,19 @@ public static void CheckError(string message = "", params object[] args) internal sealed class OpenALSoundController : IDisposable { private static OpenALSoundController _instance = null; +#if SUPPORTS_EFX + private static EffectsExtension _efx = null; +#endif private IntPtr _device; - private ContextHandle _context; - //int outputSource; - //int[] buffers; +#if !DESKTOPGL + ContextHandle _context; + ContextHandle NullContext = ContextHandle.Zero; +#else + private IntPtr _context; + IntPtr NullContext = IntPtr.Zero; +#endif + //int outputSource; + //int[] buffers; private AlcError _lastOpenALError; private int[] allSourcesArray; #if DESKTOPGL || ANGLE @@ -81,24 +100,25 @@ internal sealed class OpenALSoundController : IDisposable private const int DEFAULT_UPDATE_SIZE = 512; private const int DEFAULT_UPDATE_BUFFER_COUNT = 2; #elif DESKTOPGL - #pragma warning disable 414 - private static AudioContext _acontext; - #pragma warning restore 414 private static OggStreamer _oggstreamer; #endif private List availableSourcesCollection; private List inUseSourcesCollection; - private List playingSourcesCollection; - private List purgeMe; private bool _bSoundAvailable = false; private Exception _SoundInitException; // Here to bubble back up to the developer bool _isDisposed; + public bool SupportsADPCM = false; /// /// Sets up the hardware resources used by the controller. /// private OpenALSoundController() { +#if WINDOWS + // On Windows, set the DLL search path for correct native binaries + NativeHelper.InitDllDirectory(); +#endif + if (!OpenSoundController()) { return; @@ -109,11 +129,14 @@ private OpenALSoundController() allSourcesArray = new int[MAX_NUMBER_OF_SOURCES]; AL.GenSources(allSourcesArray); ALHelper.CheckError("Failed to generate sources."); - + Filter = 0; +#if SUPPORTS_EFX + if (Efx.IsInitialized) { + Filter = Efx.GenFilter (); + } +#endif availableSourcesCollection = new List(allSourcesArray); inUseSourcesCollection = new List(); - playingSourcesCollection = new List(); - purgeMe = new List(); } ~OpenALSoundController() @@ -137,6 +160,9 @@ private bool OpenSoundController() try { _device = Alc.OpenDevice(string.Empty); +#if DESKTOPGL + EffectsExtension.device = _device; +#endif } catch (Exception ex) { @@ -240,16 +266,13 @@ Now use OpenSL ES to create an AudioPlayer with PCM buffer queue data locator. AVAudioSession.Notifications.ObserveInterruption(handler); int[] attribute = new int[0]; -#elif !DESKTOPGL +#else int[] attribute = new int[0]; #endif + _context = Alc.CreateContext(_device, attribute); #if DESKTOPGL - _acontext = new AudioContext(); - _context = Alc.GetCurrentContext(); _oggstreamer = new OggStreamer(); -#else - _context = Alc.CreateContext(_device, attribute); #endif if (CheckALError("Could not create AL context")) @@ -258,7 +281,7 @@ Now use OpenSL ES to create an AudioPlayer with PCM buffer queue data locator. return(false); } - if (_context != ContextHandle.Zero) + if (_context != NullContext) { Alc.MakeContextCurrent(_context); if (CheckALError("Could not make AL context current")) @@ -266,6 +289,7 @@ Now use OpenSL ES to create an AudioPlayer with PCM buffer queue data locator. CleanUpOpenAL(); return(false); } + SupportsADPCM = AL.IsExtensionPresent ("AL_SOFT_MSADPCM"); return (true); } } @@ -279,6 +303,18 @@ public static OpenALSoundController GetInstance { return _instance; } } +#if SUPPORTS_EFX + public static EffectsExtension Efx { + get { + if (_efx == null) + _efx = new EffectsExtension (); + return _efx; + } + } +#endif + public int Filter { + get; private set; + } public static void DestroyInstance() { @@ -317,25 +353,19 @@ public bool CheckALError (string operation) /// private void CleanUpOpenAL() { - Alc.MakeContextCurrent(ContextHandle.Zero); -#if DESKTOPGL - if (_acontext != null) - { - _acontext.Dispose(); - _acontext = null; - } -#else - if (_context != ContextHandle.Zero) + Alc.MakeContextCurrent(NullContext); + + if (_context != NullContext) { Alc.DestroyContext (_context); - _context = ContextHandle.Zero; + _context = NullContext; } if (_device != IntPtr.Zero) { Alc.CloseDevice (_device); _device = IntPtr.Zero; } -#endif + _bSoundAvailable = false; } @@ -369,7 +399,10 @@ void Dispose(bool disposing) AL.DeleteSource(allSourcesArray[i]); ALHelper.CheckError("Failed to delete source."); } - +#if SUPPORTS_EFX + if (Filter != 0 && Efx.IsInitialized) + Efx.DeleteFilter (Filter); +#endif CleanUpOpenAL(); } } @@ -378,27 +411,31 @@ void Dispose(bool disposing) } /// - /// Reserves the given sound buffer. If there are no available sources then false is - /// returned, otherwise true will be returned and the sound buffer can be played. If - /// the controller was not able to setup the hardware, then false will be returned. + /// Reserves a sound buffer and return its identifier. If there are no available sources + /// or the controller was not able to setup the hardware then an + /// is thrown. /// - /// The sound buffer you want to play - /// True if the buffer can be played, and false if not. + /// The source number of the reserved sound buffer. public int ReserveSource() { if (!CheckInitState()) { throw new InstancePlayLimitException(); } + int sourceNumber; - if (availableSourcesCollection.Count == 0) - { - throw new InstancePlayLimitException(); - } - - sourceNumber = availableSourcesCollection.First (); - inUseSourcesCollection.Add(sourceNumber); - availableSourcesCollection.Remove (sourceNumber); + + lock (availableSourcesCollection) + { + if (availableSourcesCollection.Count == 0) + { + throw new InstancePlayLimitException(); + } + + sourceNumber = availableSourcesCollection.Last(); + inUseSourcesCollection.Add(sourceNumber); + availableSourcesCollection.Remove(sourceNumber); + } return sourceNumber; } @@ -409,29 +446,16 @@ public void RecycleSource(int sourceId) { return; } - inUseSourcesCollection.Remove(sourceId); - availableSourcesCollection.Add(sourceId); - } - public void PlaySound(SoundEffectInstance inst) - { - if (!CheckInitState()) - { - return; - } - lock (playingSourcesCollection) + lock (availableSourcesCollection) { - playingSourcesCollection.Add(inst.SourceId); + inUseSourcesCollection.Remove(sourceId); + availableSourcesCollection.Add(sourceId); } - AL.SourcePlay(inst.SourceId); - ALHelper.CheckError("Failed to play source."); } public void FreeSource(SoundEffectInstance inst) { - lock (playingSourcesCollection) { - playingSourcesCollection.Remove(inst.SourceId); - } RecycleSource(inst.SourceId); inst.SourceId = 0; inst.HasSourceId = false; @@ -471,47 +495,6 @@ public double SourceCurrentPosition (int sourceId) return pos; } - /// - /// Called repeatedly, this method cleans up the state of the management lists. This method - /// will also lock on the playingSourcesCollection. Sources that are stopped will be recycled - /// using the RecycleSource method. - /// - public void Update() - { - if (!_bSoundAvailable) - { - //OK to ignore this here because the game can run without sound. - return; - } - - ALSourceState state; - lock (playingSourcesCollection) - { - for (int i = playingSourcesCollection.Count - 1; i >= 0; --i) - { - int sourceId = playingSourcesCollection[i]; - state = AL.GetSourceState(sourceId); - ALHelper.CheckError("Failed to get source state."); - if (state == ALSourceState.Stopped) - { - purgeMe.Add(sourceId); - playingSourcesCollection.RemoveAt(i); - } - } - } - lock (purgeMe) - { - foreach (int sourceId in purgeMe) - { - AL.Source(sourceId, ALSourcei.Buffer, 0); - ALHelper.CheckError("Failed to free source from buffer."); - inUseSourcesCollection.Remove(sourceId); - availableSourcesCollection.Add(sourceId); - } - purgeMe.Clear(); - } - } - #if ANDROID const string Lib = "openal32.dll"; const CallingConvention Style = CallingConvention.Cdecl; diff --git a/MonoGame.Framework/Audio/SoundEffect.OpenAL.cs b/MonoGame.Framework/Audio/SoundEffect.OpenAL.cs index 3a0d6aea688..7660542c121 100644 --- a/MonoGame.Framework/Audio/SoundEffect.OpenAL.cs +++ b/MonoGame.Framework/Audio/SoundEffect.OpenAL.cs @@ -8,16 +8,16 @@ #if MONOMAC && PLATFORM_MACOS_LEGACY using MonoMac.AudioToolbox; using MonoMac.AudioUnit; -using MonoMac.AVFoundation; -using MonoMac.Foundation; using MonoMac.OpenAL; #elif OPENAL +#if GLES || MONOMAC using OpenTK.Audio.OpenAL; +#else +using OpenAL; +#endif #if IOS || MONOMAC using AudioToolbox; using AudioUnit; -using AVFoundation; -using Foundation; #endif #endif @@ -26,8 +26,8 @@ namespace Microsoft.Xna.Framework.Audio public sealed partial class SoundEffect : IDisposable { internal const int MAX_PLAYING_INSTANCES = OpenALSoundController.MAX_NUMBER_OF_SOURCES; - - internal byte[] _data; + internal static uint ReverbSlot = 0; + internal static uint ReverbEffect = 0; internal OALSoundBuffer SoundBuffer; @@ -39,8 +39,10 @@ public sealed partial class SoundEffect : IDisposable #region Public Constructors - private void PlatformLoadAudioStream(Stream s) + private void PlatformLoadAudioStream(Stream s, out TimeSpan duration) { + byte[] buffer; + #if OPENAL && !(MONOMAC || IOS) ALFormat format; @@ -48,33 +50,20 @@ private void PlatformLoadAudioStream(Stream s) int freq; var stream = s; -#if ANDROID - var needsDispose = false; - try - { - // If seek is not supported (usually an indicator of a stream opened into the AssetManager), then copy - // into a temporary MemoryStream. - if (!s.CanSeek) - { - needsDispose = true; - stream = new MemoryStream(); - s.CopyTo(stream); - stream.Position = 0; - } -#endif - _data = AudioLoader.Load(stream, out format, out size, out freq); -#if ANDROID - } - finally - { - if (needsDispose) - stream.Dispose(); - } -#endif + + buffer = AudioLoader.Load(stream, out format, out size, out freq); + Format = format; Size = size; Rate = freq; + var bytesPerSecond = freq; + if (format == ALFormat.Mono16 || format == ALFormat.Stereo8) + bytesPerSecond *= 2; + else if (format == ALFormat.Stereo16) + bytesPerSecond *= 4; + + duration = TimeSpan.FromSeconds((float) size / bytesPerSecond); #endif #if MONOMAC || IOS @@ -87,34 +76,16 @@ private void PlatformLoadAudioStream(Stream s) afs.ParseBytes (audiodata, false); Size = (int)afs.DataByteCount; - _data = new byte[afs.DataByteCount]; - Array.Copy (audiodata, afs.DataOffset, _data, 0, afs.DataByteCount); + buffer = new byte[afs.DataByteCount]; + Array.Copy (audiodata, afs.DataOffset, buffer, 0, afs.DataByteCount); AudioStreamBasicDescription asbd = afs.DataFormat; int channelsPerFrame = asbd.ChannelsPerFrame; int bitsPerChannel = asbd.BitsPerChannel; - // There is a random chance that properties asbd.ChannelsPerFrame and asbd.BitsPerChannel are invalid because of a bug in Xamarin.iOS - // See: https://bugzilla.xamarin.com/show_bug.cgi?id=11074 (Failed to get buffer attributes error when playing sounds) - if (channelsPerFrame <= 0 || bitsPerChannel <= 0) - { - NSError err; - using (NSData nsData = NSData.FromArray(audiodata)) - using (AVAudioPlayer player = AVAudioPlayer.FromData(nsData, out err)) - { - channelsPerFrame = (int)player.NumberOfChannels; - bitsPerChannel = player.SoundSetting.LinearPcmBitDepth.GetValueOrDefault(16); - - Rate = (float)player.SoundSetting.SampleRate; - _duration = TimeSpan.FromSeconds(player.Duration); - } - } - else - { - Rate = (float)asbd.SampleRate; - double duration = (Size / ((bitsPerChannel / 8) * channelsPerFrame)) / asbd.SampleRate; - _duration = TimeSpan.FromSeconds(duration); - } + Rate = (float)asbd.SampleRate; + double durationSec = (Size / ((bitsPerChannel / 8) * channelsPerFrame)) / asbd.SampleRate; + duration = TimeSpan.FromSeconds(durationSec); if (channelsPerFrame == 1) Format = (bitsPerChannel == 8) ? ALFormat.Mono8 : ALFormat.Mono16; @@ -123,44 +94,76 @@ private void PlatformLoadAudioStream(Stream s) } #endif + // bind buffer + SoundBuffer = new OALSoundBuffer(); + SoundBuffer.BindDataBuffer(buffer, Format, Size, (int)Rate); } - private void PlatformInitialize(byte[] buffer, int sampleRate, AudioChannels channels) + private void PlatformInitializePcm(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength) { - Rate = (float)sampleRate; - Size = (int)buffer.Length; - -#if OPENAL && !(MONOMAC || IOS) + Rate = (float)sampleRate; + Size = (int)count; + Format = channels == AudioChannels.Stereo ? ALFormat.Stereo16 : ALFormat.Mono16; - _data = buffer; - Format = (channels == AudioChannels.Stereo) ? ALFormat.Stereo16 : ALFormat.Mono16; - -#endif + // bind buffer + SoundBuffer = new OALSoundBuffer(); + SoundBuffer.BindDataBuffer(buffer, Format, Size, (int)Rate); + } -#if MONOMAC || IOS + private void PlatformInitializeAdpcm (byte [] buffer, int offset, int count, int sampleRate, AudioChannels channels, int dataFormat, int loopStart, int loopLength) + { + Rate = (float)sampleRate; + Size = (int)count; + #if DESKTOPGL + Format = channels == AudioChannels.Stereo ? ALFormat.StereoMSADPCM : ALFormat.MonoMSADPCM; + #else + Format = channels == AudioChannels.Stereo ? (ALFormat)0x1303 : (ALFormat)0x1302; + #endif - //buffer should contain 16-bit PCM wave data - short bitsPerSample = 16; + // bind buffer + SoundBuffer = new OALSoundBuffer (); + SoundBuffer.BindDataBuffer (buffer, Format, Size, (int)Rate, dataFormat); + } - if ((int)channels <= 1) - Format = bitsPerSample == 8 ? ALFormat.Mono8 : ALFormat.Mono16; - else - Format = bitsPerSample == 8 ? ALFormat.Stereo8 : ALFormat.Stereo16; + private void PlatformInitializeFormat(byte[] header, byte[] buffer, int bufferSize, int loopStart, int loopLength) + { + var format = BitConverter.ToInt16(header, 0); + var channels = BitConverter.ToInt16(header, 2); + var sampleRate = BitConverter.ToInt32(header, 4); + var blockAlignment = BitConverter.ToInt16(header, 12); + + // We need to decode MSADPCM. + var supportsADPCM = OpenALSoundController.GetInstance.SupportsADPCM; + if (format == 2 && !supportsADPCM) + { + using (var stream = new MemoryStream(buffer)) + using (var reader = new BinaryReader(stream)) + { + buffer = MSADPCMToPCM.MSADPCM_TO_PCM (reader, (short)channels, (short)blockAlignment); + format = 1; + } + } - _name = ""; - _data = buffer; + if (!supportsADPCM && format != 1) + throw new NotSupportedException("Unsupported wave format!"); -#endif - // bind buffer - SoundBuffer = new OALSoundBuffer(); - SoundBuffer.BindDataBuffer(_data, Format, Size, (int)Rate); + if (supportsADPCM && format == 2) { + PlatformInitializeAdpcm(buffer, 0, buffer.Length, sampleRate, (AudioChannels)channels, blockAlignment, loopStart, loopLength); + } else { + PlatformInitializePcm (buffer, 0, buffer.Length, sampleRate, (AudioChannels)channels, loopStart, loopLength); + } } - private void PlatformInitialize(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength) + private void PlatformInitializeXact(MiniFormatTag codec, byte[] buffer, int channels, int sampleRate, int blockAlignment, int loopStart, int loopLength, out TimeSpan duration) { - _duration = GetSampleDuration(buffer.Length, sampleRate, channels); + if (codec == MiniFormatTag.Adpcm) + { + PlatformInitializeAdpcm(buffer, 0, buffer.Length, sampleRate, (AudioChannels)channels, (blockAlignment + 16) * 2, loopStart, loopLength); + duration = TimeSpan.FromSeconds(SoundBuffer.Duration); + return; + } - throw new NotImplementedException(); + throw new NotSupportedException("Unsupported sound format!"); } #endregion @@ -174,7 +177,53 @@ private void PlatformSetupInstance(SoundEffectInstance inst) #endregion - #region IDisposable Members + internal static void PlatformSetReverbSettings(ReverbSettings reverbSettings) + { +#if SUPPORTS_EFX + if (!OpenALSoundController.Efx.IsInitialized) + return; + + if (ReverbEffect != 0) + return; + + var efx = OpenALSoundController.Efx; + efx.GenAuxiliaryEffectSlots (1, out ReverbSlot); + efx.GenEffect (out ReverbEffect); + efx.Effect (ReverbEffect, EfxEffecti.EffectType, (int)EfxEffectType.Reverb); + efx.Effect (ReverbEffect, EfxEffectf.EaxReverbReflectionsDelay, reverbSettings.ReflectionsDelayMs / 1000.0f); + efx.Effect (ReverbEffect, EfxEffectf.LateReverbDelay, reverbSettings.ReverbDelayMs / 1000.0f); + // map these from range 0-15 to 0-1 + efx.Effect (ReverbEffect, EfxEffectf.EaxReverbDiffusion, reverbSettings.EarlyDiffusion / 15f); + efx.Effect (ReverbEffect, EfxEffectf.EaxReverbDiffusion, reverbSettings.LateDiffusion / 15f); + efx.Effect (ReverbEffect, EfxEffectf.EaxReverbGainLF, Math.Min (XactHelpers.ParseVolumeFromDecibels (reverbSettings.LowEqGain - 8f), 1.0f)); + efx.Effect (ReverbEffect, EfxEffectf.EaxReverbLFReference, (reverbSettings.LowEqCutoff * 50f) + 50f); + efx.Effect (ReverbEffect, EfxEffectf.EaxReverbGainHF, XactHelpers.ParseVolumeFromDecibels (reverbSettings.HighEqGain - 8f)); + efx.Effect (ReverbEffect, EfxEffectf.EaxReverbHFReference, (reverbSettings.HighEqCutoff * 500f) + 1000f); + // According to Xamarin docs EaxReverbReflectionsGain Unit: Linear gain Range [0.0f .. 3.16f] Default: 0.05f + efx.Effect (ReverbEffect, EfxEffectf.EaxReverbReflectionsGain, Math.Min (XactHelpers.ParseVolumeFromDecibels (reverbSettings.ReflectionsGainDb), 3.16f)); + efx.Effect (ReverbEffect, EfxEffectf.EaxReverbGain, Math.Min (XactHelpers.ParseVolumeFromDecibels (reverbSettings.ReverbGainDb), 1.0f)); + // map these from 0-100 down to 0-1 + efx.Effect (ReverbEffect, EfxEffectf.EaxReverbDensity, reverbSettings.DensityPct / 100f); + efx.AuxiliaryEffectSlot (ReverbSlot, EfxEffectSlotf.EffectSlotGain, reverbSettings.WetDryMixPct / 200f); + + // Dont know what to do with these EFX has no mapping for them. Just ignore for now + // we can enable them as we go. + //efx.SetEffectParam (ReverbEffect, EfxEffectf.PositionLeft, reverbSettings.PositionLeft); + //efx.SetEffectParam (ReverbEffect, EfxEffectf.PositionRight, reverbSettings.PositionRight); + //efx.SetEffectParam (ReverbEffect, EfxEffectf.PositionLeftMatrix, reverbSettings.PositionLeftMatrix); + //efx.SetEffectParam (ReverbEffect, EfxEffectf.PositionRightMatrix, reverbSettings.PositionRightMatrix); + //efx.SetEffectParam (ReverbEffect, EfxEffectf.LowFrequencyReference, reverbSettings.RearDelayMs); + //efx.SetEffectParam (ReverbEffect, EfxEffectf.LowFrequencyReference, reverbSettings.RoomFilterFrequencyHz); + //efx.SetEffectParam (ReverbEffect, EfxEffectf.LowFrequencyReference, reverbSettings.RoomFilterMainDb); + //efx.SetEffectParam (ReverbEffect, EfxEffectf.LowFrequencyReference, reverbSettings.RoomFilterHighFrequencyDb); + //efx.SetEffectParam (ReverbEffect, EfxEffectf.LowFrequencyReference, reverbSettings.DecayTimeSec); + //efx.SetEffectParam (ReverbEffect, EfxEffectf.LowFrequencyReference, reverbSettings.RoomSizeFeet); + + efx.BindEffectToAuxiliarySlot (ReverbSlot, ReverbEffect); +#endif + } + +#region IDisposable Members private void PlatformDispose(bool disposing) { @@ -185,10 +234,29 @@ private void PlatformDispose(bool disposing) } } - #endregion +#endregion + + internal static void InitializeSoundEffect() + { + try + { + // Getting the instance for the first time initializes OpenAL + var oal = OpenALSoundController.GetInstance; + } + catch (DllNotFoundException ex) + { + throw new NoAudioHardwareException("Failed to init OpenALSoundController", ex); + } + } internal static void PlatformShutdown() { +#if SUPPORTS_EFX + if (ReverbEffect != 0) { + OpenALSoundController.Efx.DeleteAuxiliaryEffectSlot ((int)ReverbSlot); + OpenALSoundController.Efx.DeleteEffect ((int)ReverbEffect); + } +#endif OpenALSoundController.DestroyInstance(); } } diff --git a/MonoGame.Framework/Audio/SoundEffect.Web.cs b/MonoGame.Framework/Audio/SoundEffect.Web.cs index 8b971b6c45f..aec6ae999e4 100644 --- a/MonoGame.Framework/Audio/SoundEffect.Web.cs +++ b/MonoGame.Framework/Audio/SoundEffect.Web.cs @@ -17,18 +17,24 @@ public sealed partial class SoundEffect : IDisposable // This platform is only limited by memory. internal const int MAX_PLAYING_INSTANCES = int.MaxValue; - private void PlatformLoadAudioStream(Stream s) + private void PlatformLoadAudioStream(Stream s, out TimeSpan duration) { + duration = TimeSpan.Zero; } - private void PlatformInitialize(byte[] buffer, int sampleRate, AudioChannels channels) + private void PlatformInitializePcm(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength) { } - - private void PlatformInitialize(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength) + + private void PlatformInitializeFormat(byte[] header, byte[] buffer, int bufferSize, int loopStart, int loopLength) + { + } + + private void PlatformInitializeXact(MiniFormatTag codec, byte[] buffer, int channels, int sampleRate, int blockAlignment, int loopStart, int loopLength, out TimeSpan duration) { + throw new NotSupportedException("Unsupported sound format!"); } - + private void PlatformSetupInstance(SoundEffectInstance instance) { } @@ -37,6 +43,14 @@ private void PlatformDispose(bool disposing) { } + internal static void PlatformSetReverbSettings(ReverbSettings reverbSettings) + { + } + + internal static void InitializeSoundEffect() + { + } + internal static void PlatformShutdown() { } diff --git a/MonoGame.Framework/Audio/SoundEffect.XAudio.cs b/MonoGame.Framework/Audio/SoundEffect.XAudio.cs index 26d16f2051d..c2a6389b754 100644 --- a/MonoGame.Framework/Audio/SoundEffect.XAudio.cs +++ b/MonoGame.Framework/Audio/SoundEffect.XAudio.cs @@ -12,7 +12,7 @@ namespace Microsoft.Xna.Framework.Audio { - public sealed partial class SoundEffect : IDisposable + partial class SoundEffect { #if WINDOWS || (WINRT && !WINDOWS_PHONE) @@ -65,6 +65,29 @@ internal static X3DAudio Device3D } } + + private static SubmixVoice _reverbVoice; + + internal static SubmixVoice ReverbVoice + { + get + { + if (_reverbVoice == null) + { + var details = MasterVoice.VoiceDetails; + _reverbVoice = new SubmixVoice(Device, details.InputChannelCount, details.InputSampleRate); + + var reverb = new SharpDX.XAudio2.Fx.Reverb(); + var desc = new EffectDescriptor(reverb); + desc.InitialState = true; + desc.OutputChannelCount = details.InputChannelCount; + _reverbVoice.SetEffectChain(desc); + } + + return _reverbVoice; + } + } + #endregion internal DataStream _dataStream; @@ -74,11 +97,9 @@ internal static X3DAudio Device3D #region Initialization - static SoundEffect() - { - InitializeSoundEffect(); - } - + /// + /// Initializes XAudio. + /// internal static void InitializeSoundEffect() { try @@ -135,31 +156,74 @@ internal static void InitializeSoundEffect() } } - private void PlatformInitialize(byte[] buffer, int sampleRate, AudioChannels channels) + private static DataStream ToDataStream(int offset, byte[] buffer, int length) { - CreateBuffers( new WaveFormat(sampleRate, (int)channels), - DataStream.Create(buffer, true, false), - 0, - buffer.Length); + // NOTE: We make a copy here because old versions of + // DataStream.Create didn't work correctly for offsets. + var data = new byte[length - offset]; + Buffer.BlockCopy(buffer, offset, data, 0, length - offset); + + return DataStream.Create(data, true, false); } - private void PlatformInitialize(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength) + private void PlatformInitializePcm(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength) { CreateBuffers( new WaveFormat(sampleRate, (int)channels), - DataStream.Create(buffer, true, false, offset), + ToDataStream(offset, buffer, count), loopStart, loopLength); } - private void PlatformLoadAudioStream(Stream s) + private void PlatformInitializeFormat(byte[] header, byte[] buffer, int bufferSize, int loopStart, int loopLength) + { + var format = BitConverter.ToInt16(header, 0); + var channels = BitConverter.ToInt16(header, 2); + var sampleRate = BitConverter.ToInt32(header, 4); + var blockAlignment = BitConverter.ToInt16(header, 12); + + WaveFormat waveFormat; + if (format == 1) + waveFormat = new WaveFormat(sampleRate, channels); + else if (format == 2) + waveFormat = new WaveFormatAdpcm(sampleRate, channels, blockAlignment); + else + throw new NotSupportedException("Unsupported wave format!"); + + CreateBuffers( waveFormat, + ToDataStream(0, buffer, bufferSize), + loopStart, + loopLength); + } + + private void PlatformInitializeXact(MiniFormatTag codec, byte[] buffer, int channels, int sampleRate, int blockAlignment, int loopStart, int loopLength, out TimeSpan duration) + { + if (codec == MiniFormatTag.Adpcm) + { + duration = TimeSpan.FromSeconds((float)loopLength / sampleRate); + + CreateBuffers( new WaveFormatAdpcm(sampleRate, channels, blockAlignment), + ToDataStream(0, buffer, buffer.Length), + loopStart, + loopLength); + + return; + } + + throw new NotSupportedException("Unsupported sound format!"); + } + + private void PlatformLoadAudioStream(Stream s, out TimeSpan duration) { var soundStream = new SoundStream(s); + if (soundStream.Format.Encoding != WaveFormatEncoding.Pcm) + throw new ArgumentException("Ensure that the specified stream contains valid PCM mono or stereo wave data."); + var dataStream = soundStream.ToDataStream(); - var sampleLength = (int)(dataStream.Length / ((soundStream.Format.Channels * soundStream.Format.BitsPerSample) / 8)); - CreateBuffers( soundStream.Format, - dataStream, - 0, - sampleLength); + var sampleCount = (int)(dataStream.Length / ((soundStream.Format.Channels * soundStream.Format.BitsPerSample) / 8)); + + CreateBuffers(soundStream.Format, dataStream, 0, sampleCount); + + duration = TimeSpan.FromSeconds((float)sampleCount / soundStream.Format.SampleRate); } private void CreateBuffers(WaveFormat format, DataStream dataStream, int loopStart, int loopLength) @@ -218,14 +282,52 @@ private void PlatformSetupInstance(SoundEffectInstance inst) } if (voice == null && Device != null) - voice = new SourceVoice(Device, _format, VoiceFlags.None, XAudio2.MaximumFrequencyRatio); + { + voice = new SourceVoice(Device, _format, VoiceFlags.UseFilter, XAudio2.MaximumFrequencyRatio); + inst._voice = voice; + inst.UpdateOutputMatrix(); // Ensure the output matrix is set for this new voice + } - inst._voice = voice; inst._format = _format; } #endregion + internal static void PlatformSetReverbSettings(ReverbSettings reverbSettings) + { + // All parameters related to sampling rate or time are relative to a 48kHz + // voice and must be scaled for use with other sampling rates. + var timeScale = 48000.0f / ReverbVoice.VoiceDetails.InputSampleRate; + + var settings = new SharpDX.XAudio2.Fx.ReverbParameters + { + ReflectionsGain = reverbSettings.ReflectionsGainDb, + ReverbGain = reverbSettings.ReverbGainDb, + DecayTime = reverbSettings.DecayTimeSec, + ReflectionsDelay = (byte)(reverbSettings.ReflectionsDelayMs * timeScale), + ReverbDelay = (byte)(reverbSettings.ReverbDelayMs * timeScale), + RearDelay = (byte)(reverbSettings.RearDelayMs * timeScale), + RoomSize = reverbSettings.RoomSizeFeet, + Density = reverbSettings.DensityPct, + LowEQGain = (byte)reverbSettings.LowEqGain, + LowEQCutoff = (byte)reverbSettings.LowEqCutoff, + HighEQGain = (byte)reverbSettings.HighEqGain, + HighEQCutoff = (byte)reverbSettings.HighEqCutoff, + PositionLeft = (byte)reverbSettings.PositionLeft, + PositionRight = (byte)reverbSettings.PositionRight, + PositionMatrixLeft = (byte)reverbSettings.PositionLeftMatrix, + PositionMatrixRight = (byte)reverbSettings.PositionRightMatrix, + EarlyDiffusion = (byte)reverbSettings.EarlyDiffusion, + LateDiffusion = (byte)reverbSettings.LateDiffusion, + RoomFilterMain = reverbSettings.RoomFilterMainDb, + RoomFilterFreq = reverbSettings.RoomFilterFrequencyHz * timeScale, + RoomFilterHF = reverbSettings.RoomFilterHighFrequencyDb, + WetDryMix = reverbSettings.WetDryMixPct + }; + + ReverbVoice.SetEffectParameters(0, settings); + } + private void PlatformDispose(bool disposing) { if (disposing) @@ -238,11 +340,15 @@ private void PlatformDispose(bool disposing) internal static void PlatformShutdown() { - SoundEffectInstancePool.Shutdown(); + if (_reverbVoice != null) + { + _reverbVoice.DestroyVoice(); + _reverbVoice.Dispose(); + _reverbVoice = null; + } if (MasterVoice != null) { - MasterVoice.DestroyVoice(); MasterVoice.Dispose(); MasterVoice = null; } diff --git a/MonoGame.Framework/Audio/SoundEffect.cs b/MonoGame.Framework/Audio/SoundEffect.cs index cb89c484e19..531399ed730 100644 --- a/MonoGame.Framework/Audio/SoundEffect.cs +++ b/MonoGame.Framework/Audio/SoundEffect.cs @@ -9,7 +9,7 @@ namespace Microsoft.Xna.Framework.Audio { /// Represents a loaded sound resource. /// - /// A SoundEffect represents the buffer used to hold audio data and metadata. SoundEffectInstances are used to play from SoundEffects. Multiple SoundEffectinstance objects can be created and played from the same SoundEffect object. + /// A SoundEffect represents the buffer used to hold audio data and metadata. SoundEffectInstances are used to play from SoundEffects. Multiple SoundEffectInstance objects can be created and played from the same SoundEffect object. /// The only limit on the number of loaded SoundEffects is restricted by available memory. When a SoundEffect is disposed, all SoundEffectInstances created from it will become invalid. /// SoundEffect.Play() can be used for 'fire and forget' sounds. If advanced playback controls like volume or pitch is required, use SoundEffect.CreateInstance(). /// @@ -17,44 +17,132 @@ public sealed partial class SoundEffect : IDisposable { #region Internal Audio Data - private string _name; + private string _name = string.Empty; private bool _isDisposed = false; - private TimeSpan _duration = TimeSpan.Zero; + private readonly TimeSpan _duration; #endregion #region Internal Constructors - internal SoundEffect() { } + // Only used from SoundEffect.FromStream. + private SoundEffect(Stream stream) + { + /* + The Stream object must point to the head of a valid PCM wave file. Also, this wave file must be in the RIFF bitstream format. + The audio format has the following restrictions: + Must be a PCM wave file + Can only be mono or stereo + Must be 8 or 16 bit + Sample rate must be between 8,000 Hz and 48,000 Hz + */ + + PlatformLoadAudioStream(stream, out _duration); + } + + // Only used from SoundEffectReader. + internal SoundEffect(byte[] header, byte[] buffer, int bufferSize, int durationMs, int loopStart, int loopLength) + { + _duration = TimeSpan.FromMilliseconds(durationMs); + + // Peek at the format... handle regular PCM data. + var format = BitConverter.ToInt16(header, 0); + if (format == 1) + { + var channels = BitConverter.ToInt16(header, 2); + var sampleRate = BitConverter.ToInt32(header, 4); + PlatformInitializePcm(buffer, 0, bufferSize, sampleRate, (AudioChannels)channels, loopStart, loopLength); + return; + } + + // Everything else is platform specific. + PlatformInitializeFormat(header, buffer, bufferSize, loopStart, loopLength); + } + + // Only used from XACT WaveBank. + internal SoundEffect(MiniFormatTag codec, byte[] buffer, int channels, int sampleRate, int blockAlignment, int loopStart, int loopLength) + { + // Handle the common case... the rest is platform specific. + if (codec == MiniFormatTag.Pcm) + { + _duration = TimeSpan.FromSeconds((float)buffer.Length / (sampleRate * blockAlignment)); + PlatformInitializePcm(buffer, 0, buffer.Length, sampleRate, (AudioChannels)channels, loopStart, loopLength); + return; + } + + PlatformInitializeXact(codec, buffer, channels, sampleRate, blockAlignment, loopStart, loopLength, out _duration); + } #endregion #region Public Constructors - /// Buffer containing PCM wave data. - /// Sample rate, in Hertz (Hz) - /// Number of channels (mono or stereo). + /// + /// Create a sound effect. + /// + /// The buffer with the sound data. + /// The sound data sample rate in hertz. + /// The number of channels in the sound data. + /// This only supports uncompressed 16bit PCM wav data. public SoundEffect(byte[] buffer, int sampleRate, AudioChannels channels) + : this(buffer, 0, buffer != null ? buffer.Length : 0, sampleRate, channels, 0, 0) { - _duration = GetSampleDuration(buffer.Length, sampleRate, channels); - - PlatformInitialize(buffer, sampleRate, channels); } - /// Buffer containing PCM wave data. - /// Offset, in bytes, to the starting position of the audio data. - /// Amount, in bytes, of audio data. - /// Sample rate, in Hertz (Hz) - /// Number of channels (mono or stereo). - /// The position, in samples, where the audio should begin looping. - /// The duration, in samples, that audio should loop over. - /// Use SoundEffect.GetSampleDuration() to convert time to samples. + /// + /// Create a sound effect. + /// + /// The buffer with the sound data. + /// The offset to the start of the sound data in bytes. + /// The length of the sound data in bytes. + /// The sound data sample rate in hertz. + /// The number of channels in the sound data. + /// The position where the sound should begin looping in samples. + /// The duration of the sound data loop in samples. + /// This only supports uncompressed 16bit PCM wav data. public SoundEffect(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength) { + if (sampleRate < 8000 || sampleRate > 48000) + throw new ArgumentOutOfRangeException("sampleRate"); + if ((int)channels != 1 && (int)channels != 2) + throw new ArgumentOutOfRangeException("channels"); + + if (buffer == null || buffer.Length == 0) + throw new ArgumentException("Ensure that the buffer length is non-zero.", "buffer"); + + var blockAlign = (int)channels * 2; + if ((buffer.Length % blockAlign) != 0) + throw new ArgumentException("Ensure that the buffer meets the block alignment requirements for the number of channels.", "buffer"); + + if (count <= 0) + throw new ArgumentException("Ensure that the count is greater than zero.", "count"); + if ((count % blockAlign) != 0) + throw new ArgumentException("Ensure that the count meets the block alignment requirements for the number of channels.", "count"); + + if (offset < 0) + throw new ArgumentException("The offset cannot be negative.", "offset"); + if (((ulong)count + (ulong)offset) > (ulong)buffer.Length) + throw new ArgumentException("Ensure that the offset+count region lines within the buffer.", "offset"); + + var totalSamples = buffer.Length / blockAlign; + + if (loopStart < 0) + throw new ArgumentException("The loopStart cannot be negative.", "loopStart"); + if (loopStart > totalSamples) + throw new ArgumentException("The loopStart cannot be greater than the total number of samples.", "loopStart"); + + if (loopLength == 0) + loopLength = totalSamples - loopStart; + + if (loopLength < 0) + throw new ArgumentException("The loopLength cannot be negative.", "loopLength"); + if (((ulong)loopStart + (ulong)loopLength) > (ulong)totalSamples) + throw new ArgumentException("Ensure that the loopStart+loopLength region lies within the sample range.", "loopLength"); + _duration = GetSampleDuration(count, sampleRate, channels); - PlatformInitialize(buffer, offset, count, sampleRate, channels, loopStart, loopLength); + PlatformInitializePcm(buffer, offset, count, sampleRate, channels, loopStart, loopLength); } #endregion @@ -91,46 +179,42 @@ public SoundEffectInstance CreateInstance() } /// - /// Creates a SoundEffect object based on the specified data stream. + /// Creates a new SoundEffect object based on the specified data stream. /// - /// Stream object containing PCM wave data. + /// A stream containing the PCM wave data. /// A new SoundEffect object. - /// The Stream object must point to the head of a valid PCM wave file. Also, this wave file must be in the RIFF bitstream format. - public static SoundEffect FromStream(Stream s) + /// The stream must point to the head of a valid PCM wave file in the RIFF bitstream format. + public static SoundEffect FromStream(Stream stream) { - if (s == null) - throw new ArgumentNullException(); - - // Notes from the docs: + if (stream == null) + throw new ArgumentNullException("stream"); - /*The Stream object must point to the head of a valid PCM wave file. Also, this wave file must be in the RIFF bitstream format. - The audio format has the following restrictions: - Must be a PCM wave file - Can only be mono or stereo - Must be 8 or 16 bit - Sample rate must be between 8,000 Hz and 48,000 Hz*/ - - var sfx = new SoundEffect(); - - sfx.PlatformLoadAudioStream(s); - - return sfx; + return new SoundEffect(stream); } /// - /// Gets the TimeSpan representation of a single sample. + /// Returns the duration for 16bit PCM audio. /// - /// Size, in bytes, of audio data. + /// The length of the audio data in bytes. /// Sample rate, in Hertz (Hz). Must be between 8000 Hz and 48000 Hz /// Number of channels in the audio data. - /// TimeSpan object that represents the calculated sample duration. + /// The duration of the audio data. public static TimeSpan GetSampleDuration(int sizeInBytes, int sampleRate, AudioChannels channels) { + if (sizeInBytes < 0) + throw new ArgumentException("Buffer size cannot be negative.", "sizeInBytes"); if (sampleRate < 8000 || sampleRate > 48000) - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException("sampleRate"); - // Reference: http://social.msdn.microsoft.com/Forums/windows/en-US/5a92be69-3b4e-4d92-b1d2-141ef0a50c91/how-to-calculate-duration-of-wave-file-from-its-size?forum=winforms var numChannels = (int)channels; + if (numChannels != 1 && numChannels != 2) + throw new ArgumentOutOfRangeException("channels"); + + if (sizeInBytes == 0) + return TimeSpan.Zero; + + // Reference + // http://tinyurl.com/hq9slfy var dur = sizeInBytes / (sampleRate * numChannels * 16f / 8f); @@ -140,20 +224,25 @@ public static TimeSpan GetSampleDuration(int sizeInBytes, int sampleRate, AudioC } /// - /// Gets the size of a sample from a TimeSpan. + /// Returns the data size in bytes for 16bit PCM audio. /// - /// TimeSpan object that contains the sample duration. + /// The total duration of the audio data. /// Sample rate, in Hertz (Hz), of audio data. Must be between 8,000 and 48,000 Hz. /// Number of channels in the audio data. - /// Size of a single sample of audio data. + /// The size in bytes of a single sample of audio data. public static int GetSampleSizeInBytes(TimeSpan duration, int sampleRate, AudioChannels channels) { + if (duration < TimeSpan.Zero || duration > TimeSpan.FromMilliseconds(0x7FFFFFF)) + throw new ArgumentOutOfRangeException("duration"); if (sampleRate < 8000 || sampleRate > 48000) - throw new ArgumentOutOfRangeException(); - - // Reference: http://social.msdn.microsoft.com/Forums/windows/en-US/5a92be69-3b4e-4d92-b1d2-141ef0a50c91/how-to-calculate-duration-of-wave-file-from-its-size?forum=winforms + throw new ArgumentOutOfRangeException("sampleRate"); var numChannels = (int)channels; + if (numChannels != 1 && numChannels != 2) + throw new ArgumentOutOfRangeException("channels"); + + // Reference + // http://tinyurl.com/hq9slfy var sizeInBytes = duration.TotalSeconds * (sampleRate * numChannels * 16f / 8f); diff --git a/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs index de0d328cccd..d4d44f84a76 100644 --- a/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs +++ b/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs @@ -6,9 +6,16 @@ #if MONOMAC && PLATFORM_MACOS_LEGACY using MonoMac.OpenAL; -#elif OPENAL +#endif +#if MONOMAC && !PLATFORM_MACOS_LEGACY +using OpenTK.Audio.OpenAL; +#endif +#if GLES using OpenTK.Audio.OpenAL; #endif +#if DESKTOPGL +using OpenAL; +#endif namespace Microsoft.Xna.Framework.Audio { @@ -19,13 +26,20 @@ public partial class SoundEffectInstance : IDisposable private float _alVolume = 1; internal int SourceId; + private float reverb = 0f; + bool applyFilter = false; +#if SUPPORTS_EFX + EfxFilterType filterType; +#endif + float filterQ; + float frequency; int pauseCount; - private OpenALSoundController controller; + internal OpenALSoundController controller; internal bool HasSourceId = false; - #region Initialization +#region Initialization /// /// Creates a standalone SoundEffectInstance from given wavedata. @@ -44,7 +58,7 @@ internal void InitializeSound() controller = OpenALSoundController.GetInstance; } - #endregion // Initialization +#endregion // Initialization /// /// Converts the XNA [-1, 1] pitch range to OpenAL pitch (0, INF) or Android SoundPool playback rate [0.5, 2]. @@ -52,19 +66,6 @@ internal void InitializeSound() /// private static float XnaPitchToAlPitch(float xnaPitch) { - /*XNA sets pitch bounds to [-1.0f, 1.0f], each end being one octave. - •OpenAL's AL_PITCH boundaries are (0.0f, INF). * - •Consider the function f(x) = 2 ^ x - •The domain is (-INF, INF) and the range is (0, INF). * - •0.0f is the original pitch for XNA, 1.0f is the original pitch for OpenAL. - •Note that f(0) = 1, f(1) = 2, f(-1) = 0.5, and so on. - •XNA's pitch values are on the domain, OpenAL's are on the range. - •Remember: the XNA limit is arbitrarily between two octaves on the domain. * - •To convert, we just plug XNA pitch into f(x).*/ - - if (xnaPitch < -1.0f || xnaPitch > 1.0f) - throw new ArgumentOutOfRangeException("XNA PITCH MUST BE WITHIN [-1.0f, 1.0f]!"); - return (float)Math.Pow(2, xnaPitch); } @@ -142,9 +143,14 @@ private void PlatformPlay() // Pitch AL.Source (SourceId, ALSourcef.Pitch, XnaPitchToAlPitch(_pitch)); ALHelper.CheckError("Failed to set source pitch."); +#if SUPPORTS_EFX + ApplyReverb (); + ApplyFilter (); +#endif + AL.SourcePlay(SourceId); + ALHelper.CheckError("Failed to play source."); + - controller.PlaySound (this); - //Console.WriteLine ("playing: " + sourceId + " : " + soundEffect.Name); SoundState = SoundState.Playing; } @@ -176,9 +182,6 @@ private void PlatformStop(bool immediate) { if (HasSourceId) { - //Console.WriteLine ("stop " + sourceId + " : " + soundEffect.Name); - - if (!controller.CheckInitState()) { return; @@ -186,6 +189,14 @@ private void PlatformStop(bool immediate) AL.SourceStop(SourceId); ALHelper.CheckError("Failed to stop source."); +#if SUPPORTS_EFX + // Reset the SendFilter to 0 if we are NOT using revert since + // sources are recyled + OpenALSoundController.Efx.BindSourceToAuxiliarySlot (SourceId, 0, 0, 0); + ALHelper.CheckError ("Failed to unset reverb."); + AL.Source (SourceId, ALSourcei.EfxDirectFilter, 0); + ALHelper.CheckError ("Failed to unset filter."); +#endif AL.Source(SourceId, ALSourcei.Buffer, 0); ALHelper.CheckError("Failed to free source from buffer."); @@ -266,6 +277,95 @@ private void PlatformSetVolume(float value) } } + internal void PlatformSetReverbMix(float mix) + { +#if SUPPORTS_EFX + if (!OpenALSoundController.Efx.IsInitialized) + return; + reverb = mix; + if (State == SoundState.Playing) { + ApplyReverb (); + reverb = 0f; + } +#endif + } + +#if SUPPORTS_EFX + void ApplyReverb () + { + if (reverb > 0f && SoundEffect.ReverbSlot != 0) { + OpenALSoundController.Efx.BindSourceToAuxiliarySlot (SourceId, (int)SoundEffect.ReverbSlot, 0, 0); + ALHelper.CheckError ("Failed to set reverb."); + } + } + + void ApplyFilter () + { + if (applyFilter && controller.Filter > 0) { + var freq = frequency / 20000f; + var lf = 1.0f - freq; + var efx = OpenALSoundController.Efx; + efx.Filter (controller.Filter, EfxFilteri.FilterType, (int)filterType); + ALHelper.CheckError ("Failed to set filter."); + switch (filterType) { + case EfxFilterType.Lowpass: + efx.Filter (controller.Filter, EfxFilterf.LowpassGainHF, freq); + ALHelper.CheckError ("Failed to set LowpassGainHF."); + break; + case EfxFilterType.Highpass: + efx.Filter (controller.Filter, EfxFilterf.HighpassGainLF, freq); + ALHelper.CheckError ("Failed to set HighpassGainLF."); + break; + case EfxFilterType.Bandpass: + efx.Filter (controller.Filter, EfxFilterf.BandpassGainHF, freq); + ALHelper.CheckError ("Failed to set BandpassGainHF."); + efx.Filter (controller.Filter, EfxFilterf.BandpassGainLF, lf); + ALHelper.CheckError ("Failed to set BandpassGainLF."); + break; + } + AL.Source (SourceId, ALSourcei.EfxDirectFilter, controller.Filter); + ALHelper.CheckError ("Failed to set DirectFilter."); + } + } +#endif + + internal void PlatformSetFilter(FilterMode mode, float filterQ, float frequency) + { +#if SUPPORTS_EFX + if (!OpenALSoundController.Efx.IsInitialized) + return; + + applyFilter = true; + switch (mode) { + case FilterMode.BandPass: + filterType = EfxFilterType.Bandpass; + break; + case FilterMode.LowPass: + filterType = EfxFilterType.Lowpass; + break; + case FilterMode.HighPass: + filterType = EfxFilterType.Highpass; + break; + } + this.filterQ = filterQ; + this.frequency = frequency; + if (State == SoundState.Playing) { + ApplyFilter (); + applyFilter = false; + } +#endif + } + + internal void PlatformClearFilter() + { +#if SUPPORTS_EFX + if (!OpenALSoundController.Efx.IsInitialized) + return; + + applyFilter = false; +#endif + } + private void PlatformDispose(bool disposing) { diff --git a/MonoGame.Framework/Audio/SoundEffectInstance.Web.cs b/MonoGame.Framework/Audio/SoundEffectInstance.Web.cs index 9278678f874..83981ee4331 100644 --- a/MonoGame.Framework/Audio/SoundEffectInstance.Web.cs +++ b/MonoGame.Framework/Audio/SoundEffectInstance.Web.cs @@ -59,6 +59,18 @@ private void PlatformSetVolume(float value) { } + internal void PlatformSetReverbMix(float mix) + { + } + + internal void PlatformSetFilter(FilterMode mode, float filterQ, float frequency) + { + } + + internal void PlatformClearFilter() + { + } + private void PlatformDispose(bool disposing) { } diff --git a/MonoGame.Framework/Audio/SoundEffectInstance.XAudio.cs b/MonoGame.Framework/Audio/SoundEffectInstance.XAudio.cs index ddb24ff4ce9..67591764292 100644 --- a/MonoGame.Framework/Audio/SoundEffectInstance.XAudio.cs +++ b/MonoGame.Framework/Audio/SoundEffectInstance.XAudio.cs @@ -11,10 +11,16 @@ namespace Microsoft.Xna.Framework.Audio { public partial class SoundEffectInstance : IDisposable { + private static float[] _defaultChannelAzimuths = new float[] { 0f, 0f }; + internal SourceVoice _voice; internal WaveFormat _format; - private static float[] _panMatrix; + private SharpDX.XAudio2.Fx.Reverb _reverb; + + private static readonly float[] _outputMatrix = new float[16]; + + private float _reverbMix; private bool _paused; private bool _loop; @@ -31,13 +37,20 @@ private void PlatformApply3D(AudioListener listener, AudioEmitter emitter) return; // Convert from XNA Emitter to a SharpDX Emitter - var e = emitter.ToEmitter(); + var e = ToDXEmitter(emitter); e.CurveDistanceScaler = SoundEffect.DistanceScale; e.DopplerScaler = SoundEffect.DopplerScale; e.ChannelCount = _effect._format.Channels; + + //stereo channel + if (e.ChannelCount > 1) + { + e.ChannelRadius = 0; + e.ChannelAzimuths = _defaultChannelAzimuths; + } // Convert from XNA Listener to a SharpDX Listener - var l = listener.ToListener(); + var l = ToDXListener(listener); // Number of channels in the sound being played. // Not actually sure if XNA supported 3D attenuation of sterio sounds, but X3DAudio does. @@ -56,6 +69,99 @@ private void PlatformApply3D(AudioListener listener, AudioEmitter emitter) _voice.SetFrequencyRatio(dpsSettings.DopplerFactor); } + private SharpDX.X3DAudio.Emitter _dxEmitter; + private SharpDX.X3DAudio.Listener _dxListener; + + private SharpDX.X3DAudio.Emitter ToDXEmitter(AudioEmitter emitter) + { + // Pulling out Vector properties for efficiency. + var pos = emitter.Position; + var vel = emitter.Velocity; + var forward = emitter.Forward; + var up = emitter.Up; + + // From MSDN: + // X3DAudio uses a left-handed Cartesian coordinate system, + // with values on the x-axis increasing from left to right, on the y-axis from bottom to top, + // and on the z-axis from near to far. + // Azimuths are measured clockwise from a given reference direction. + // + // From MSDN: + // The XNA Framework uses a right-handed coordinate system, + // with the positive z-axis pointing toward the observer when the positive x-axis is pointing to the right, + // and the positive y-axis is pointing up. + // + // Programmer Notes: + // According to this description the z-axis (forward vector) is inverted between these two coordinate systems. + // Therefore, we need to negate the z component of any position/velocity values, and negate any forward vectors. + + forward *= -1.0f; + pos.Z *= -1.0f; + vel.Z *= -1.0f; + + if (_dxEmitter == null) + _dxEmitter = new Emitter(); + +#if WINDOWS_UAP + _dxEmitter.Position = new SharpDX.Mathematics.Interop.RawVector3 { X = pos.X, Y = pos.Y, Z = pos.Z }; + _dxEmitter.Velocity = new SharpDX.Mathematics.Interop.RawVector3 { X = vel.X, Y = vel.Y, Z = vel.Z }; + _dxEmitter.OrientFront = new SharpDX.Mathematics.Interop.RawVector3 { X = forward.X, Y = forward.Y, Z = forward.Z }; + _dxEmitter.OrientTop = new SharpDX.Mathematics.Interop.RawVector3 { X = up.X, Y = up.Y, Z = up.Z }; + +#else + _dxEmitter.Position = new SharpDX.Vector3(pos.X, pos.Y, pos.Z); + _dxEmitter.Velocity = new SharpDX.Vector3(vel.X, vel.Y, vel.Z); + _dxEmitter.OrientFront = new SharpDX.Vector3(forward.X, forward.Y, forward.Z); + _dxEmitter.OrientTop = new SharpDX.Vector3(up.X, up.Y, up.Z); + _dxEmitter.DopplerScaler = emitter.DopplerScale; +#endif + return _dxEmitter; + } + + private SharpDX.X3DAudio.Listener ToDXListener(AudioListener listener) + { + // Pulling out Vector properties for efficiency. + var pos = listener.Position; + var vel = listener.Velocity; + var forward = listener.Forward; + var up = listener.Up; + + // From MSDN: + // X3DAudio uses a left-handed Cartesian coordinate system, + // with values on the x-axis increasing from left to right, on the y-axis from bottom to top, + // and on the z-axis from near to far. + // Azimuths are measured clockwise from a given reference direction. + // + // From MSDN: + // The XNA Framework uses a right-handed coordinate system, + // with the positive z-axis pointing toward the observer when the positive x-axis is pointing to the right, + // and the positive y-axis is pointing up. + // + // Programmer Notes: + // According to this description the z-axis (forward vector) is inverted between these two coordinate systems. + // Therefore, we need to negate the z component of any position/velocity values, and negate any forward vectors. + + forward *= -1.0f; + pos.Z *= -1.0f; + vel.Z *= -1.0f; + + if (_dxListener == null) + _dxListener = new Listener(); + +#if WINDOWS_UAP + _dxListener.Position = new SharpDX.Mathematics.Interop.RawVector3 { X = pos.X, Y = pos.Y, Z = pos.Z }; + _dxListener.Velocity = new SharpDX.Mathematics.Interop.RawVector3 { X = vel.X, Y = vel.Y, Z = vel.Z }; + _dxListener.OrientFront = new SharpDX.Mathematics.Interop.RawVector3 { X = forward.X, Y = forward.Y, Z = forward.Z }; + _dxListener.OrientTop = new SharpDX.Mathematics.Interop.RawVector3 { X = up.X, Y = up.Y, Z = up.Z }; +#else + _dxListener.Position = new SharpDX.Vector3(pos.X, pos.Y, pos.Z); + _dxListener.Velocity = new SharpDX.Vector3(vel.X, vel.Y, vel.Z); + _dxListener.OrientFront = new SharpDX.Vector3(forward.X, forward.Y, forward.Z); + _dxListener.OrientTop = new SharpDX.Vector3(up.X, up.Y, up.Z); +#endif + return _dxListener; + } + private void PlatformPause() { if (_voice != null) @@ -135,87 +241,68 @@ private void PlatformSetPan(float value) _pan = MathHelper.Clamp(value, -1.0f, 1.0f); // If we have no voice then nothing more to do. - if (_voice == null || _effect == null) + if (_voice == null) return; - var srcChannelCount = _effect._format.Channels; - var dstChannelCount = SoundEffect.MasterVoice.VoiceDetails.InputChannelCount; - - if (_panMatrix == null || _panMatrix.Length < dstChannelCount) - _panMatrix = new float[Math.Max(dstChannelCount, 8)]; - - // Default to full volume for all channels/destinations - for (var i = 0; i < _panMatrix.Length; i++) - _panMatrix[i] = 1.0f; - - // From X3DAudio documentation: - /* - For submix and mastering voices, and for source voices without a channel mask or a channel mask of 0, - XAudio2 assumes default speaker positions according to the following table. - - Channels + UpdateOutputMatrix(); + } - Implicit Channel Positions + internal void UpdateOutputMatrix() + { + var srcChannelCount = _voice.VoiceDetails.InputChannelCount; + var dstChannelCount = SoundEffect.MasterVoice.VoiceDetails.InputChannelCount; - 1 Always maps to FrontLeft and FrontRight at full scale in both speakers (special case for mono sounds) - 2 FrontLeft, FrontRight (basic stereo configuration) - 3 FrontLeft, FrontRight, LowFrequency (2.1 configuration) - 4 FrontLeft, FrontRight, BackLeft, BackRight (quadraphonic) - 5 FrontLeft, FrontRight, FrontCenter, SideLeft, SideRight (5.0 configuration) - 6 FrontLeft, FrontRight, FrontCenter, LowFrequency, SideLeft, SideRight (5.1 configuration) (see the following remarks) - 7 FrontLeft, FrontRight, FrontCenter, LowFrequency, SideLeft, SideRight, BackCenter (6.1 configuration) - 8 FrontLeft, FrontRight, FrontCenter, LowFrequency, BackLeft, BackRight, SideLeft, SideRight (7.1 configuration) - 9 or more No implicit positions (one-to-one mapping) - */ + // Set the pan on the correct channels based on the reverb mix. + if (!(_reverbMix > 0.0f)) + _voice.SetOutputMatrix(srcChannelCount, dstChannelCount, CalculateOutputMatrix(_pan, 1.0f, srcChannelCount)); + else + { + _voice.SetOutputMatrix(SoundEffect.ReverbVoice, srcChannelCount, dstChannelCount, CalculateOutputMatrix(_pan, _reverbMix, srcChannelCount)); + _voice.SetOutputMatrix(SoundEffect.MasterVoice, srcChannelCount, dstChannelCount, CalculateOutputMatrix(_pan, 1.0f - Math.Min(_reverbMix, 1.0f), srcChannelCount)); + } + } - // Notes: - // - // Since XNA does not appear to expose any 'master' voice channel mask / speaker configuration, - // I assume the mappings listed above should be used. - // - // Assuming it is correct to pan all channels which have a left/right component. + internal static float[] CalculateOutputMatrix(float pan, float scale, int inputChannels) + { + // XNA only ever outputs to the front left/right speakers (channels 0 and 1) + // Assumes there are at least 2 speaker channels to output to - var lVal = 1.0f - _pan; - var rVal = 1.0f + _pan; + // Clear all the channels. + var outputMatrix = _outputMatrix; + Array.Clear(outputMatrix, 0, outputMatrix.Length); - switch (SoundEffect.Speakers) + if (inputChannels == 1) // Mono source { - case Speakers.Stereo: - case Speakers.TwoPointOne: - case Speakers.Surround: - _panMatrix[0] = lVal; - _panMatrix[1] = rVal; - break; - - case Speakers.Quad: - _panMatrix[0] = _panMatrix[2] = lVal; - _panMatrix[1] = _panMatrix[3] = rVal; - break; - - case Speakers.FourPointOne: - _panMatrix[0] = _panMatrix[3] = lVal; - _panMatrix[1] = _panMatrix[4] = rVal; - break; - - case Speakers.FivePointOne: - case Speakers.SevenPointOne: - case Speakers.FivePointOneSurround: - _panMatrix[0] = _panMatrix[4] = lVal; - _panMatrix[1] = _panMatrix[5] = rVal; - break; - - case Speakers.SevenPointOneSurround: - _panMatrix[0] = _panMatrix[4] = _panMatrix[6] = lVal; - _panMatrix[1] = _panMatrix[5] = _panMatrix[7] = rVal; - break; - - case Speakers.Mono: - default: - // don't do any panning here - break; + // Left/Right output levels: + // Pan -1.0: L = 1.0, R = 0.0 + // Pan 0.0: L = 1.0, R = 1.0 + // Pan +1.0: L = 0.0, R = 1.0 + outputMatrix[0] = (pan > 0f) ? ((1f - pan) * scale) : scale; // Front-left output + outputMatrix[1] = (pan < 0f) ? ((1f + pan) * scale) : scale; // Front-right output + } + else if (inputChannels == 2) // Stereo source + { + // Left/Right input (Li/Ri) mix for Left/Right outputs (Lo/Ro): + // Pan -1.0: Lo = 0.5Li + 0.5Ri, Ro = 0.0Li + 0.0Ri + // Pan 0.0: Lo = 1.0Li + 0.0Ri, Ro = 0.0Li + 1.0Ri + // Pan +1.0: Lo = 0.0Li + 0.0Ri, Ro = 0.5Li + 0.5Ri + if (pan <= 0f) + { + outputMatrix[0] = (1f + pan * 0.5f) * scale; // Front-left output, Left input + outputMatrix[1] = (-pan * 0.5f) * scale; // Front-left output, Right input + outputMatrix[2] = 0f; // Front-right output, Left input + outputMatrix[3] = (1f + pan) * scale; // Front-right output, Right input + } + else + { + outputMatrix[0] = (1f - pan) * scale; // Front-left output, Left input + outputMatrix[1] = 0f; // Front-left output, Right input + outputMatrix[2] = (pan * 0.5f) * scale; // Front-right output, Left input + outputMatrix[3] = (1f - pan * 0.5f) * scale; // Front-right output, Right input + } } - _voice.SetOutputMatrix(srcChannelCount, dstChannelCount, _panMatrix); + return outputMatrix; } private void PlatformSetPitch(float value) @@ -251,10 +338,56 @@ private void PlatformSetVolume(float value) _voice.SetVolume(value, XAudio2.CommitNow); } + internal void PlatformSetReverbMix(float mix) + { + // At least for XACT we can't go over 2x the volume on the mix. + _reverbMix = MathHelper.Clamp(mix, 0, 2); + + // If we have no voice then nothing more to do. + if (_voice == null) + return; + + if (!(_reverbMix > 0.0f)) + _voice.SetOutputVoices(new VoiceSendDescriptor(SoundEffect.MasterVoice)); + else + { + _voice.SetOutputVoices( new VoiceSendDescriptor(SoundEffect.ReverbVoice), + new VoiceSendDescriptor(SoundEffect.MasterVoice)); + } + + UpdateOutputMatrix(); + } + + internal void PlatformSetFilter(FilterMode mode, float filterQ, float frequency) + { + if (_voice == null) + return; + + var filter = new FilterParameters + { + Frequency = XAudio2.CutoffFrequencyToRadians(frequency, _voice.VoiceDetails.InputSampleRate), + OneOverQ = 1.0f / filterQ, + Type = (FilterType)mode + }; + _voice.SetFilterParameters(filter); + } + + internal void PlatformClearFilter() + { + if (_voice == null) + return; + + var filter = new FilterParameters { Frequency = 1.0f, OneOverQ = 1.0f, Type = FilterType.LowPassFilter }; + _voice.SetFilterParameters(filter); + } + private void PlatformDispose(bool disposing) { if (disposing) { + if (_reverb != null) + _reverb.Dispose(); + if (_voice != null) { _voice.DestroyVoice(); @@ -263,6 +396,7 @@ private void PlatformDispose(bool disposing) } _voice = null; _effect = null; + _reverb = null; } } } diff --git a/MonoGame.Framework/Audio/SoundEffectInstance.cs b/MonoGame.Framework/Audio/SoundEffectInstance.cs index f237d39e1cc..14e3092fd84 100644 --- a/MonoGame.Framework/Audio/SoundEffectInstance.cs +++ b/MonoGame.Framework/Audio/SoundEffectInstance.cs @@ -15,6 +15,7 @@ public partial class SoundEffectInstance : IDisposable private bool _isDisposed = false; internal bool _isPooled = true; internal bool _isXAct; + internal bool _isDynamic; internal SoundEffect _effect; private float _pan; private float _volume; @@ -22,7 +23,7 @@ public partial class SoundEffectInstance : IDisposable /// Enables or Disables whether the SoundEffectInstance should repeat after playback. /// This value has no effect on an already playing sound. - public bool IsLooped + public virtual bool IsLooped { get { return PlatformGetIsLooped(); } set { PlatformSetIsLooped(value); } @@ -50,7 +51,8 @@ public float Pitch get { return _pitch; } set { - if (value < -1.0f || value > 1.0f) + // XAct sounds effects don't have pitch limits + if (!_isXAct && (value < -1.0f || value > 1.0f)) throw new ArgumentOutOfRangeException(); _pitch = value; @@ -83,7 +85,7 @@ public float Volume } /// Gets the SoundEffectInstance's current playback state. - public SoundState State { get { return PlatformGetState(); } } + public virtual SoundState State { get { return PlatformGetState(); } } /// Indicates whether the object is disposed. public bool IsDisposed { get { return _isDisposed; } } @@ -129,15 +131,18 @@ public void Apply3D(AudioListener[] listeners, AudioEmitter emitter) /// Pauses playback of a SoundEffectInstance. /// Paused instances can be resumed with SoundEffectInstance.Play() or SoundEffectInstance.Resume(). - public void Pause() + public virtual void Pause() { PlatformPause(); } /// Plays or resumes a SoundEffectInstance. /// Throws an exception if more sounds are playing than the platform allows. - public void Play() + public virtual void Play() { + if (_isDisposed) + throw new ObjectDisposedException("SoundEffectInstance"); + if (State == SoundState.Playing) return; @@ -161,13 +166,13 @@ public void Play() /// Resumes playback for a SoundEffectInstance. /// Only has effect on a SoundEffectInstance in a paused state. - public void Resume() + public virtual void Resume() { PlatformResume(); } /// Immediately stops playing a SoundEffectInstance. - public void Stop() + public virtual void Stop() { PlatformStop(true); } @@ -175,7 +180,7 @@ public void Stop() /// Stops playing a SoundEffectInstance, either immediately or as authored. /// Determined whether the sound stops immediately, or after playing its release phase and/or transitions. /// Stopping a sound with the immediate argument set to false will allow it to play any release phases, such as fade, before coming to a stop. - public void Stop(bool immediate) + public virtual void Stop(bool immediate) { PlatformStop(immediate); } diff --git a/MonoGame.Framework/Audio/SoundEffectInstancePool.cs b/MonoGame.Framework/Audio/SoundEffectInstancePool.cs index e15940a264a..4cea5d6e304 100644 --- a/MonoGame.Framework/Audio/SoundEffectInstancePool.cs +++ b/MonoGame.Framework/Audio/SoundEffectInstancePool.cs @@ -79,7 +79,9 @@ internal static SoundEffectInstance GetInstance(bool forXAct) inst.Volume = 1.0f; inst.Pan = 0.0f; inst.Pitch = 0.0f; - inst.IsLooped = false; + inst.IsLooped = false; + inst.PlatformSetReverbMix(0); + inst.PlatformClearFilter(); } else { @@ -97,18 +99,18 @@ internal static SoundEffectInstance GetInstance(bool forXAct) /// internal static void Update() { -#if OPENAL - OpenALSoundController.GetInstance.Update(); -#endif - SoundEffectInstance inst = null; // Cleanup instances which have finished playing. for (var x = 0; x < _playingInstances.Count;) { - inst = _playingInstances[x]; - - if (inst.State == SoundState.Stopped || inst.IsDisposed || inst._effect == null) - { + inst = _playingInstances[x]; + + if (inst.IsDisposed || inst.State == SoundState.Stopped || (inst._effect == null && !inst._isDynamic)) + { +#if OPENAL + if (!inst.IsDisposed) + inst.Stop(true); // force stopping it to free its AL source +#endif Add(inst); continue; } @@ -154,23 +156,5 @@ internal static void UpdateMasterVolume() } } - internal static void Shutdown() - { - // We need to dispose all SoundEffectInstances before shutdown, - // so as to destroy all SourceVoice instances, - // before we can destroy our XAudio MasterVoice instance. - // Otherwise XAudio shutdown fails, causing intermittent crashes. - foreach (var inst in _playingInstances) - { - inst.Dispose(); - } - _playingInstances.Clear(); - - foreach (var inst in _pooledInstances) - { - inst.Dispose(); - } - _pooledInstances.Clear(); - } } } diff --git a/MonoGame.Framework/Audio/Xact/AudioCategory.cs b/MonoGame.Framework/Audio/Xact/AudioCategory.cs index 328a679f5b0..86198bebd54 100644 --- a/MonoGame.Framework/Audio/Xact/AudioCategory.cs +++ b/MonoGame.Framework/Audio/Xact/AudioCategory.cs @@ -12,85 +12,71 @@ namespace Microsoft.Xna.Framework.Audio /// /// Provides functionality for manipulating multiple sounds at a time. /// - public struct AudioCategory : IEquatable - { - string name; - AudioEngine engine; + public struct AudioCategory : IEquatable + { + readonly string _name; + readonly AudioEngine _engine; + readonly List _sounds; - // Thisis a bit gross, but we use an array here + // This is a bit gross, but we use an array here // instead of a field since AudioCategory is a struct // This allows us to save _volume when the user // holds onto a reference of AudioCategory, or when a cue // is created/loaded after the volume's already been set. - internal float[] _volume; - internal bool isBackgroundMusic; - internal bool isPublic; - - internal bool instanceLimit; - internal int maxInstances; - - List sounds; - - //insatnce limiting behaviour - internal enum MaxInstanceBehaviour { - FailToPlay, - Queue, - ReplaceOldest, - ReplaceQuietest, - ReplaceLowestPriority, - } - internal MaxInstanceBehaviour instanceBehaviour; - - internal enum CrossfadeType { - Linear, - Logarithmic, - EqualPower, - } - internal CrossfadeType fadeType; - internal float fadeIn; - internal float fadeOut; - - - internal AudioCategory (AudioEngine audioengine, string name, BinaryReader reader) - { - Debug.Assert(audioengine != null); + internal float[] _volume; + + internal bool isBackgroundMusic; + internal bool isPublic; + + internal bool instanceLimit; + internal int maxInstances; + internal MaxInstanceBehavior InstanceBehavior; + + internal CrossfadeType fadeType; + internal float fadeIn; + internal float fadeOut; + + + internal AudioCategory (AudioEngine audioengine, string name, BinaryReader reader) + { + Debug.Assert(audioengine != null); Debug.Assert(!string.IsNullOrEmpty(name)); - this.sounds = new List(); - this.name = name; - engine = audioengine; + _sounds = new List(); + _name = name; + _engine = audioengine; - maxInstances = reader.ReadByte (); - instanceLimit = maxInstances != 0xff; + maxInstances = reader.ReadByte (); + instanceLimit = maxInstances != 0xff; - fadeIn = (reader.ReadUInt16 () / 1000f); - fadeOut = (reader.ReadUInt16 () / 1000f); + fadeIn = (reader.ReadUInt16 () / 1000f); + fadeOut = (reader.ReadUInt16 () / 1000f); - byte instanceFlags = reader.ReadByte (); - fadeType = (CrossfadeType)(instanceFlags & 0x7); - instanceBehaviour = (MaxInstanceBehaviour)(instanceFlags >> 3); + byte instanceFlags = reader.ReadByte (); + fadeType = (CrossfadeType)(instanceFlags & 0x7); + InstanceBehavior = (MaxInstanceBehavior)(instanceFlags >> 3); - reader.ReadUInt16 (); //unkn + reader.ReadUInt16 (); //unkn var volume = XactHelpers.ParseVolumeFromDecibels(reader.ReadByte()); _volume = new float[1] { volume }; - byte visibilityFlags = reader.ReadByte (); - isBackgroundMusic = (visibilityFlags & 0x1) != 0; - isPublic = (visibilityFlags & 0x2) != 0; - } + byte visibilityFlags = reader.ReadByte (); + isBackgroundMusic = (visibilityFlags & 0x1) != 0; + isPublic = (visibilityFlags & 0x2) != 0; + } - internal void AddSound(XactSound sound) - { - sounds.Add(sound); - } + internal void AddSound(XactSound sound) + { + _sounds.Add(sound); + } internal int GetPlayingInstanceCount() { var sum = 0; - for (var i = 0; i < sounds.Count; i++) + for (var i = 0; i < _sounds.Count; i++) { - if (sounds[i].Playing) + if (_sounds[i].Playing) sum++; } return sum; @@ -98,10 +84,10 @@ internal int GetPlayingInstanceCount() internal XactSound GetOldestInstance() { - for (var i = 0; i < sounds.Count; i++) + for (var i = 0; i < _sounds.Count; i++) { - if (sounds[i].Playing) - return sounds[i]; + if (_sounds[i].Playing) + return _sounds[i]; } return null; } @@ -109,42 +95,45 @@ internal XactSound GetOldestInstance() /// /// Gets the category's friendly name. /// - public string Name { get { return name; } } + public string Name { get { return _name; } } /// /// Pauses all associated sounds. /// - public void Pause () - { - foreach (var sound in sounds) - sound.Pause(); - } + public void Pause () + { + foreach (var sound in _sounds) + sound.Pause(); + } /// /// Resumes all associated paused sounds. /// - public void Resume () - { - foreach (var sound in sounds) - sound.Resume(); - } + public void Resume () + { + foreach (var sound in _sounds) + sound.Resume(); + } /// /// Stops all associated sounds. /// public void Stop(AudioStopOptions options) - { - foreach (var sound in sounds) + { + foreach (var sound in _sounds) sound.Stop(options); - } + } - public void SetVolume(float volume) + public void SetVolume(float volume) { + if (volume < 0) + throw new ArgumentException("The volume must be positive."); + _volume[0] = volume; - foreach (var sound in sounds) - sound.UpdateCategoryVolume(volume); - } + foreach (var sound in _sounds) + sound.UpdateCategoryVolume(volume); + } /// /// Determines whether two AudioCategory instances are equal. @@ -154,7 +143,7 @@ public void SetVolume(float volume) /// true if the objects are equal or false if they aren't. public static bool operator ==(AudioCategory first, AudioCategory second) { - return first.engine == second.engine && first.name.Equals(second.name, StringComparison.Ordinal); + return first._engine == second._engine && first._name.Equals(second._name, StringComparison.Ordinal); } /// @@ -164,19 +153,19 @@ public void SetVolume(float volume) /// Second AudioCategory instance to compare. /// true if the objects are not equal or false if they are. public static bool operator !=(AudioCategory first, AudioCategory second) - { - return first.engine != second.engine || !first.name.Equals(second.name, StringComparison.Ordinal); - } + { + return first._engine != second._engine || !first._name.Equals(second._name, StringComparison.Ordinal); + } /// /// Determines whether two AudioCategory instances are equal. /// /// AudioCategory to compare with this instance. /// true if the objects are equal or false if they aren't - public bool Equals(AudioCategory other) - { - return engine == other.engine && name.Equals(other.name, StringComparison.Ordinal); - } + public bool Equals(AudioCategory other) + { + return _engine == other._engine && _name.Equals(other._name, StringComparison.Ordinal); + } /// /// Determines whether two AudioCategory instances are equal. @@ -188,7 +177,7 @@ public override bool Equals(object obj) if (obj is AudioCategory) { var other = (AudioCategory)obj; - return engine == other.engine && name.Equals(other.name, StringComparison.Ordinal); + return _engine == other._engine && _name.Equals(other._name, StringComparison.Ordinal); } return false; @@ -200,7 +189,7 @@ public override bool Equals(object obj) /// Hash code for this object. public override int GetHashCode() { - return name.GetHashCode() ^ engine.GetHashCode(); + return _name.GetHashCode() ^ _engine.GetHashCode(); } /// @@ -209,8 +198,8 @@ public override int GetHashCode() /// Friendly name of the AudioCategory public override string ToString() { - return name; + return _name; } - } + } } diff --git a/MonoGame.Framework/Audio/Xact/AudioEngine.cs b/MonoGame.Framework/Audio/Xact/AudioEngine.cs index 0b058785730..24d03d2d6f5 100644 --- a/MonoGame.Framework/Audio/Xact/AudioEngine.cs +++ b/MonoGame.Framework/Audio/Xact/AudioEngine.cs @@ -7,269 +7,385 @@ using System.IO; using System.Collections.Generic; -using Microsoft.Xna.Framework; - namespace Microsoft.Xna.Framework.Audio { /// /// Class used to create and manipulate code audio objects. /// - public class AudioEngine : IDisposable - { - /// - ///Specifies the current content version. - /// - public const int ContentVersion = 46; - - internal Dictionary Wavebanks = new Dictionary(); - - AudioCategory[] categories; - Dictionary categoryLookup = new Dictionary(); - - internal List _activeCues = new List(); - - internal AudioCategory[] Categories { get { return categories; } } - - struct Variable { - public string name; - public float value; - - public bool isGlobal; - public bool isReadOnly; - public bool isPublic; - public bool isReserved; - - public float initValue; - public float maxValue; - public float minValue; - } - Variable[] variables; - Dictionary variableLookup = new Dictionary(); - - enum RpcPointType { - Linear, - Fast, - Slow, - SinCos - } - struct RpcPoint { - public float x, y; - public RpcPointType type; - } - - enum RpcParameter { - Volume, - Pitch, - ReverbSend, - FilterFrequency, - FilterQFactor - } - struct RpcCurve { - public int variable; - public RpcParameter parameter; - public RpcPoint[] points; - } - - RpcCurve[] rpcCurves; - private Stopwatch _stopwatch; + public class AudioEngine : IDisposable + { + private readonly AudioCategory[] _categories; + private readonly Dictionary _categoryLookup = new Dictionary(); + + private readonly RpcVariable[] _variables; + private readonly Dictionary _variableLookup = new Dictionary(); + + private readonly RpcVariable[] _cueVariables; + + private readonly Stopwatch _stopwatch; private TimeSpan _lastUpdateTime; + private readonly ReverbSettings _reverbSettings; + private readonly RpcCurve[] _reverbCurves; + + internal List ActiveCues = new List(); + internal AudioCategory[] Categories { get { return _categories; } } + + internal Dictionary Wavebanks = new Dictionary(); + + internal readonly RpcCurve[] RpcCurves; + + internal RpcVariable[] CreateCueVariables() + { + var clone = new RpcVariable[_cueVariables.Length]; + Array.Copy(_cueVariables, clone, _cueVariables.Length); + return clone; + } + + /// + /// The current content version. + /// + public const int ContentVersion = 39; /// Path to a XACT settings file. - public AudioEngine (string settingsFile) - : this(settingsFile, TimeSpan.Zero, "") - { - } + public AudioEngine(string settingsFile) + : this(settingsFile, TimeSpan.Zero, "") + { + } + + internal static Stream OpenStream(string filePath) + { + var stream = TitleContainer.OpenStream(filePath); + +#if ANDROID + // Read the asset into memory in one go. This results in a ~50% reduction + // in load times on Android due to slow Android asset streams. + var memStream = new MemoryStream(); + stream.CopyTo(memStream); + memStream.Seek(0, SeekOrigin.Begin); + stream.Close(); + stream = memStream; +#endif + + return stream; + } /// Path to a XACT settings file. /// Determines how many milliseconds the engine will look ahead when determing when to transition to another sound. /// A string that specifies the audio renderer to use. /// For the best results, use a lookAheadTime of 250 milliseconds or greater. - public AudioEngine (string settingsFile, TimeSpan lookAheadTime, string rendererId) - { - //Read the xact settings file - //Credits to alisci01 for initial format documentation -#if !ANDROID - using (var stream = TitleContainer.OpenStream(settingsFile)) - { -#else - using (var fileStream = Game.Activity.Assets.Open(settingsFile)) - { - MemoryStream stream = new MemoryStream(); - fileStream.CopyTo(stream); - stream.Position = 0; -#endif - using (var reader = new BinaryReader(stream)) { - uint magic = reader.ReadUInt32 (); - if (magic != 0x46534758) { //'XGFS' - throw new ArgumentException ("XGS format not recognized"); - } - - reader.ReadUInt16 (); // toolVersion -#if DEBUG - uint formatVersion = reader.ReadUInt16 (); - if (formatVersion != 42) { - System.Diagnostics.Debug.WriteLine ("Warning: XGS format not supported"); - } -#else - reader.ReadUInt16 (); // formatVersion -#endif - reader.ReadUInt16 (); // crc - - reader.ReadUInt32 (); // lastModifiedLow - reader.ReadUInt32 (); // lastModifiedHigh - - reader.ReadByte (); //unkn, 0x03. Platform? - - uint numCats = reader.ReadUInt16 (); - uint numVars = reader.ReadUInt16 (); - - reader.ReadUInt16 (); //unkn, 0x16 - reader.ReadUInt16 (); //unkn, 0x16 - - uint numRpc = reader.ReadUInt16 (); - reader.ReadUInt16 (); // numDspPresets - reader.ReadUInt16 (); // numDspParams - - uint catsOffset = reader.ReadUInt32 (); - uint varsOffset = reader.ReadUInt32 (); - - reader.ReadUInt32 (); //unknown, leads to a short with value of 1? - reader.ReadUInt32 (); // catNameIndexOffset - reader.ReadUInt32 (); //unknown, two shorts of values 2 and 3? - reader.ReadUInt32 (); // varNameIndexOffset - - uint catNamesOffset = reader.ReadUInt32 (); - uint varNamesOffset = reader.ReadUInt32 (); - uint rpcOffset = reader.ReadUInt32 (); - reader.ReadUInt32 (); // dspPresetsOffset - reader.ReadUInt32 (); // dspParamsOffset - reader.BaseStream.Seek (catNamesOffset, SeekOrigin.Begin); - string[] categoryNames = readNullTerminatedStrings (numCats, reader); - - categories = new AudioCategory[numCats]; - reader.BaseStream.Seek (catsOffset, SeekOrigin.Begin); - for (int i=0; i(); + var cueVariables = new List(); + var globalVariables = new List(); + reader.BaseStream.Seek (varsOffset, SeekOrigin.Begin); + for (var i=0; i < numVars; i++) + { + var v = new RpcVariable(); + v.Name = varNames[i]; + v.Flags = reader.ReadByte(); + v.InitValue = reader.ReadSingle(); + v.MinValue = reader.ReadSingle(); + v.MaxValue = reader.ReadSingle(); + v.Value = v.InitValue; + + variables.Add(v); + if (!v.IsGlobal) + cueVariables.Add(v); + else + { + globalVariables.Add(v); + _variableLookup.Add(v.Name, globalVariables.Count - 1); + } + } + _cueVariables = cueVariables.ToArray(); + _variables = globalVariables.ToArray(); + + var reverbCurves = new List(); + RpcCurves = new RpcCurve[numRpc]; + if (numRpc > 0) + { + reader.BaseStream.Seek(rpcOffset, SeekOrigin.Begin); + for (var i=0; i < numRpc; i++) + { + var curve = new RpcCurve(); + curve.FileOffset = (uint)reader.BaseStream.Position; + + var variable = variables[ reader.ReadUInt16() ]; + if (variable.IsGlobal) + { + curve.IsGlobal = true; + curve.Variable = globalVariables.FindIndex(e => e.Name == variable.Name); + } + else + { + curve.IsGlobal = false; + curve.Variable = cueVariables.FindIndex(e => e.Name == variable.Name); + } + + var pointCount = (int)reader.ReadByte(); + curve.Parameter = (RpcParameter)reader.ReadUInt16(); + + curve.Points = new RpcPoint[pointCount]; + for (var j=0; j < pointCount; j++) + { + curve.Points[j].Position = reader.ReadSingle(); + curve.Points[j].Value = reader.ReadSingle(); + curve.Points[j].Type = (RpcPointType)reader.ReadByte(); + } + + // If the parameter is greater than the max then this is a DSP + // parameter which is for reverb. + var dspParameter = curve.Parameter - RpcParameter.NumParameters; + if (dspParameter >= 0 && variable.IsGlobal) + reverbCurves.Add(curve); + + RpcCurves[i] = curve; + } + } + _reverbCurves = reverbCurves.ToArray(); + + if (numDspPresets > 0) + { + // Note: It seemed like MS designed this to support multiple + // DSP effects, but in practice XACT only has one... Microsoft Reverb. + // + // So because of this we know exactly how many presets and + // parameters we should have. + if (numDspPresets != 1) + throw new Exception("Unexpected number of DSP presets!"); + if (numDspParams != 22) + throw new Exception("Unexpected number of DSP parameters!"); + + reader.BaseStream.Seek(dspParamsOffset, SeekOrigin.Begin); + _reverbSettings = new ReverbSettings(reader); + } + } _stopwatch = new Stopwatch(); _stopwatch.Start(); - } - - //wtf C# - private static string[] readNullTerminatedStrings (uint count, BinaryReader reader) - { - string[] ret = new string[count]; - for (int i=0; i s = new List (); - while (reader.PeekChar() != 0) { - s.Add (reader.ReadChar ()); - } - reader.ReadChar (); - ret [i] = new string (s.ToArray ()); - } - return ret; - } - - /// Performs periodic work required by the audio engine. + } + + internal int GetRpcIndex(uint fileOffset) + { + for (var i = 0; i < RpcCurves.Length; i++) + { + if (RpcCurves[i].FileOffset == fileOffset) + return i; + } + + return -1; + } + + private static string[] ReadNullTerminatedStrings(uint count, BinaryReader reader) + { + var ret = new string[count]; + + for (var i=0; i < count; i++) + { + var s = new List(); + while (reader.PeekChar() != 0) + s.Add(reader.ReadChar()); + + reader.ReadChar(); + ret[i] = new string(s.ToArray()); + } + + return ret; + } + + /// + /// Performs periodic work required by the audio engine. + /// /// Must be called at least once per frame. - public void Update () + public void Update() { var cur = _stopwatch.Elapsed; var elapsed = cur - _lastUpdateTime; _lastUpdateTime = cur; var dt = (float)elapsed.TotalSeconds; - for (var x = 0; x < _activeCues.Count; ) + for (var x = 0; x < ActiveCues.Count; ) { - var cue = _activeCues[x]; + var cue = ActiveCues[x]; cue.Update(dt); if (cue.IsStopped) { - _activeCues.Remove(cue); + ActiveCues.Remove(cue); continue; } x++; } - } - + + // The only global curves we can process seem to be + // specifically for the reverb DSP effect. + if (_reverbSettings != null) + { + for (var i = 0; i < _reverbCurves.Length; i++) + { + var curve = _reverbCurves[i]; + var result = curve.Evaluate(_variables[curve.Variable].Value); + var parameter = curve.Parameter - RpcParameter.NumParameters; + _reverbSettings[parameter] = result; + } + + SoundEffect.PlatformSetReverbSettings(_reverbSettings); + } + } + /// Returns an audio category by name. /// Friendly name of the category to get. /// The AudioCategory with a matching name. Throws an exception if not found. - public AudioCategory GetCategory (string name) - { - return categories [categoryLookup [name]]; - } + public AudioCategory GetCategory(string name) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException("name"); + + int i; + if (!_categoryLookup.TryGetValue(name, out i)) + throw new InvalidOperationException("This resource could not be created."); + + return _categories[i]; + } /// Gets the value of a global variable. /// Friendly name of the variable. /// float value of the queried variable. /// A global variable has global scope. It can be accessed by all code within a project. - public float GetGlobalVariable(string name) - { - return variables[variableLookup[name]].value; - } + public float GetGlobalVariable(string name) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException("name"); + + int i; + if (!_variableLookup.TryGetValue(name, out i) || !_variables[i].IsPublic) + throw new IndexOutOfRangeException("The specified variable index is invalid."); + + return _variables[i].Value; + } + + internal float GetGlobalVariable(int index) + { + return _variables[index].Value; + } /// Sets the value of a global variable. /// Friendly name of the variable. /// Value of the global variable. - public void SetGlobalVariable (string name, float value) - { - variables [variableLookup [name]].value = value; - } - - #region IDisposable implementation - public void Dispose () - { - } - #endregion - } + public void SetGlobalVariable(string name, float value) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException("name"); + + int i; + if (!_variableLookup.TryGetValue(name, out i) || !_variables[i].IsPublic) + throw new IndexOutOfRangeException("The specified variable index is invalid."); + + _variables[i].SetValue(value); + } + + /// + /// This event is triggered when the AudioEngine is disposed. + /// + public event EventHandler Disposing; + + /// + /// Is true if the AudioEngine has been disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Disposes the AudioEngine. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~AudioEngine() + { + Dispose(false); + } + + private void Dispose(bool disposing) + { + if (IsDisposed) + return; + + IsDisposed = true; + + // TODO: Should we be forcing any active + // audio cues to stop here? + + if (disposing && Disposing != null) + Disposing(this, EventArgs.Empty); + } + } } diff --git a/MonoGame.Framework/Audio/Xact/ClipEvent.cs b/MonoGame.Framework/Audio/Xact/ClipEvent.cs index 47c30dd76a8..c2aace826aa 100644 --- a/MonoGame.Framework/Audio/Xact/ClipEvent.cs +++ b/MonoGame.Framework/Audio/Xact/ClipEvent.cs @@ -28,9 +28,9 @@ protected ClipEvent(XactClip clip, float timeStamp, float randomOffset) public abstract void Resume(); public abstract void SetFade(float fadeInDuration, float fadeOutDuration); public abstract void SetTrackVolume(float volume); + public abstract void SetTrackPan(float pan); + public abstract void SetState(float volume, float pitch, float reverbMix, float? filterFrequency, float? filterQFactor); public abstract bool Update(float dt); - - public abstract void Apply3D(AudioListener listener, AudioEmitter emitter); } } diff --git a/MonoGame.Framework/Audio/Xact/CrossfadeType.cs b/MonoGame.Framework/Audio/Xact/CrossfadeType.cs new file mode 100644 index 00000000000..c1594e39579 --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/CrossfadeType.cs @@ -0,0 +1,13 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +namespace Microsoft.Xna.Framework.Audio +{ + enum CrossfadeType + { + Linear, + Logarithmic, + EqualPower, + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/Cue.cs b/MonoGame.Framework/Audio/Xact/Cue.cs index 52f31b4205f..138987a30b9 100644 --- a/MonoGame.Framework/Audio/Xact/Cue.cs +++ b/MonoGame.Framework/Audio/Xact/Cue.cs @@ -12,64 +12,59 @@ namespace Microsoft.Xna.Framework.Audio /// Cues also define specific properties such as pitch or volume. /// Cues are referenced through SoundBank objects. /// - public class Cue : IDisposable - { + public class Cue : IDisposable + { private readonly AudioEngine _engine; private readonly string _name; private readonly XactSound[] _sounds; - private readonly float[] _probs; + private readonly float[] _probs; + + private readonly RpcVariable[] _variables; private XactSound _curSound; - private float _volume = 1.0f; + + private bool _applied3D; + private bool _played; /// Indicates whether or not the cue is currently paused. /// IsPlaying and IsPaused both return true if a cue is paused while playing. - public bool IsPaused - { - get + public bool IsPaused + { + get { - if (_curSound != null) - return _curSound.IsPaused; + if (_curSound != null) + return _curSound.IsPaused; - return false; - } - } + return false; + } + } /// Indicates whether or not the cue is currently playing. /// IsPlaying and IsPaused both return true if a cue is paused while playing. - public bool IsPlaying - { - get + public bool IsPlaying + { + get { - if (_curSound != null) - return _curSound.Playing; + if (_curSound != null) + return _curSound.Playing; - return false; - } - } + return false; + } + } /// Indicates whether or not the cue is currently stopped. - public bool IsStopped - { - get + public bool IsStopped + { + get { - if (_curSound != null) + if (_curSound != null) return _curSound.Stopped; - return true; - } - } - - public bool IsStopping - { - get - { - // TODO: Implement me! - return false; + return !IsDisposed && !IsPrepared; } } - public bool IsPreparing + public bool IsStopping { get { @@ -78,98 +73,122 @@ public bool IsPreparing } } - public bool IsPrepared + public bool IsPreparing { - get - { - // TODO: Implement me! - return false; - } + get { return false; } } + public bool IsPrepared { get; internal set; } + + public bool IsCreated { get; internal set; } + /// Gets the friendly name of the cue. /// The friendly name is a value set from the designer. - public string Name - { - get { return _name; } - } - - internal Cue(AudioEngine engine, string cuename, XactSound sound) - { - _engine = engine; - _name = cuename; - _sounds = new XactSound[1]; - _sounds[0] = sound; - _probs = new float[1]; - _probs[0] = 1.0f; - } - - internal Cue(AudioEngine engine, string cuename, XactSound[] sounds, float[] probs) - { + public string Name + { + get { return _name; } + } + + internal Cue(AudioEngine engine, string cuename, XactSound sound) + { _engine = engine; - _name = cuename; - _sounds = sounds; - _probs = probs; - } + _name = cuename; + _sounds = new XactSound[1]; + _sounds[0] = sound; + _probs = new float[1]; + _probs[0] = 1.0f; + _variables = engine.CreateCueVariables(); + } + + internal Cue(AudioEngine engine, string cuename, XactSound[] sounds, float[] probs) + { + _engine = engine; + _name = cuename; + _sounds = sounds; + _probs = probs; + _variables = engine.CreateCueVariables(); + } + + internal void Prepare() + { + IsDisposed = false; + IsCreated = false; + IsPrepared = true; + _curSound = null; + } /// Pauses playback. - public void Pause() - { - if (_curSound != null) - _curSound.Pause(); - } + public void Pause() + { + if (_curSound != null) + _curSound.Pause(); + } /// Requests playback of a prepared or preparing Cue. /// Calling Play when the Cue already is playing can result in an InvalidOperationException. - public void Play() - { - if (!_engine._activeCues.Contains(this)) - _engine._activeCues.Add(this); - - //TODO: Probabilities + public void Play() + { + if (!_engine.ActiveCues.Contains(this)) + _engine.ActiveCues.Add(this); + + //TODO: Probabilities var index = XactHelpers.Random.Next(_sounds.Length); _curSound = _sounds[index]; - - _curSound.SetCueVolume(_volume); - _curSound.Play(); - } + + _curSound.Play(1.0f, _engine); + _played = true; + IsPrepared = false; + } /// Resumes playback of a paused Cue. - public void Resume() - { - if (_curSound != null) - _curSound.Resume(); - } + public void Resume() + { + if (_curSound != null) + _curSound.Resume(); + } /// Stops playback of a Cue. /// Specifies if the sound should play any pending release phases or transitions before stopping. - public void Stop(AudioStopOptions options) - { - _engine._activeCues.Remove(this); - - if (_curSound != null) + public void Stop(AudioStopOptions options) + { + _engine.ActiveCues.Remove(this); + + if (_curSound != null) _curSound.Stop(options); - } - + + IsPrepared = false; + } + + private int FindVariable(string name) + { + // Do a simple linear search... which is fast + // for as little variables as most cues have. + for (var i = 0; i < _variables.Length; i++) + { + if (_variables[i].Name == name) + return i; + } + + return -1; + } + /// /// Sets the value of a cue-instance variable based on its friendly name. /// /// Friendly name of the variable to set. /// Value to assign to the variable. /// The friendly name is a value set from the designer. - public void SetVariable (string name, float value) - { - if (name == "Volume") - { - _volume = value; - if (_curSound != null) - _curSound.SetCueVolume(_volume); - } - else - { - _engine.SetGlobalVariable (name, value); - } - } + public void SetVariable(string name, float value) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException("name"); + + var i = FindVariable(name); + if (i == -1 || !_variables[i].IsPublic) + throw new IndexOutOfRangeException("The specified variable index is invalid."); + + _variables[i].SetValue(value); + } /// Gets a cue-instance variable value based on its friendly name. /// Friendly name of the variable. @@ -178,13 +197,17 @@ public void SetVariable (string name, float value) /// Cue-instance variables are useful when multiple instantiations of a single cue (and its associated sounds) are required (for example, a "car" cue where there may be more than one car at any given time). While a global variable allows multiple audio elements to be controlled in unison, a cue instance variable grants discrete control of each instance of a cue, even for each copy of the same cue. /// The friendly name is a value set from the designer. /// - public float GetVariable (string name) - { - if (name == "Volume") - return _volume; + public float GetVariable(string name) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException("name"); - return _engine.GetGlobalVariable (name); - } + var i = FindVariable(name); + if (i == -1 || !_variables[i].IsPublic) + throw new IndexOutOfRangeException("The specified variable index is invalid."); + + return _variables[i].Value; + } /// Updates the simulated 3D Audio settings calculated between an AudioEmitter and AudioListener. /// The listener to calculate. @@ -193,28 +216,133 @@ public float GetVariable (string name) /// This must be called before Play(). /// Calling this method automatically converts the sound to monoaural and sets the speaker mix for any sound played by this cue to a value calculated with the listener's and emitter's positions. Any stereo information in the sound will be discarded. /// - public void Apply3D(AudioListener listener, AudioEmitter emitter) + public void Apply3D(AudioListener listener, AudioEmitter emitter) { + if (listener == null) + throw new ArgumentNullException("listener"); + if (emitter == null) + throw new ArgumentNullException("emitter"); + + if (_played && !_applied3D) + throw new InvalidOperationException("You must call Apply3D on a Cue before calling Play to be able to call Apply3D after calling Play."); + + var direction = listener.Position - emitter.Position; + + // Set the distance for falloff. + var distance = direction.Length(); + var i = FindVariable("Distance"); + _variables[i].SetValue(distance); + + // Calculate the orientation. + if (distance > 0.0f) + direction /= distance; + var right = Vector3.Cross(listener.Up, listener.Forward); + var slope = Vector3.Dot(direction, listener.Forward); + var angle = MathHelper.ToDegrees((float)Math.Acos(slope)); + var j = FindVariable("OrientationAngle"); + _variables[j].SetValue(angle); if (_curSound != null) - _curSound.Apply3D(listener, emitter); + _curSound.SetCuePan(Vector3.Dot(direction, right)); + + // Calculate doppler effect. + var relativeVelocity = emitter.Velocity - listener.Velocity; + relativeVelocity *= emitter.DopplerScale; + + _applied3D = true; } internal void Update(float dt) { if (_curSound != null) _curSound.Update(dt); + + // Evaluate the runtime parameter controls. + var rpcCurves = _curSound.RpcCurves; + if (rpcCurves.Length > 0) + { + var volume = 1.0f; + var pitch = 0.0f; + var reverbMix = 1.0f; + float? filterFrequency = null; + float? filterQFactor = null; + + for (var i = 0; i < rpcCurves.Length; i++) + { + var rpcCurve = _engine.RpcCurves[rpcCurves[i]]; + + // Some curves are driven by global variables and others by cue instance variables. + float value; + if (rpcCurve.IsGlobal) + value = rpcCurve.Evaluate(_engine.GetGlobalVariable(rpcCurve.Variable)); + else + value = rpcCurve.Evaluate(_variables[rpcCurve.Variable].Value); + + // Process the final curve value based on the parameter type it is. + switch (rpcCurve.Parameter) + { + case RpcParameter.Volume: + volume *= XactHelpers.ParseVolumeFromDecibels(value / 100.0f); + break; + + case RpcParameter.Pitch: + pitch += value / 1000.0f; + break; + + case RpcParameter.ReverbSend: + reverbMix *= XactHelpers.ParseVolumeFromDecibels(value / 100.0f); + break; + + case RpcParameter.FilterFrequency: + filterFrequency = value; + break; + + case RpcParameter.FilterQFactor: + filterQFactor = value; + break; + + default: + throw new ArgumentOutOfRangeException("rpcCurve.Parameter"); + } + } + + _curSound.UpdateState(_engine, volume, pitch, reverbMix, filterFrequency, filterQFactor); + } + } + + /// + /// This event is triggered when the Cue is disposed. + /// + public event EventHandler Disposing; + + /// + /// Is true if the Cue has been disposed. + /// + public bool IsDisposed { get; internal set; } + + /// + /// Disposes the Cue. + /// + public void Dispose() + { + Dispose(true); + } + + private void Dispose(bool disposing) + { + if (IsDisposed) + return; + + IsDisposed = true; + + if (disposing) + { + IsCreated = false; + IsPrepared = false; + + if (Disposing != null) + Disposing(this, EventArgs.Empty); + } } - - - /// Indicateds whether or not the object has been disposed. - public bool IsDisposed { get { return false; } } - - #region IDisposable implementation - /// Immediately releases any unmanaged resources used by this object. - public void Dispose () - { - } - #endregion - } + } } diff --git a/MonoGame.Framework/Audio/Xact/DspParameter.cs b/MonoGame.Framework/Audio/Xact/DspParameter.cs new file mode 100644 index 00000000000..4ecf9cefe0a --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/DspParameter.cs @@ -0,0 +1,47 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System.IO; + +namespace Microsoft.Xna.Framework.Audio +{ + struct DspParameter + { + public float Value; + public readonly float MinValue; + public readonly float MaxValue; + + public DspParameter(BinaryReader reader) + { + // This is 1 if the type is byte sized and 0 for + // floats... not sure if we should use this info. + reader.ReadByte(); + + // The value and the min/max range for limiting the + // results from the RPC curve when animated. + Value = reader.ReadSingle(); + MinValue = reader.ReadSingle(); + MaxValue = reader.ReadSingle(); + + // Looks to always be zero... maybe some padding + // for future expansion that never occured? + reader.ReadUInt16(); + } + + public void SetValue(float value) + { + if (value < MinValue) + Value = MinValue; + else if (value > MaxValue) + Value = MaxValue; + else + Value = value; + } + + public override string ToString() + { + return "Value:" + Value + " MinValue:" + MinValue + " MaxValue:" + MaxValue; + } + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/FilterMode.cs b/MonoGame.Framework/Audio/Xact/FilterMode.cs new file mode 100644 index 00000000000..e2970c2c8b5 --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/FilterMode.cs @@ -0,0 +1,13 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +namespace Microsoft.Xna.Framework.Audio +{ + enum FilterMode + { + LowPass = 0, + BandPass = 1, + HighPass = 2, + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/MaxInstanceBehavior.cs b/MonoGame.Framework/Audio/Xact/MaxInstanceBehavior.cs new file mode 100644 index 00000000000..6bc9e61a1c4 --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/MaxInstanceBehavior.cs @@ -0,0 +1,15 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +namespace Microsoft.Xna.Framework.Audio +{ + enum MaxInstanceBehavior + { + FailToPlay, + Queue, + ReplaceOldest, + ReplaceQuietest, + ReplaceLowestPriority, + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/MiniFormatTag.cs b/MonoGame.Framework/Audio/Xact/MiniFormatTag.cs new file mode 100644 index 00000000000..ee94888329a --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/MiniFormatTag.cs @@ -0,0 +1,17 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +namespace Microsoft.Xna.Framework.Audio +{ + enum MiniFormatTag + { + Pcm = 0x0, + Xma = 0x1, + Adpcm = 0x2, + Wma = 0x3, + + // We allow XMA to be reused for a platform specific format. + PlatformSpecific = Xma, + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/PlayWaveEvent.cs b/MonoGame.Framework/Audio/Xact/PlayWaveEvent.cs index ca6c136e1a4..a54ef677b15 100644 --- a/MonoGame.Framework/Audio/Xact/PlayWaveEvent.cs +++ b/MonoGame.Framework/Audio/Xact/PlayWaveEvent.cs @@ -16,7 +16,7 @@ enum VariationType Shuffle }; - class PlayWaveEvent : ClipEvent + class PlayWaveEvent : ClipEvent { private readonly SoundBank _soundBank; @@ -33,7 +33,15 @@ class PlayWaveEvent : ClipEvent private readonly int _totalWeights; private float _trackVolume; + private float _trackPitch; + private float _trackFilterFrequency; + private float _trackFilterQFactor; + private float _clipVolume; + private float _clipPitch; + private float _clipReverbMix; + + private readonly Vector4? _filterVar; private readonly Vector2? _volumeVar; private readonly Vector2? _pitchVar; @@ -44,7 +52,7 @@ class PlayWaveEvent : ClipEvent public PlayWaveEvent( XactClip clip, float timeStamp, float randomOffset, SoundBank soundBank, int[] waveBanks, int[] tracks, byte[] weights, int totalWeights, - VariationType variation, Vector2? volumeVar, Vector2? pitchVar, + VariationType variation, Vector2? volumeVar, Vector2? pitchVar, Vector4? filterVar, int loopCount, bool newWaveOnLoop) : base(clip, timeStamp, randomOffset) { @@ -55,15 +63,25 @@ public PlayWaveEvent( XactClip clip, float timeStamp, float randomOffset, Soun _totalWeights = totalWeights; _volumeVar = volumeVar; _pitchVar = pitchVar; + _filterVar = filterVar; _wavIndex = -1; _loopIndex = 0; + _trackVolume = 1.0f; + _trackPitch = 0; + _trackFilterFrequency = 0; + _trackFilterQFactor = 0; + + _clipVolume = 1.0f; + _clipPitch = 0; + _clipReverbMix = 0; + _variation = variation; _loopCount = loopCount; _newWaveOnLoop = newWaveOnLoop; } - public override void Play() + public override void Play() { if (_wav != null && _wav.State != SoundState.Stopped) _wav.Stop(); @@ -145,21 +163,34 @@ private void Play(bool pickNewWav) return; } - // Set the volume. - SetTrackVolume(_trackVolume); - - // Set the pitch. + // Do all the randoms before we play. + if (_volumeVar.HasValue) + _trackVolume = _volumeVar.Value.X + ((float)XactHelpers.Random.NextDouble() * _volumeVar.Value.Y); if (_pitchVar.HasValue) - _wav.Pitch = _pitchVar.Value.X + ((float)XactHelpers.Random.NextDouble() * _pitchVar.Value.Y); - else - _wav.Pitch = 0; - + _trackPitch = _pitchVar.Value.X + ((float)XactHelpers.Random.NextDouble() * _pitchVar.Value.Y); + if (_clip.FilterEnabled) + { + if (_filterVar.HasValue) + { + _trackFilterFrequency = _filterVar.Value.X + ((float)XactHelpers.Random.NextDouble() * _filterVar.Value.Y); + _trackFilterQFactor = _filterVar.Value.Z + ((float)XactHelpers.Random.NextDouble() * _filterVar.Value.W); + } + else + { + _trackFilterFrequency = _clip.FilterFrequency; + _trackFilterQFactor = _clip.FilterQ; + } + } + // This is a shortcut for infinite looping of a single track. _wav.IsLooped = _loopCount == 255 && trackCount == 1; + + // Update all the wave states then play. + UpdateState(); _wav.Play(); - } + } - public override void Stop() + public override void Stop() { if (_wav != null) { @@ -167,31 +198,58 @@ public override void Stop() _wav = null; } _loopIndex = 0; - } + } - public override void Pause() + public override void Pause() { if (_wav != null) _wav.Pause(); - } + } - public override void Resume() - { + public override void Resume() + { if (_wav != null && _wav.State == SoundState.Paused) _wav.Resume(); - } + } public override void SetTrackVolume(float volume) { - _trackVolume = volume; + _clipVolume = volume; + if (_wav != null) + _wav.Volume = _trackVolume * _clipVolume; + } + public override void SetTrackPan(float pan) + { if (_wav != null) - { - if (_volumeVar.HasValue) - _wav.Volume = _trackVolume * (_volumeVar.Value.X + ((float)XactHelpers.Random.NextDouble() * _volumeVar.Value.Y)); - else - _wav.Volume = _trackVolume; - } + _wav.Pan = pan; + } + + public override void SetState(float volume, float pitch, float reverbMix, float? filterFrequency, float? filterQFactor) + { + _clipVolume = volume; + _clipPitch = pitch; + _clipReverbMix = reverbMix; + + // The RPC filter overrides the randomized track filter. + if (filterFrequency.HasValue) + _trackFilterFrequency = filterFrequency.Value; + if (filterQFactor.HasValue) + _trackFilterQFactor = filterQFactor.Value; + + if (_wav != null) + UpdateState(); + } + + private void UpdateState() + { + _wav.Volume = _trackVolume * _clipVolume; + _wav.Pitch = _trackPitch + _clipPitch; + + if (_clip.UseReverb) + _wav.PlatformSetReverbMix(_clipReverbMix); + if (_clip.FilterEnabled) + _wav.PlatformSetFilter(_clip.FilterMode, _trackFilterQFactor, _trackFilterFrequency); } public override void SetFade(float fadeInDuration, float fadeOutDuration) @@ -223,12 +281,6 @@ public override bool Update(float dt) return _wav != null && _wav.State != SoundState.Stopped; } - - public override void Apply3D(AudioListener listener, AudioEmitter emitter) - { - if (_wav != null) - _wav.Apply3D(listener, emitter); - } } } diff --git a/MonoGame.Framework/Audio/Xact/ReverbSettings.cs b/MonoGame.Framework/Audio/Xact/ReverbSettings.cs new file mode 100644 index 00000000000..6c414f5f596 --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/ReverbSettings.cs @@ -0,0 +1,69 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; +using System.IO; + +namespace Microsoft.Xna.Framework.Audio +{ + class ReverbSettings + { + private readonly DspParameter[] _parameters = new DspParameter[22]; + + public ReverbSettings(BinaryReader reader) + { + _parameters[0] = new DspParameter(reader); // ReflectionsDelayMs + _parameters[1] = new DspParameter(reader); // ReverbDelayMs + _parameters[2] = new DspParameter(reader); // PositionLeft + _parameters[3] = new DspParameter(reader); // PositionRight + _parameters[4] = new DspParameter(reader); // PositionLeftMatrix + _parameters[5] = new DspParameter(reader); // PositionRightMatrix + _parameters[6] = new DspParameter(reader); // EarlyDiffusion + _parameters[7] = new DspParameter(reader); // LateDiffusion + _parameters[8] = new DspParameter(reader); // LowEqGain + _parameters[9] = new DspParameter(reader); // LowEqCutoff + _parameters[10] = new DspParameter(reader); // HighEqGain + _parameters[11] = new DspParameter(reader); // HighEqCutoff + _parameters[12] = new DspParameter(reader); // RearDelayMs + _parameters[13] = new DspParameter(reader); // RoomFilterFrequencyHz + _parameters[14] = new DspParameter(reader); // RoomFilterMainDb + _parameters[15] = new DspParameter(reader); // RoomFilterHighFrequencyDb + _parameters[16] = new DspParameter(reader); // ReflectionsGainDb + _parameters[17] = new DspParameter(reader); // ReverbGainDb + _parameters[18] = new DspParameter(reader); // DecayTimeSec + _parameters[19] = new DspParameter(reader); // DensityPct + _parameters[20] = new DspParameter(reader); // RoomSizeFeet + _parameters[21] = new DspParameter(reader); // WetDryMixPct + } + + public float this[int index] + { + get { return _parameters[index].Value; } + set { _parameters[index].SetValue(value); } + } + + public float ReflectionsDelayMs { get { return _parameters[0].Value; } } + public float ReverbDelayMs { get { return _parameters[1].Value; } } + public float PositionLeft { get { return _parameters[2].Value; } } + public float PositionRight { get { return _parameters[3].Value; } } + public float PositionLeftMatrix { get { return _parameters[4].Value; } } + public float PositionRightMatrix { get { return _parameters[5].Value; } } + public float EarlyDiffusion { get { return _parameters[6].Value; } } + public float LateDiffusion { get { return _parameters[7].Value; } } + public float LowEqGain { get { return _parameters[8].Value; } } + public float LowEqCutoff { get { return _parameters[9].Value; } } + public float HighEqGain { get { return _parameters[10].Value; } } + public float HighEqCutoff { get { return _parameters[11].Value; } } + public float RearDelayMs { get { return _parameters[12].Value; } } + public float RoomFilterFrequencyHz { get { return _parameters[13].Value; } } + public float RoomFilterMainDb { get { return _parameters[14].Value; } } + public float RoomFilterHighFrequencyDb { get { return _parameters[15].Value; } } + public float ReflectionsGainDb { get { return _parameters[16].Value; } } + public float ReverbGainDb { get { return _parameters[17].Value; } } + public float DecayTimeSec { get { return _parameters[18].Value; } } + public float DensityPct { get { return _parameters[19].Value; } } + public float RoomSizeFeet { get { return _parameters[20].Value; } } + public float WetDryMixPct { get { return _parameters[21].Value; } } + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/RpcCurve.cs b/MonoGame.Framework/Audio/Xact/RpcCurve.cs new file mode 100644 index 00000000000..bba1861d8bf --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/RpcCurve.cs @@ -0,0 +1,47 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +namespace Microsoft.Xna.Framework.Audio +{ + struct RpcCurve + { + public uint FileOffset; + public int Variable; + public bool IsGlobal; + public RpcParameter Parameter; + public RpcPoint[] Points; + + public float Evaluate(float position) + { + // TODO: We need to implement the different RpcPointTypes. + + var first = Points[0]; + if (position <= first.Position) + return first.Value; + + var second = Points[Points.Length - 1]; + if (position >= second.Position) + return second.Value; + + for (var i = 1; i < Points.Length; ++i) + { + second = Points[i]; + if (second.Position >= position) + break; + + first = second; + } + + switch (first.Type) + { + default: + case RpcPointType.Linear: + { + var t = (position - first.Position) / (second.Position - first.Position); + return first.Value + ((second.Value - first.Value) * t); + } + } + } + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/RpcParameter.cs b/MonoGame.Framework/Audio/Xact/RpcParameter.cs new file mode 100644 index 00000000000..20b3d34d2fd --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/RpcParameter.cs @@ -0,0 +1,16 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +namespace Microsoft.Xna.Framework.Audio +{ + enum RpcParameter + { + Volume, + Pitch, + ReverbSend, + FilterFrequency, + FilterQFactor, + NumParameters, + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/RpcPoint.cs b/MonoGame.Framework/Audio/Xact/RpcPoint.cs new file mode 100644 index 00000000000..68be3816de9 --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/RpcPoint.cs @@ -0,0 +1,13 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +namespace Microsoft.Xna.Framework.Audio +{ + struct RpcPoint + { + public RpcPointType Type; + public float Position; + public float Value; + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/RpcPointType.cs b/MonoGame.Framework/Audio/Xact/RpcPointType.cs new file mode 100644 index 00000000000..f70e1e949a2 --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/RpcPointType.cs @@ -0,0 +1,14 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +namespace Microsoft.Xna.Framework.Audio +{ + enum RpcPointType + { + Linear, + Fast, + Slow, + SinCos + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/RpcVariable.cs b/MonoGame.Framework/Audio/Xact/RpcVariable.cs new file mode 100644 index 00000000000..39b76400f58 --- /dev/null +++ b/MonoGame.Framework/Audio/Xact/RpcVariable.cs @@ -0,0 +1,46 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +namespace Microsoft.Xna.Framework.Audio +{ + struct RpcVariable + { + public string Name; + public float Value; + public byte Flags; + public float InitValue; + public float MaxValue; + public float MinValue; + + public bool IsPublic + { + get { return (Flags & 0x1) != 0; } + } + + public bool IsReadOnly + { + get { return (Flags & 0x2) != 0; } + } + + public bool IsGlobal + { + get { return (Flags & 0x4) == 0; } + } + + public bool IsReserved + { + get { return (Flags & 0x8) != 0; } + } + + public void SetValue(float value) + { + if (value < MinValue) + Value = MinValue; + else if (value > MaxValue) + Value = MaxValue; + else + Value = value; + } + } +} \ No newline at end of file diff --git a/MonoGame.Framework/Audio/Xact/SoundBank.cs b/MonoGame.Framework/Audio/Xact/SoundBank.cs index 9520c4b6243..fad0d319db4 100644 --- a/MonoGame.Framework/Audio/Xact/SoundBank.cs +++ b/MonoGame.Framework/Audio/Xact/SoundBank.cs @@ -16,209 +16,206 @@ public class SoundBank : IDisposable readonly AudioEngine _audioengine; readonly string[] _waveBankNames; readonly WaveBank[] _waveBanks; - readonly Dictionary _cues = new Dictionary(); - - public bool IsDisposed { get; private set; } - internal AudioEngine AudioEngine { get { return _audioengine; } } - + readonly float [] defaultProbability = new float [1] { 1.0f }; + readonly Dictionary _sounds = new Dictionary(); + readonly Dictionary _probabilities = new Dictionary (); + + /// + /// Is true if the SoundBank has any live Cues in use. + /// + public bool IsInUse { get; private set; } + /// AudioEngine that will be associated with this sound bank. /// Path to a .xsb SoundBank file. public SoundBank(AudioEngine audioEngine, string fileName) { + if (audioEngine == null) + throw new ArgumentNullException("audioEngine"); + if (string.IsNullOrEmpty(fileName)) + throw new ArgumentNullException("fileName"); + _audioengine = audioEngine; - fileName = FileHelpers.NormalizeFilePathSeparators(fileName); -#if !ANDROID - using (var stream = TitleContainer.OpenStream(fileName)) - { -#else - using (var fileStream = Game.Activity.Assets.Open(fileName)) + using (var stream = AudioEngine.OpenStream(fileName)) + using (var reader = new BinaryReader(stream)) { - MemoryStream stream = new MemoryStream(); - fileStream.CopyTo(stream); - stream.Position = 0; -#endif - using (var reader = new BinaryReader(stream)) - { - // Thanks to Liandril for "xactxtract" for some of the offsets. - - uint magic = reader.ReadUInt32(); - if (magic != 0x4B424453) //"SDBK" - throw new Exception ("Bad soundbank format"); - - reader.ReadUInt16(); // toolVersion - - uint formatVersion = reader.ReadUInt16(); - // TODO: This does not match XNA, XNA uses 43. - if (formatVersion != 46) - Debug.WriteLine("Warning: SoundBank format {0} not supported.", formatVersion); - - reader.ReadUInt16(); // crc, TODO: Verify crc (FCS16) - - reader.ReadUInt32(); // lastModifiedLow - reader.ReadUInt32(); // lastModifiedHigh - reader.ReadByte(); // platform ??? - - uint numSimpleCues = reader.ReadUInt16(); - uint numComplexCues = reader.ReadUInt16(); - reader.ReadUInt16(); //unkn - reader.ReadUInt16(); // numTotalCues - uint numWaveBanks = reader.ReadByte(); - reader.ReadUInt16(); // numSounds - uint cueNameTableLen = reader.ReadUInt16(); - reader.ReadUInt16(); //unkn - - uint simpleCuesOffset = reader.ReadUInt32(); - uint complexCuesOffset = reader.ReadUInt32(); //unkn - uint cueNamesOffset = reader.ReadUInt32(); - reader.ReadUInt32(); //unkn - reader.ReadUInt32(); // variationTablesOffset - reader.ReadUInt32(); //unkn - uint waveBankNameTableOffset = reader.ReadUInt32(); - reader.ReadUInt32(); // cueNameHashTableOffset - reader.ReadUInt32(); // cueNameHashValsOffset - reader.ReadUInt32(); // soundsOffset - - //name = System.Text.Encoding.UTF8.GetString(soundbankreader.ReadBytes(64),0,64).Replace("\0",""); - - //parse wave bank name table - stream.Seek(waveBankNameTableOffset, SeekOrigin.Begin); - _waveBanks = new WaveBank[numWaveBanks]; - _waveBankNames = new string[numWaveBanks]; - for (int i=0; i 0) + // Thanks to Liandril for "xactxtract" for some of the offsets. + + uint magic = reader.ReadUInt32(); + if (magic != 0x4B424453) //"SDBK" + throw new Exception ("Bad soundbank format"); + + reader.ReadUInt16(); // toolVersion + + uint formatVersion = reader.ReadUInt16(); + if (formatVersion != 43) + Debug.WriteLine("Warning: SoundBank format {0} not supported.", formatVersion); + + reader.ReadUInt16(); // crc, TODO: Verify crc (FCS16) + + reader.ReadUInt32(); // lastModifiedLow + reader.ReadUInt32(); // lastModifiedHigh + reader.ReadByte(); // platform ??? + + uint numSimpleCues = reader.ReadUInt16(); + uint numComplexCues = reader.ReadUInt16(); + reader.ReadUInt16(); //unkn + reader.ReadUInt16(); // numTotalCues + uint numWaveBanks = reader.ReadByte(); + reader.ReadUInt16(); // numSounds + uint cueNameTableLen = reader.ReadUInt16(); + reader.ReadUInt16(); //unkn + + uint simpleCuesOffset = reader.ReadUInt32(); + uint complexCuesOffset = reader.ReadUInt32(); //unkn + uint cueNamesOffset = reader.ReadUInt32(); + reader.ReadUInt32(); //unkn + reader.ReadUInt32(); // variationTablesOffset + reader.ReadUInt32(); //unkn + uint waveBankNameTableOffset = reader.ReadUInt32(); + reader.ReadUInt32(); // cueNameHashTableOffset + reader.ReadUInt32(); // cueNameHashValsOffset + reader.ReadUInt32(); // soundsOffset + + //name = System.Text.Encoding.UTF8.GetString(soundbankreader.ReadBytes(64),0,64).Replace("\0",""); + + //parse wave bank name table + stream.Seek(waveBankNameTableOffset, SeekOrigin.Begin); + _waveBanks = new WaveBank[numWaveBanks]; + _waveBankNames = new string[numWaveBanks]; + for (int i=0; i 0) + { + stream.Seek(simpleCuesOffset, SeekOrigin.Begin); + for (int i = 0; i < numSimpleCues; i++) { - stream.Seek(simpleCuesOffset, SeekOrigin.Begin); - for (int i = 0; i < numSimpleCues; i++) + reader.ReadByte(); // flags + uint soundOffset = reader.ReadUInt32(); + + var oldPosition = stream.Position; + stream.Seek(soundOffset, SeekOrigin.Begin); + XactSound sound = new XactSound(audioEngine, this, reader); + stream.Seek(oldPosition, SeekOrigin.Begin); + + _sounds.Add(cueNames [i], new XactSound [] { sound } ); + _probabilities.Add (cueNames [i], defaultProbability); + } + } + + // Complex cues + if (numComplexCues > 0) + { + stream.Seek(complexCuesOffset, SeekOrigin.Begin); + for (int i = 0; i < numComplexCues; i++) + { + //Cue cue; + + byte flags = reader.ReadByte(); + if (((flags >> 2) & 1) != 0) { - reader.ReadByte(); // flags uint soundOffset = reader.ReadUInt32(); + reader.ReadUInt32(); //unkn var oldPosition = stream.Position; stream.Seek(soundOffset, SeekOrigin.Begin); - XactSound sound = new XactSound(this, reader); + XactSound sound = new XactSound(audioEngine, this, reader); stream.Seek(oldPosition, SeekOrigin.Begin); - Cue cue = new Cue(_audioengine, cueNames[i], sound); - _cues.Add(cue.Name, cue); + _sounds.Add (cueNames [numSimpleCues + i], new XactSound [] { sound }); + _probabilities.Add (cueNames [numSimpleCues + i], defaultProbability); } - } - - // Complex cues - if (numComplexCues > 0) - { - stream.Seek(complexCuesOffset, SeekOrigin.Begin); - for (int i = 0; i < numComplexCues; i++) + else { - Cue cue; + uint variationTableOffset = reader.ReadUInt32(); + reader.ReadUInt32(); // transitionTableOffset - byte flags = reader.ReadByte(); - if (((flags >> 2) & 1) != 0) - { - uint soundOffset = reader.ReadUInt32(); - reader.ReadUInt32(); //unkn + //parse variation table + long savepos = stream.Position; + stream.Seek(variationTableOffset, SeekOrigin.Begin); - var oldPosition = stream.Position; - stream.Seek(soundOffset, SeekOrigin.Begin); - XactSound sound = new XactSound(this, reader); - stream.Seek(oldPosition, SeekOrigin.Begin); + uint numEntries = reader.ReadUInt16(); + uint variationflags = reader.ReadUInt16(); + reader.ReadByte(); + reader.ReadUInt16(); + reader.ReadByte(); - cue = new Cue(_audioengine, cueNames[numSimpleCues + i], sound); - } - else - { - uint variationTableOffset = reader.ReadUInt32(); - reader.ReadUInt32(); // transitionTableOffset - - //parse variation table - long savepos = stream.Position; - stream.Seek(variationTableOffset, SeekOrigin.Begin); - - uint numEntries = reader.ReadUInt16(); - uint variationflags = reader.ReadUInt16(); - reader.ReadByte(); - reader.ReadUInt16(); - reader.ReadByte(); + XactSound[] cueSounds = new XactSound[numEntries]; + float[] probs = new float[numEntries]; - XactSound[] cueSounds = new XactSound[numEntries]; - float[] probs = new float[numEntries]; - - uint tableType = (variationflags >> 3) & 0x7; - for (int j = 0; j < numEntries; j++) + uint tableType = (variationflags >> 3) & 0x7; + for (int j = 0; j < numEntries; j++) + { + switch (tableType) { - switch (tableType) - { - case 0: //Wave - { - int trackIndex = reader.ReadUInt16(); - int waveBankIndex = reader.ReadByte(); - reader.ReadByte(); // weightMin - reader.ReadByte(); // weightMax - - cueSounds[j] = new XactSound(this, waveBankIndex, trackIndex); - break; - } - case 1: - { - uint soundOffset = reader.ReadUInt32(); - reader.ReadByte(); // weightMin - reader.ReadByte(); // weightMax - - var oldPosition = stream.Position; - stream.Seek(soundOffset, SeekOrigin.Begin); - cueSounds[j] = new XactSound(this, reader); - stream.Seek(oldPosition, SeekOrigin.Begin); - break; - } - case 3: - { - uint soundOffset = reader.ReadUInt32(); - reader.ReadSingle(); // weightMin - reader.ReadSingle(); // weightMax - reader.ReadUInt32(); // flags - - var oldPosition = stream.Position; - stream.Seek(soundOffset, SeekOrigin.Begin); - cueSounds[j] = new XactSound(this, reader); - stream.Seek(oldPosition, SeekOrigin.Begin); - break; - } - case 4: //CompactWave - { - int trackIndex = reader.ReadUInt16(); - int waveBankIndex = reader.ReadByte(); - cueSounds[j] = new XactSound(this, waveBankIndex, trackIndex); - break; - } - default: - throw new NotSupportedException(); - } + case 0: //Wave + { + int trackIndex = reader.ReadUInt16(); + int waveBankIndex = reader.ReadByte(); + reader.ReadByte(); // weightMin + reader.ReadByte(); // weightMax + + cueSounds[j] = new XactSound(this, waveBankIndex, trackIndex); + break; + } + case 1: + { + uint soundOffset = reader.ReadUInt32(); + reader.ReadByte(); // weightMin + reader.ReadByte(); // weightMax + + var oldPosition = stream.Position; + stream.Seek(soundOffset, SeekOrigin.Begin); + cueSounds[j] = new XactSound(audioEngine, this, reader); + stream.Seek(oldPosition, SeekOrigin.Begin); + break; + } + case 3: + { + uint soundOffset = reader.ReadUInt32(); + reader.ReadSingle(); // weightMin + reader.ReadSingle(); // weightMax + reader.ReadUInt32(); // flags + + var oldPosition = stream.Position; + stream.Seek(soundOffset, SeekOrigin.Begin); + cueSounds[j] = new XactSound(audioEngine, this, reader); + stream.Seek(oldPosition, SeekOrigin.Begin); + break; + } + case 4: //CompactWave + { + int trackIndex = reader.ReadUInt16(); + int waveBankIndex = reader.ReadByte(); + cueSounds[j] = new XactSound(this, waveBankIndex, trackIndex); + break; + } + default: + throw new NotSupportedException(); } - - stream.Seek(savepos, SeekOrigin.Begin); - - cue = new Cue(_audioengine, cueNames[numSimpleCues + i], cueSounds, probs); } - // Instance limiting - reader.ReadByte(); //instanceLimit - reader.ReadUInt16(); //fadeInSec, divide by 1000.0f - reader.ReadUInt16(); //fadeOutSec, divide by 1000.0f - reader.ReadByte(); //instanceFlags + stream.Seek(savepos, SeekOrigin.Begin); - _cues.Add(cue.Name, cue); + _sounds.Add (cueNames [numSimpleCues + i], cueSounds); + _probabilities.Add (cueNames [numSimpleCues + i], probs); } + + // Instance limiting + reader.ReadByte(); //instanceLimit + reader.ReadUInt16(); //fadeInSec, divide by 1000.0f + reader.ReadUInt16(); //fadeOutSec, divide by 1000.0f + reader.ReadByte(); //instanceFlags } - } - } + } + } } internal SoundEffectInstance GetSoundEffectInstance(int waveBankIndex, int trackIndex) @@ -237,7 +234,7 @@ internal SoundEffectInstance GetSoundEffectInstance(int waveBankIndex, int track var sound = waveBank.GetSoundEffect(trackIndex); return sound.GetPooledInstance(true); } - + /// /// Returns a pooled Cue object. /// @@ -248,20 +245,46 @@ internal SoundEffectInstance GetSoundEffectInstance(int waveBankIndex, int track /// public Cue GetCue(string name) { - // Note: In XNA this returns a new Cue instance, but that - // generates garbage which is one reason to not do it. - return _cues[name]; + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException("name"); + + XactSound[] sounds; + if (!_sounds.TryGetValue(name, out sounds)) + throw new ArgumentException(); + + float [] probs; + if (!_probabilities.TryGetValue (name, out probs)) + throw new ArgumentException (); + + IsInUse = true; + + var cue = new Cue (_audioengine, name, sounds, probs); + cue.Prepare(); + return cue; } - + /// /// Plays a cue. /// /// Name of the cue to play. - public void PlayCue(string name) - { - var cue = GetCue(name); + public void PlayCue(string name) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException("name"); + + XactSound[] sounds; + if (!_sounds.TryGetValue(name, out sounds)) + throw new ArgumentException(); + + float [] probs; + if (!_probabilities.TryGetValue (name, out probs)) + throw new ArgumentException (); + + IsInUse = true; + var cue = new Cue (_audioengine, name, sounds, probs); + cue.Prepare(); cue.Play(); - } + } /// /// Plays a cue with static 3D positional information. @@ -274,26 +297,67 @@ public void PlayCue(string name) /// The cue emitter state. public void PlayCue(string name, AudioListener listener, AudioEmitter emitter) { - var cue = GetCue(name); - cue.Play(); + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException("name"); + + XactSound[] sounds; + if (!_sounds.TryGetValue(name, out sounds)) + throw new InvalidOperationException(); + + float [] probs; + if (!_probabilities.TryGetValue (name, out probs)) + throw new ArgumentException (); + + IsInUse = true; + + var cue = new Cue (_audioengine, name, sounds, probs); + cue.Prepare(); cue.Apply3D(listener, emitter); + cue.Play(); } - #region IDisposable implementation /// - /// Immediately releases any unmanaged resources used by this object. + /// This event is triggered when the SoundBank is disposed. + /// + public event EventHandler Disposing; + + /// + /// Is true if the SoundBank has been disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Disposes the SoundBank. /// public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~SoundBank() + { + Dispose(false); + } + + private void Dispose(bool disposing) { if (IsDisposed) return; - foreach (var cue in _cues.Values) - cue.Dispose(); - IsDisposed = true; + + if (disposing) + { + //foreach (var cue in _cues.Values) + // cue.Dispose(); + + IsInUse = false; + + if (Disposing != null) + Disposing(this, EventArgs.Empty); + } } - #endregion } } diff --git a/MonoGame.Framework/Audio/Xact/VolumeEvent.cs b/MonoGame.Framework/Audio/Xact/VolumeEvent.cs index 6e998828336..49adbf9dc29 100644 --- a/MonoGame.Framework/Audio/Xact/VolumeEvent.cs +++ b/MonoGame.Framework/Audio/Xact/VolumeEvent.cs @@ -38,6 +38,14 @@ public override void SetTrackVolume(float volume) { } + public override void SetTrackPan(float pan) + { + } + + public override void SetState(float volume, float pitch, float reverbMix, float? filterFrequency, float? filterQFactor) + { + } + public override bool Update(float dt) { return false; @@ -47,9 +55,6 @@ public override void SetFade(float fadeInDuration, float fadeOutDuration) { } - public override void Apply3D(AudioListener listener, AudioEmitter emitter) - { - } } } diff --git a/MonoGame.Framework/Audio/Xact/WaveBank.cs b/MonoGame.Framework/Audio/Xact/WaveBank.cs index 5eccada30b1..c87e07d63ca 100644 --- a/MonoGame.Framework/Audio/Xact/WaveBank.cs +++ b/MonoGame.Framework/Audio/Xact/WaveBank.cs @@ -5,19 +5,14 @@ using System; using System.IO; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Utilities; - namespace Microsoft.Xna.Framework.Audio { /// Represents a collection of wave files. public class WaveBank : IDisposable { - private SoundEffect[] _sounds; + private readonly SoundEffect[] _sounds; private string _bankName; - public bool IsDisposed { get; private set; } - struct Segment { public int Offset; @@ -56,20 +51,28 @@ struct WaveBankData private const int Flag_SeekTables = 0x00080000; // Bank includes seek tables. private const int Flag_Mask = 0x000F0000; - private const int MiniFormatTag_PCM = 0x0; - private const int MiniFormatTag_XMA = 0x1; - private const int MiniFormatTag_ADPCM = 0x2; - private const int MiniForamtTag_WMA = 0x3; + /// + /// + public bool IsInUse { get; private set; } + + /// + /// + public bool IsPrepared { get; private set; } /// Instance of the AudioEngine to associate this wave bank with. /// Path to the .xwb file to load. /// This constructor immediately loads all wave data into memory at once. public WaveBank(AudioEngine audioEngine, string nonStreamingWaveBankFilename) { + if (audioEngine == null) + throw new ArgumentNullException("audioEngine"); + if (string.IsNullOrEmpty(nonStreamingWaveBankFilename)) + throw new ArgumentNullException("nonStreamingWaveBankFilename"); + //XWB PARSING //Adapted from MonoXNA //Originally adaped from Luigi Auriemma's unxwb - + WaveBankHeader wavebankheader; WaveBankData wavebankdata; WaveBankEntry wavebankentry; @@ -85,19 +88,9 @@ public WaveBank(AudioEngine audioEngine, string nonStreamingWaveBankFilename) int wavebank_offset = 0; - nonStreamingWaveBankFilename = FileHelpers.NormalizeFilePathSeparators(nonStreamingWaveBankFilename); + BinaryReader reader = new BinaryReader(AudioEngine.OpenStream(nonStreamingWaveBankFilename)); -#if !ANDROID - BinaryReader reader = new BinaryReader(TitleContainer.OpenStream(nonStreamingWaveBankFilename)); -#else - Stream stream = Game.Activity.Assets.Open(nonStreamingWaveBankFilename); - MemoryStream ms = new MemoryStream(); - stream.CopyTo( ms ); - stream.Close(); - ms.Position = 0; - BinaryReader reader = new BinaryReader(ms); -#endif - reader.ReadBytes(4); + reader.ReadBytes(4); wavebankheader.Version = reader.ReadInt32(); @@ -177,8 +170,8 @@ public WaveBank(AudioEngine audioEngine, string nonStreamingWaveBankFilename) //SHOWFILEOFF; //memset(&wavebankentry, 0, sizeof(wavebankentry)); - wavebankentry.LoopRegion.Length = 0; - wavebankentry.LoopRegion.Offset = 0; + wavebankentry.LoopRegion.Length = 0; + wavebankentry.LoopRegion.Offset = 0; if ((wavebankdata.Flags & Flag_Compact) != 0) { @@ -239,8 +232,8 @@ public WaveBank(AudioEngine audioEngine, string nonStreamingWaveBankFilename) wavebankentry.PlayRegion.Offset += playregion_offset; // Parse WAVEBANKMINIWAVEFORMAT - - int codec; + + MiniFormatTag codec; int chans; int rate; int align; @@ -258,7 +251,7 @@ public WaveBank(AudioEngine audioEngine, string nonStreamingWaveBankFilename) // | wBlockAlign // wBitsPerSample - codec = (wavebankentry.Format) & ((1 << 1) - 1); + codec = (MiniFormatTag)((wavebankentry.Format) & ((1 << 1) - 1)); chans = (wavebankentry.Format >> (1)) & ((1 << 3) - 1); rate = (wavebankentry.Format >> (1 + 3 + 1)) & ((1 << 18) - 1); align = (wavebankentry.Format >> (1 + 3 + 1 + 18)) & ((1 << 8) - 1); @@ -295,7 +288,7 @@ public WaveBank(AudioEngine audioEngine, string nonStreamingWaveBankFilename) // | wBlockAlign // wBitsPerSample - codec = (wavebankentry.Format) & ((1 << 2) - 1); + codec = (MiniFormatTag)((wavebankentry.Format) & ((1 << 2) - 1)); chans = (wavebankentry.Format >> (2)) & ((1 << 3) - 1); rate = (wavebankentry.Format >> (2 + 3)) & ((1 << 18) - 1); align = (wavebankentry.Format >> (2 + 3 + 18)) & ((1 << 8) - 1); @@ -304,113 +297,16 @@ public WaveBank(AudioEngine audioEngine, string nonStreamingWaveBankFilename) reader.BaseStream.Seek(wavebankentry.PlayRegion.Offset, SeekOrigin.Begin); byte[] audiodata = reader.ReadBytes(wavebankentry.PlayRegion.Length); - - if (codec == MiniFormatTag_PCM) { - - //write PCM data into a wav -#if DIRECTX - - // TODO: Wouldn't storing a SoundEffectInstance like this - // result in the "parent" SoundEffect being garbage collected? - - SharpDX.Multimedia.WaveFormat waveFormat = new SharpDX.Multimedia.WaveFormat(rate, chans); - var sfx = new SoundEffect(audiodata, 0, audiodata.Length, rate, (AudioChannels)chans, wavebankentry.LoopRegion.Offset, wavebankentry.LoopRegion.Length) - { - _format = waveFormat - }; - - _sounds[current_entry] = sfx; -#else - _sounds[current_entry] = new SoundEffect(audiodata, rate, (AudioChannels)chans); -#endif - } else if (codec == MiniForamtTag_WMA) { //WMA or xWMA (or XMA2) - byte[] wmaSig = {0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11, 0xa6, 0xd9, 0x0, 0xaa, 0x0, 0x62, 0xce, 0x6c}; - - bool isWma = true; - for (int i=0; iInstance of the AudioEngine to associate this wave bank with. /// Path to the .xwb to stream from. /// DVD sector-aligned offset within the wave bank data file. @@ -420,31 +316,61 @@ public WaveBank(AudioEngine audioEngine, string nonStreamingWaveBankFilename) /// Note that packetsize is in sectors, which is 2048 bytes. /// AudioEngine.Update() must be called at least once before using data from a streaming wave bank. /// - public WaveBank(AudioEngine audioEngine, string streamingWaveBankFilename, int offset, short packetsize) - : this(audioEngine, streamingWaveBankFilename) - { - if (offset != 0) { - throw new NotImplementedException(); - } - } + public WaveBank(AudioEngine audioEngine, string streamingWaveBankFilename, int offset, short packetsize) + : this(audioEngine, streamingWaveBankFilename) + { + if (offset != 0) + throw new NotImplementedException(); + } internal SoundEffect GetSoundEffect(int trackIndex) { return _sounds[trackIndex]; } - #region IDisposable implementation - public void Dispose () - { + /// + /// This event is triggered when the WaveBank is disposed. + /// + public event EventHandler Disposing; + + /// + /// Is true if the WaveBank has been disposed. + /// + public bool IsDisposed { get; private set; } + + /// + /// Disposes the WaveBank. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~WaveBank() + { + Dispose(false); + } + + private void Dispose(bool disposing) + { if (IsDisposed) return; - foreach (var s in _sounds) - s.Dispose(); - IsDisposed = true; + + if (disposing) + { + foreach (var s in _sounds) + s.Dispose(); + + IsPrepared = false; + IsInUse = false; + + if (Disposing != null) + Disposing(this, EventArgs.Empty); + } } - #endregion } } diff --git a/MonoGame.Framework/Audio/Xact/XactClip.cs b/MonoGame.Framework/Audio/Xact/XactClip.cs index f372f0f73bc..fb8ffe5fbcb 100644 --- a/MonoGame.Framework/Audio/Xact/XactClip.cs +++ b/MonoGame.Framework/Audio/Xact/XactClip.cs @@ -7,50 +7,63 @@ namespace Microsoft.Xna.Framework.Audio { - class XactClip - { + class XactClip + { private readonly float _defaultVolume; private float _volumeScale; private float _volume; - private readonly ClipEvent[] _events; + private readonly ClipEvent[] _events; private float _time; private int _nextEvent; - public XactClip (SoundBank soundBank, BinaryReader clipReader) - { + internal readonly bool FilterEnabled; + internal readonly FilterMode FilterMode; + internal readonly float FilterQ; + internal readonly ushort FilterFrequency; + + internal readonly bool UseReverb; + + public XactClip (SoundBank soundBank, BinaryReader clipReader, bool useReverb) + { #pragma warning disable 0219 State = SoundState.Stopped; - var volumeDb = XactHelpers.ParseDecibels(clipReader.ReadByte()); + UseReverb = useReverb; + + var volumeDb = XactHelpers.ParseDecibels(clipReader.ReadByte()); _defaultVolume = XactHelpers.ParseVolumeFromDecibels(volumeDb); var clipOffset = clipReader.ReadUInt32(); - // Unknown! - clipReader.ReadUInt32(); - - var oldPosition = clipReader.BaseStream.Position; - clipReader.BaseStream.Seek(clipOffset, SeekOrigin.Begin); - - var numEvents = clipReader.ReadByte(); - _events = new ClipEvent[numEvents]; - - for (var i=0; i> 1) & 3); + FilterQ = (filterQAndFlags >> 3) * 0.01f; + FilterFrequency = clipReader.ReadUInt16(); + + var oldPosition = clipReader.BaseStream.Position; + clipReader.BaseStream.Seek(clipOffset, SeekOrigin.Begin); + + var numEvents = clipReader.ReadByte(); + _events = new ClipEvent[numEvents]; + + for (var i=0; i> 5) & 0xFFFF) * 0.001f; var unknown = eventInfo >> 21; - switch (eventId) { + switch (eventId) { case 0: // Stop Event throw new NotImplementedException("Stop event"); - case 1: + case 1: { // Unknown! clipReader.ReadByte(); @@ -61,9 +74,9 @@ public XactClip (SoundBank soundBank, BinaryReader clipReader) var panEnabled = (eventFlags & 0x02) == 0x02; var useCenterSpeaker = (eventFlags & 0x04) == 0x04; - int trackIndex = clipReader.ReadUInt16(); + int trackIndex = clipReader.ReadUInt16(); int waveBankIndex = clipReader.ReadByte(); - var loopCount = clipReader.ReadByte(); + var loopCount = clipReader.ReadByte(); var panAngle = clipReader.ReadUInt16() / 100.0f; var panArc = clipReader.ReadUInt16() / 100.0f; @@ -79,10 +92,11 @@ public XactClip (SoundBank soundBank, BinaryReader clipReader) VariationType.Ordered, null, null, + null, loopCount, false); - break; + break; } case 3: @@ -141,6 +155,7 @@ public XactClip (SoundBank soundBank, BinaryReader clipReader) variationType, null, null, + null, loopCount, newWaveOnLoop); @@ -173,14 +188,31 @@ public XactClip (SoundBank soundBank, BinaryReader clipReader) var maxVolume = XactHelpers.ParseVolumeFromDecibels(clipReader.ReadByte()); // Filter variation - var minFrequency = clipReader.ReadSingle() / 1000.0f; - var maxFrequency = clipReader.ReadSingle() / 1000.0f; + var minFrequency = clipReader.ReadSingle(); + var maxFrequency = clipReader.ReadSingle(); var minQ = clipReader.ReadSingle(); var maxQ = clipReader.ReadSingle(); // Unknown! clipReader.ReadByte(); + var variationFlags = clipReader.ReadByte(); + + // Enable pitch variation + Vector2? pitchVar = null; + if ((variationFlags & 0x10) == 0x10) + pitchVar = new Vector2(minPitch, maxPitch - minPitch); + + // Enable volume variation + Vector2? volumeVar = null; + if ((variationFlags & 0x20) == 0x20) + volumeVar = new Vector2(minVolume, maxVolume - minVolume); + + // Enable filter variation + Vector4? filterVar = null; + if ((variationFlags & 0x40) == 0x40) + filterVar = new Vector4(minFrequency, maxFrequency - minFrequency, minQ, maxQ - minQ); + _events[i] = new PlayWaveEvent( this, timeStamp, @@ -191,8 +223,9 @@ public XactClip (SoundBank soundBank, BinaryReader clipReader) null, 0, VariationType.Ordered, - new Vector2(minVolume, maxVolume - minVolume), - new Vector2(minPitch, maxPitch - minPitch), + volumeVar, + pitchVar, + filterVar, loopCount, false); @@ -223,8 +256,8 @@ public XactClip (SoundBank soundBank, BinaryReader clipReader) var maxVolume = XactHelpers.ParseVolumeFromDecibels(clipReader.ReadByte()); // Filter variation range - var minFrequency = clipReader.ReadSingle() / 1000.0f; - var maxFrequency = clipReader.ReadSingle() / 1000.0f; + var minFrequency = clipReader.ReadSingle(); + var maxFrequency = clipReader.ReadSingle(); var minQ = clipReader.ReadSingle(); var maxQ = clipReader.ReadSingle(); @@ -290,6 +323,7 @@ public XactClip (SoundBank soundBank, BinaryReader clipReader) variationType, volumeVar, pitchVar, + filterVar, loopCount, newWaveOnLoop); @@ -331,14 +365,14 @@ public XactClip (SoundBank soundBank, BinaryReader clipReader) // Marker Event throw new NotImplementedException("Marker event"); - default: + default: throw new NotSupportedException("Unknown event " + eventId); - } - } - + } + } + clipReader.BaseStream.Seek (oldPosition, SeekOrigin.Begin); #pragma warning restore 0219 - } + } internal void Update(float dt) { @@ -379,36 +413,45 @@ internal void SetFade(float fadeInDuration, float fadeOutDuration) evt.SetFade(fadeInDuration, fadeOutDuration); } } - - public void Play() - { - _time = 0.0f; + + internal void UpdateState(float volume, float pitch, float reverbMix, float? filterFrequency, float? filterQFactor) + { + _volumeScale = volume; + var trackVolume = _volume * _volumeScale; + + foreach (var evt in _events) + evt.SetState(trackVolume, pitch, reverbMix, filterFrequency, filterQFactor); + } + + public void Play() + { + _time = 0.0f; _nextEvent = 0; - SetVolume(_defaultVolume); + SetVolume(_defaultVolume); State = SoundState.Playing; Update(0); } - public void Resume() - { + public void Resume() + { foreach (var evt in _events) evt.Resume(); State = SoundState.Playing; - } - - public void Stop() + } + + public void Stop() { foreach (var evt in _events) evt.Stop(); State = SoundState.Stopped; } - - public void Pause() + + public void Pause() { - foreach (var evt in _events) - evt.Pause(); + foreach (var evt in _events) + evt.Pause(); State = SoundState.Paused; } @@ -422,7 +465,7 @@ public void Pause() public void SetVolumeScale(float volume) { _volumeScale = volume; - UpdateVolumes(); + UpdateVolumes(); } /// @@ -435,17 +478,17 @@ public void SetVolume(float volume) UpdateVolumes(); } - private void UpdateVolumes() - { + private void UpdateVolumes() + { var volume = _volume * _volumeScale; foreach (var evt in _events) evt.SetTrackVolume(volume); - } + } - public void Apply3D(AudioListener listener, AudioEmitter emitter) + public void SetPan(float pan) { foreach (var evt in _events) - evt.Apply3D(listener, emitter); + evt.SetTrackPan(pan); } } } diff --git a/MonoGame.Framework/Audio/Xact/XactHelpers.cs b/MonoGame.Framework/Audio/Xact/XactHelpers.cs index d9361ce0b88..0ce35af0b21 100644 --- a/MonoGame.Framework/Audio/Xact/XactHelpers.cs +++ b/MonoGame.Framework/Audio/Xact/XactHelpers.cs @@ -21,10 +21,10 @@ public static float ParseDecibels(byte decibles) //0x5a -12.0 //0x14 -38.0 //0x00 -96.0 - var a = -96.0; - var b = 0.432254984608615; - var c = 80.1748600297963; - var d = 67.7385212334047; + const double a = -96.0; + const double b = 0.432254984608615; + const double c = 80.1748600297963; + const double d = 67.7385212334047; var dB = (float)(((a - d) / (1 + (Math.Pow(decibles / c, b)))) + d); return dB; @@ -41,10 +41,10 @@ public static float ParseVolumeFromDecibels(byte decibles) //0x5a -12.0 //0x14 -38.0 //0x00 -96.0 - var a = -96.0; - var b = 0.432254984608615; - var c = 80.1748600297963; - var d = 67.7385212334047; + const double a = -96.0; + const double b = 0.432254984608615; + const double c = 80.1748600297963; + const double d = 67.7385212334047; var dB = (float)(((a - d) / (1 + (Math.Pow(decibles / c, b)))) + d); return ParseVolumeFromDecibels(dB); diff --git a/MonoGame.Framework/Audio/Xact/XactSound.cs b/MonoGame.Framework/Audio/Xact/XactSound.cs index 0524d9222f5..90c54c8731a 100644 --- a/MonoGame.Framework/Audio/Xact/XactSound.cs +++ b/MonoGame.Framework/Audio/Xact/XactSound.cs @@ -7,9 +7,9 @@ namespace Microsoft.Xna.Framework.Audio { - class XactSound - { - private readonly bool _complexSound; + class XactSound + { + private readonly bool _complexSound; private readonly XactClip[] _soundClips; private readonly int _waveBankIndex; private readonly int _trackIndex; @@ -17,11 +17,18 @@ class XactSound private readonly float _pitch; private readonly uint _categoryID; private readonly SoundBank _soundBank; + private readonly bool _useReverb; private SoundEffectInstance _wave; - private float _cueVolume; + private float _cueVolume = 1; + private float _cuePitch = 0; + private float _cueReverbMix = 0; + private float? _cueFilterFrequency; + private float? _cueFilterQFactor; + internal readonly int[] RpcCurves; + public XactSound(SoundBank soundBank, int waveBankIndex, int trackIndex) { _complexSound = false; @@ -29,56 +36,73 @@ public XactSound(SoundBank soundBank, int waveBankIndex, int trackIndex) _soundBank = soundBank; _waveBankIndex = waveBankIndex; _trackIndex = trackIndex; + RpcCurves = new int[0]; } - public XactSound(SoundBank soundBank, BinaryReader soundReader) - { + public XactSound(AudioEngine engine, SoundBank soundBank, BinaryReader soundReader) + { _soundBank = soundBank; - + var flags = soundReader.ReadByte(); _complexSound = (flags & 0x1) != 0; var hasRPCs = (flags & 0x0E) != 0; - var hasEffects = (flags & 0x10) != 0; + var hasDSPs = (flags & 0x10) != 0; _categoryID = soundReader.ReadUInt16(); _volume = XactHelpers.ParseVolumeFromDecibels(soundReader.ReadByte()); _pitch = soundReader.ReadInt16() / 1000.0f; - soundReader.ReadByte(); //priority + soundReader.ReadByte(); //priority soundReader.ReadUInt16(); // filter stuff? - - uint numClips = 0; - if (_complexSound) - numClips = soundReader.ReadByte(); - else + + var numClips = 0; + if (_complexSound) + numClips = soundReader.ReadByte(); + else { - _trackIndex = soundReader.ReadUInt16(); - _waveBankIndex = soundReader.ReadByte(); - } - - if (hasRPCs) - { - var current = soundReader.BaseStream.Position; - var dataLength = soundReader.ReadUInt16(); - soundReader.BaseStream.Seek(current + dataLength, SeekOrigin.Begin); - } - - if (hasEffects) - { - var current = soundReader.BaseStream.Position; - var dataLength = soundReader.ReadUInt16(); - soundReader.BaseStream.Seek(current + dataLength, SeekOrigin.Begin); - } - - if (_complexSound) + _trackIndex = soundReader.ReadUInt16(); + _waveBankIndex = soundReader.ReadByte(); + } + + if (!hasRPCs) + RpcCurves = new int[0]; + else { - _soundClips = new XactClip[numClips]; - for (int i=0; i= category.maxInstances) { @@ -113,11 +140,11 @@ public void Play() } } - if (_complexSound) + if (_complexSound) { - foreach (XactClip clip in _soundClips) - clip.Play(); - } + foreach (XactClip clip in _soundClips) + clip.Play(); + } else { if (_wave != null && _wave.State != SoundState.Stopped && _wave.IsLooped) @@ -132,11 +159,12 @@ public void Play() return; } - _wave.Pitch = _pitch; - _wave.Volume = _volume * category._volume[0]; + _wave.Pitch = _pitch + _cuePitch; + _wave.Volume = _volume * _cueVolume * category._volume[0]; + _wave.PlatformSetReverbMix(_useReverb ? _cueReverbMix : 0.0f); _wave.Play(); - } - } + } + } internal void Update(float dt) { @@ -168,8 +196,8 @@ internal void StopAll(AudioStopOptions options) } } } - - public void Stop(AudioStopOptions options) + + public void Stop(AudioStopOptions options) { if (_complexSound) { @@ -184,41 +212,41 @@ public void Stop(AudioStopOptions options) _wave = null; } } - } - - public void Pause() + } + + public void Pause() { - if (_complexSound) + if (_complexSound) { foreach (var sound in _soundClips) { if (sound.State == SoundState.Playing) sound.Pause(); } - } + } else { if (_wave != null && _wave.State == SoundState.Playing) _wave.Pause(); - } - } + } + } - public void Resume() + public void Resume() { - if (_complexSound) + if (_complexSound) { foreach (var sound in _soundClips) { if (sound.State == SoundState.Paused) sound.Resume(); } - } + } else { if (_wave != null && _wave.State == SoundState.Paused) _wave.Resume(); - } - } + } + } internal void UpdateCategoryVolume(float categoryVolume) { @@ -237,29 +265,60 @@ internal void UpdateCategoryVolume(float categoryVolume) } } - internal void SetCueVolume(float volume) - { + internal void UpdateState(AudioEngine engine, float volume, float pitch, float reverbMix, float? filterFrequency, float? filterQFactor) + { _cueVolume = volume; - var category = _soundBank.AudioEngine.Categories[_categoryID]; - UpdateCategoryVolume(category._volume[0]); + var finalVolume = _volume * _cueVolume * engine.Categories[_categoryID]._volume[0]; + + _cueReverbMix = reverbMix; + _cueFilterFrequency = filterFrequency; + _cueFilterQFactor = filterQFactor; + + _cuePitch = pitch; + var finalPitch = _pitch + _cuePitch; + + if (_complexSound) + { + foreach (var clip in _soundClips) + clip.UpdateState(finalVolume, finalPitch, _useReverb ? _cueReverbMix : 0.0f, _cueFilterFrequency, _cueFilterQFactor); + } + else if (_wave != null) + { + _wave.PlatformSetReverbMix(_useReverb ? _cueReverbMix : 0.0f); + _wave.Pitch = finalPitch; + } + } + + internal void SetCuePan(float pan) + { + if (_complexSound) + { + foreach (var clip in _soundClips) + clip.SetPan(pan); + } + else + { + if (_wave != null) + _wave.Pan = pan; + } } - public bool Playing + public bool Playing { - get + get { - if (_complexSound) + if (_complexSound) { foreach (var clip in _soundClips) if (clip.State == SoundState.Playing) return true; return false; - } + } return _wave != null && _wave.State == SoundState.Playing; - } - } + } + } public bool Stopped { @@ -278,34 +337,20 @@ public bool Stopped } } - public bool IsPaused - { - get - { - if (_complexSound) + public bool IsPaused + { + get + { + if (_complexSound) { - foreach (var clip in _soundClips) - if (clip.State == SoundState.Paused) + foreach (var clip in _soundClips) + if (clip.State == SoundState.Paused) return true; - return false; + return false; } return _wave != null && _wave.State == SoundState.Paused; - } - } - - public void Apply3D(AudioListener listener, AudioEmitter emitter) - { - if (_complexSound) - { - foreach (var clip in _soundClips) - clip.Apply3D(listener, emitter); - } - else - { - if (_wave != null) - _wave.Apply3D(listener, emitter); } } } diff --git a/MonoGame.Framework/BoundingBox.cs b/MonoGame.Framework/BoundingBox.cs index 077a01da9fc..c51d7e40b36 100644 --- a/MonoGame.Framework/BoundingBox.cs +++ b/MonoGame.Framework/BoundingBox.cs @@ -214,16 +214,11 @@ public void Contains(ref Vector3 point, out ContainmentType result) || point.Z > this.Max.Z) { result = ContainmentType.Disjoint; - }//or if point is on box because coordonate of point is lesser or equal - else if (point.X == this.Min.X - || point.X == this.Max.X - || point.Y == this.Min.Y - || point.Y == this.Max.Y - || point.Z == this.Min.Z - || point.Z == this.Max.Z) - result = ContainmentType.Intersects; + } else + { result = ContainmentType.Contains; + } } private static readonly Vector3 MaxVector3 = new Vector3(float.MaxValue); diff --git a/MonoGame.Framework/Color.cs b/MonoGame.Framework/Color.cs index 534d52c17b5..bbfc61b9ec4 100644 --- a/MonoGame.Framework/Color.cs +++ b/MonoGame.Framework/Color.cs @@ -162,16 +162,21 @@ static Color() Yellow = new Color(0xff00ffff); YellowGreen = new Color(0xff32cd9a); } - // ARGB + + // Stored as RGBA with R in the least significant octet: + // |-------|-------|-------|------- + // A B G R private uint _packedValue; - private Color(uint packedValue) + /// + /// Constructs an RGBA color from a packed value. + /// The value is a 32-bit unsigned integer, with R in the least significant octet. + /// + /// The packed value. + [CLSCompliant(false)] + public Color(uint packedValue) { _packedValue = packedValue; - // ARGB - //_packedValue = (packedValue << 8) | ((packedValue & 0xff000000) >> 24); - // ABGR - //_packedValue = (packedValue & 0xff00ff00) | ((packedValue & 0x000000ff) << 16) | ((packedValue & 0x00ff0000) >> 16); } /// @@ -179,13 +184,8 @@ private Color(uint packedValue) /// /// A representing color. public Color(Vector4 color) + : this((int)(color.X * 255), (int)(color.Y * 255), (int)(color.Z * 255), (int)(color.W * 255)) { - _packedValue = 0; - - R = (byte)MathHelper.Clamp(color.X * 255, Byte.MinValue, Byte.MaxValue); - G = (byte)MathHelper.Clamp(color.Y * 255, Byte.MinValue, Byte.MaxValue); - B = (byte)MathHelper.Clamp(color.Z * 255, Byte.MinValue, Byte.MaxValue); - A = (byte)MathHelper.Clamp(color.W * 255, Byte.MinValue, Byte.MaxValue); } /// @@ -193,13 +193,8 @@ public Color(Vector4 color) /// /// A representing color. public Color(Vector3 color) + : this((int)(color.X * 255), (int)(color.Y * 255), (int)(color.Z * 255)) { - _packedValue = 0; - - R = (byte)MathHelper.Clamp(color.X * 255, Byte.MinValue, Byte.MaxValue); - G = (byte)MathHelper.Clamp(color.Y * 255, Byte.MinValue, Byte.MaxValue); - B = (byte)MathHelper.Clamp(color.Z * 255, Byte.MinValue, Byte.MaxValue); - A = 255; } /// @@ -209,12 +204,16 @@ public Color(Vector3 color) /// The alpha component value from 0 to 255. public Color(Color color, int alpha) { - _packedValue = 0; + if ((alpha & 0xFFFFFF00) != 0) + { + var clampedA = (uint)MathHelper.Clamp(alpha, Byte.MinValue, Byte.MaxValue); - R = color.R; - G = color.G; - B = color.B; - A = (byte)MathHelper.Clamp(alpha, Byte.MinValue, Byte.MaxValue); + _packedValue = (color._packedValue & 0x00FFFFFF) | (clampedA << 24); + } + else + { + _packedValue = (color._packedValue & 0x00FFFFFF) | ((uint)alpha << 24); + } } /// @@ -222,49 +221,60 @@ public Color(Color color, int alpha) /// /// A for RGB values of new instance. /// Alpha component value from 0.0f to 1.0f. - public Color(Color color, float alpha) + public Color(Color color, float alpha): + this(color, (int)(alpha * 255)) { - _packedValue = 0; - - R = color.R; - G = color.G; - B = color.B; - A = (byte)MathHelper.Clamp(alpha * 255, Byte.MinValue, Byte.MaxValue); } /// - /// Constructs an RGBA color from scalars which representing red, green and blue values. Alpha value will be opaque. + /// Constructs an RGBA color from scalars representing red, green and blue values. Alpha value will be opaque. /// /// Red component value from 0.0f to 1.0f. /// Green component value from 0.0f to 1.0f. /// Blue component value from 0.0f to 1.0f. public Color(float r, float g, float b) + : this((int)(r * 255), (int)(g * 255), (int)(b * 255)) { - _packedValue = 0; - - R = (byte)MathHelper.Clamp(r * 255, Byte.MinValue, Byte.MaxValue); - G = (byte)MathHelper.Clamp(g * 255, Byte.MinValue, Byte.MaxValue); - B = (byte)MathHelper.Clamp(b * 255, Byte.MinValue, Byte.MaxValue); - A = 255; } /// - /// Constructs an RGBA color from scalars which representing red, green and blue values. Alpha value will be opaque. + /// Constructs an RGBA color from scalars representing red, green, blue and alpha values. + /// + /// Red component value from 0.0f to 1.0f. + /// Green component value from 0.0f to 1.0f. + /// Blue component value from 0.0f to 1.0f. + /// Alpha component value from 0.0f to 1.0f. + public Color(float r, float g, float b, float alpha) + : this((int)(r * 255), (int)(g * 255), (int)(b * 255), (int)(alpha * 255)) + { + } + + /// + /// Constructs an RGBA color from scalars representing red, green and blue values. Alpha value will be opaque. /// /// Red component value from 0 to 255. /// Green component value from 0 to 255. /// Blue component value from 0 to 255. public Color(int r, int g, int b) { - _packedValue = 0; - R = (byte)MathHelper.Clamp(r, Byte.MinValue, Byte.MaxValue); - G = (byte)MathHelper.Clamp(g, Byte.MinValue, Byte.MaxValue); - B = (byte)MathHelper.Clamp(b, Byte.MinValue, Byte.MaxValue); - A = (byte)255; + _packedValue = 0xFF000000; // A = 255 + + if (((r | g | b) & 0xFFFFFF00) != 0) + { + var clampedR = (uint)MathHelper.Clamp(r, Byte.MinValue, Byte.MaxValue); + var clampedG = (uint)MathHelper.Clamp(g, Byte.MinValue, Byte.MaxValue); + var clampedB = (uint)MathHelper.Clamp(b, Byte.MinValue, Byte.MaxValue); + + _packedValue |= (clampedB << 16) | (clampedG << 8) | (clampedR); + } + else + { + _packedValue |= ((uint)b << 16) | ((uint)g << 8) | ((uint)r); + } } /// - /// Constructs an RGBA color from scalars which representing red, green, blue and alpha values. + /// Constructs an RGBA color from scalars representing red, green, blue and alpha values. /// /// Red component value from 0 to 255. /// Green component value from 0 to 255. @@ -272,28 +282,34 @@ public Color(int r, int g, int b) /// Alpha component value from 0 to 255. public Color(int r, int g, int b, int alpha) { - _packedValue = 0; - R = (byte)MathHelper.Clamp(r, Byte.MinValue, Byte.MaxValue); - G = (byte)MathHelper.Clamp(g, Byte.MinValue, Byte.MaxValue); - B = (byte)MathHelper.Clamp(b, Byte.MinValue, Byte.MaxValue); - A = (byte)MathHelper.Clamp(alpha, Byte.MinValue, Byte.MaxValue); + if (((r | g | b | alpha) & 0xFFFFFF00) != 0) + { + var clampedR = (uint)MathHelper.Clamp(r, Byte.MinValue, Byte.MaxValue); + var clampedG = (uint)MathHelper.Clamp(g, Byte.MinValue, Byte.MaxValue); + var clampedB = (uint)MathHelper.Clamp(b, Byte.MinValue, Byte.MaxValue); + var clampedA = (uint)MathHelper.Clamp(alpha, Byte.MinValue, Byte.MaxValue); + + _packedValue = (clampedA << 24) | (clampedB << 16) | (clampedG << 8) | (clampedR); + } + else + { + _packedValue = ((uint)alpha << 24) | ((uint)b << 16) | ((uint)g << 8) | ((uint)r); + } } /// - /// Constructs an RGBA color from scalars which representing red, green, blue and alpha values. + /// Constructs an RGBA color from scalars representing red, green, blue and alpha values. /// - /// Red component value from 0.0f to 1.0f. - /// Green component value from 0.0f to 1.0f. - /// Blue component value from 0.0f to 1.0f. - /// Alpha component value from 0.0f to 1.0f. - public Color(float r, float g, float b, float alpha) + /// + /// This overload sets the values directly without clamping, and may therefore be faster than the other overloads. + /// + /// + /// + /// + /// + public Color(byte r, byte g, byte b, byte alpha) { - _packedValue = 0; - - R = (byte)MathHelper.Clamp(r * 255, Byte.MinValue, Byte.MaxValue); - G = (byte)MathHelper.Clamp(g * 255, Byte.MinValue, Byte.MaxValue); - B = (byte)MathHelper.Clamp(b * 255, Byte.MinValue, Byte.MaxValue); - A = (byte)MathHelper.Clamp(alpha * 255, Byte.MinValue, Byte.MaxValue); + _packedValue = ((uint)alpha << 24) | ((uint)b << 16) | ((uint)g << 8) | (r); } /// @@ -380,10 +396,7 @@ public byte A /// true if the instances are equal; false otherwise. public static bool operator ==(Color a, Color b) { - return (a.A == b.A && - a.R == b.R && - a.G == b.G && - a.B == b.B); + return (a._packedValue == b._packedValue); } /// @@ -394,7 +407,7 @@ public byte A /// true if the instances are not equal; false otherwise. public static bool operator !=(Color a, Color b) { - return !(a == b); + return (a._packedValue != b._packedValue); } /// @@ -1721,14 +1734,10 @@ public static Color Lerp(Color value1, Color value2, Single amount) } /// - /// Performs linear interpolation of using on MathHelper. - /// Less efficient but more precise compared to . - /// See remarks section of on MathHelper for more info. + /// should be used instead of this function. /// - /// Source . - /// Destination . - /// Interpolation factor. /// Interpolated . + [Obsolete("Color.Lerp should be used instead of this function.")] public static Color LerpPrecise(Color value1, Color value2, Single amount) { amount = MathHelper.Clamp(amount, 0, 1); @@ -1779,7 +1788,7 @@ public Vector4 ToVector4() return new Vector4(R / 255.0f, G / 255.0f, B / 255.0f, A / 255.0f); } - /// + /// /// Gets or sets packed value of this . /// [CLSCompliant(false)] @@ -1844,7 +1853,7 @@ public static Color FromNonPremultiplied(Vector4 vector) /// A which contains premultiplied alpha data. public static Color FromNonPremultiplied(int r, int g, int b, int a) { - return new Color((byte)(r * a / 255),(byte)(g * a / 255), (byte)(b * a / 255), a); + return new Color(r * a / 255, g * a / 255, b * a / 255, a); } #region IEquatable Members diff --git a/MonoGame.Framework/Content/ContentManager.cs b/MonoGame.Framework/Content/ContentManager.cs index 1bbcbab0785..38bf5eea6a8 100644 --- a/MonoGame.Framework/Content/ContentManager.cs +++ b/MonoGame.Framework/Content/ContentManager.cs @@ -37,7 +37,6 @@ public partial class ContentManager : IDisposable { 'w', // Windows (DirectX) 'x', // Xbox360 - 'm', // WindowsPhone 'i', // iOS 'a', // Android 'd', // DesktopGL @@ -47,6 +46,8 @@ public partial class ContentManager : IDisposable 'M', // WindowsPhone8 'r', // RaspberryPi 'P', // PlayStation4 + 'v', // PSVita + 'O', // XboxOne // NOTE: There are additional idenfiers for consoles that // are not defined in this repository. Be sure to ask the @@ -59,7 +60,6 @@ public partial class ContentManager : IDisposable 'p', // PlayStationMobile 'g', // Windows (OpenGL) 'l', // Linux - 'u', // Ouya }; @@ -295,59 +295,17 @@ protected T ReadAsset(string assetName, Action recordDisposableO } } - Stream stream = null; - try + // Try to load as XNB file + var stream = OpenStream(assetName); + using (var xnbReader = new BinaryReader(stream)) { - //try load it traditionally - stream = OpenStream(assetName); - - // Try to load as XNB file - try + using (var reader = GetContentReaderFromXnb(assetName, stream, xnbReader, recordDisposableObject)) { - using (BinaryReader xnbReader = new BinaryReader(stream)) - { - using (ContentReader reader = GetContentReaderFromXnb(assetName, ref stream, xnbReader, recordDisposableObject)) - { - result = reader.ReadAsset(); - if (result is GraphicsResource) - ((GraphicsResource)result).Name = originalAssetName; - } - } - } - finally - { - if (stream != null) - { - stream.Dispose(); - } + result = reader.ReadAsset(); + if (result is GraphicsResource) + ((GraphicsResource)result).Name = originalAssetName; } } - catch (ContentLoadException ex) - { - //MonoGame try to load as a non-content file - - assetName = TitleContainer.GetFilename(Path.Combine(RootDirectory, assetName)); - - assetName = Normalize(assetName); - - if (string.IsNullOrEmpty(assetName)) - { - throw new ContentLoadException("Could not load " + originalAssetName + " asset as a non-content file!", ex); - } - - result = ReadRawAsset(assetName, originalAssetName); - - // Because Raw Assets skip the ContentReader step, they need to have their - // disopsables recorded here. Doing it outside of this catch will - // result in disposables being logged twice. - if (result is IDisposable) - { - if (recordDisposableObject != null) - recordDisposableObject(result as IDisposable); - else - disposableAssets.Add(result as IDisposable); - } - } if (result == null) throw new ContentLoadException("Could not load " + originalAssetName + " asset!"); @@ -355,74 +313,7 @@ protected T ReadAsset(string assetName, Action recordDisposableO return (T)result; } - protected virtual string Normalize(string assetName) - { - if (typeof(T) == typeof(Texture2D) || typeof(T) == typeof(Texture)) - { - return Texture2DReader.Normalize(assetName); - } - else if ((typeof(T) == typeof(SpriteFont))) - { - return SpriteFontReader.Normalize(assetName); - } -#if !WINRT - else if ((typeof(T) == typeof(Song))) - { - return SongReader.Normalize(assetName); - } - else if ((typeof(T) == typeof(SoundEffect))) - { - return SoundEffectReader.Normalize(assetName); - } -#endif - else if ((typeof(T) == typeof(Effect))) - { - return EffectReader.Normalize(assetName); - } - return null; - } - - protected virtual object ReadRawAsset(string assetName, string originalAssetName) - { - if (typeof(T) == typeof(Texture2D) || typeof(T) == typeof(Texture)) - { - using (Stream assetStream = TitleContainer.OpenStream(assetName)) - { - Texture2D texture = Texture2D.FromStream( - graphicsDeviceService.GraphicsDevice, assetStream); - texture.Name = originalAssetName; - return texture; - } - } - else if ((typeof(T) == typeof(SpriteFont))) - { - //result = new SpriteFont(Texture2D.FromFile(graphicsDeviceService.GraphicsDevice,assetName), null, null, null, 0, 0.0f, null, null); - throw new NotImplementedException(); - } -#if !DIRECTX - else if ((typeof(T) == typeof(Song))) - { - return new Song(assetName); - } - else if ((typeof(T) == typeof(SoundEffect))) - { - using (Stream s = TitleContainer.OpenStream(assetName)) - return SoundEffect.FromStream(s); - } -#endif - else if ((typeof(T) == typeof(Effect))) - { - using (Stream assetStream = TitleContainer.OpenStream(assetName)) - { - var data = new byte[assetStream.Length]; - assetStream.Read(data, 0, (int)assetStream.Length); - return new Effect(this.graphicsDeviceService.GraphicsDevice, data); - } - } - return null; - } - - private ContentReader GetContentReaderFromXnb(string originalAssetName, ref Stream stream, BinaryReader xnbReader, Action recordDisposableObject) + private ContentReader GetContentReaderFromXnb(string originalAssetName, Stream stream, BinaryReader xnbReader, Action recordDisposableObject) { // The first 4 bytes should be the "XNB" header. i use that to detect an invalid file byte x = xnbReader.ReadByte(); @@ -457,60 +348,8 @@ private ContentReader GetContentReaderFromXnb(string originalAssetName, ref Stre if (compressedLzx) { - //thanks to ShinAli (https://bitbucket.org/alisci01/xnbdecompressor) - // default window size for XNB encoded files is 64Kb (need 16 bits to represent it) - LzxDecoder dec = new LzxDecoder(16); - decompressedStream = new MemoryStream(decompressedSize); int compressedSize = xnbLength - 14; - long startPos = stream.Position; - long pos = startPos; - - while (pos - startPos < compressedSize) - { - // the compressed stream is seperated into blocks that will decompress - // into 32Kb or some other size if specified. - // normal, 32Kb output blocks will have a short indicating the size - // of the block before the block starts - // blocks that have a defined output will be preceded by a byte of value - // 0xFF (255), then a short indicating the output size and another - // for the block size - // all shorts for these cases are encoded in big endian order - int hi = stream.ReadByte(); - int lo = stream.ReadByte(); - int block_size = (hi << 8) | lo; - int frame_size = 0x8000; // frame size is 32Kb by default - // does this block define a frame size? - if (hi == 0xFF) - { - hi = lo; - lo = (byte)stream.ReadByte(); - frame_size = (hi << 8) | lo; - hi = (byte)stream.ReadByte(); - lo = (byte)stream.ReadByte(); - block_size = (hi << 8) | lo; - pos += 5; - } - else - pos += 2; - - // either says there is nothing to decode - if (block_size == 0 || frame_size == 0) - break; - - dec.Decompress(stream, block_size, decompressedStream, frame_size); - pos += block_size; - - // reset the position of the input just incase the bit buffer - // read in some unused bytes - stream.Seek(pos, SeekOrigin.Begin); - } - - if (decompressedStream.Position != decompressedSize) - { - throw new ContentLoadException("Decompression of " + originalAssetName + " failed. "); - } - - decompressedStream.Seek(0, SeekOrigin.Begin); + decompressedStream = new LzxDecoderStream(stream, decompressedSize, compressedSize); } else if (compressedLz4) { @@ -585,58 +424,16 @@ protected virtual void ReloadAsset(string originalAssetName, T currentAsset) throw new InvalidOperationException("No Graphics Device Service"); } } - - Stream stream = null; - try - { - //try load it traditionally - stream = OpenStream(assetName); - - // Try to load as XNB file - try - { - using (BinaryReader xnbReader = new BinaryReader(stream)) - { - using (ContentReader reader = GetContentReaderFromXnb(assetName, ref stream, xnbReader, null)) - { - reader.InitializeTypeReaders(); - reader.ReadObject(currentAsset); - reader.ReadSharedResources(); - } - } - } - finally - { - if (stream != null) - { - stream.Dispose(); - } - } - } - catch (ContentLoadException) - { - // Try to reload as a non-xnb file. - // Just textures supported for now. - - assetName = TitleContainer.GetFilename(Path.Combine(RootDirectory, assetName)); - - assetName = Normalize(assetName); - - ReloadRawAsset(currentAsset, assetName, originalAssetName); - } - } - protected virtual void ReloadRawAsset(T asset, string assetName, string originalAssetName) - { - if (asset is Texture2D) + var stream = OpenStream(assetName); + using (var xnbReader = new BinaryReader(stream)) { - using (Stream assetStream = TitleContainer.OpenStream(assetName)) + using (var reader = GetContentReaderFromXnb(assetName, stream, xnbReader, null)) { - var textureAsset = asset as Texture2D; - textureAsset.Reload(assetStream); + reader.ReadAsset(currentAsset); } } - } + } public virtual void Unload() { diff --git a/MonoGame.Framework/Content/ContentReader.cs b/MonoGame.Framework/Content/ContentReader.cs index e752c7597cb..f869b6ce98c 100644 --- a/MonoGame.Framework/Content/ContentReader.cs +++ b/MonoGame.Framework/Content/ContentReader.cs @@ -77,6 +77,19 @@ internal object ReadAsset() return result; } + internal object ReadAsset(T existingInstance) + { + InitializeTypeReaders(); + + // Read primary object + object result = ReadObject(existingInstance); + + // Read shared resources + ReadSharedResources(); + + return result; + } + internal void InitializeTypeReaders() { typeReaderManager = new ContentTypeReaderManager(); @@ -147,7 +160,7 @@ private void RecordDisposable(T result) public T ReadObject() { - return ReadObject(default(T)); + return InnerReadObject(default(T)); } public T ReadObject(ContentTypeReader typeReader) diff --git a/MonoGame.Framework/Content/ContentReaders/EffectMaterialReader.cs b/MonoGame.Framework/Content/ContentReaders/EffectMaterialReader.cs index 315dc07ed27..538e559237e 100644 --- a/MonoGame.Framework/Content/ContentReaders/EffectMaterialReader.cs +++ b/MonoGame.Framework/Content/ContentReaders/EffectMaterialReader.cs @@ -1,42 +1,6 @@ -#region License -/* -Microsoft Public License (Ms-PL) -MonoGame - Copyright © 2009 The MonoGame Team - -All rights reserved. - -This license governs use of the accompanying software. If you use the software, you accept this license. If you do not -accept the license, do not use the software. - -1. Definitions -The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under -U.S. copyright law. - -A "contribution" is the original software, or any additions or changes to the software. -A "contributor" is any person that distributes its contribution under this license. -"Licensed patents" are a contributor's patent claims that read directly on its contribution. - -2. Grant of Rights -(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, -each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. -(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, -each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - -3. Conditions and Limitations -(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. -(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, -your patent license from such contributor to the software ends automatically. -(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution -notices that are present in the software. -(D) If you distribute any portion of the software in source code form, you may do so only under this license by including -a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object -code form, you may only do so under a license that complies with this license. -(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees -or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent -permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular -purpose and non-infringement. -*/ -#endregion License +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. using System; using System.Collections.Generic; diff --git a/MonoGame.Framework/Content/ContentReaders/EffectReader.cs b/MonoGame.Framework/Content/ContentReaders/EffectReader.cs index 313f7c4a3d9..ef295c65c4f 100644 --- a/MonoGame.Framework/Content/ContentReaders/EffectReader.cs +++ b/MonoGame.Framework/Content/ContentReaders/EffectReader.cs @@ -3,7 +3,6 @@ // file 'LICENSE.txt', which is part of this source code package. using Microsoft.Xna.Framework.Graphics; -using System.Linq; namespace Microsoft.Xna.Framework.Content { @@ -12,22 +11,6 @@ internal class EffectReader : ContentTypeReader public EffectReader() { } - static string [] supportedExtensions = new string[] {".fxg"}; - - public static string Normalize(string FileName) - { - return ContentTypeReader.Normalize(FileName, supportedExtensions); - } - - private static string TryFindAnyCased(string search, string[] arr, params string[] extensions) - { - return arr.FirstOrDefault(s => extensions.Any(ext => s.ToLowerInvariant() == (search.ToLowerInvariant() + ext))); - } - - private static bool Contains(string search, string[] arr) - { - return arr.Any(s => s == search); - } protected internal override Effect Read(ContentReader input, Effect existingInstance) { @@ -35,10 +18,8 @@ protected internal override Effect Read(ContentReader input, Effect existingInst byte[] data = input.ContentManager.GetScratchBuffer(dataSize); input.Read(data, 0, dataSize); var effect = new Effect(input.GraphicsDevice, data, 0, dataSize); - effect.Name = input.AssetName; - return effect; } } -} \ No newline at end of file +} diff --git a/MonoGame.Framework/Content/ContentReaders/SongReader.cs b/MonoGame.Framework/Content/ContentReaders/SongReader.cs index 79ca26d2b86..59df217bf48 100644 --- a/MonoGame.Framework/Content/ContentReaders/SongReader.cs +++ b/MonoGame.Framework/Content/ContentReaders/SongReader.cs @@ -1,43 +1,6 @@ -// #region License -// /* -// Microsoft Public License (Ms-PL) -// MonoGame - Copyright © 2009 The MonoGame Team -// -// All rights reserved. -// -// This license governs use of the accompanying software. If you use the software, you accept this license. If you do not -// accept the license, do not use the software. -// -// 1. Definitions -// The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under -// U.S. copyright law. -// -// A "contribution" is the original software, or any additions or changes to the software. -// A "contributor" is any person that distributes its contribution under this license. -// "Licensed patents" are a contributor's patent claims that read directly on its contribution. -// -// 2. Grant of Rights -// (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, -// each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. -// (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, -// each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. -// -// 3. Conditions and Limitations -// (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. -// (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, -// your patent license from such contributor to the software ends automatically. -// (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution -// notices that are present in the software. -// (D) If you distribute any portion of the software in source code form, you may do so only under this license by including -// a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object -// code form, you may only do so under a license that complies with this license. -// (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees -// or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent -// permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular -// purpose and non-infringement. -// */ -// #endregion License -// +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. using System; using System.IO; @@ -49,17 +12,6 @@ namespace Microsoft.Xna.Framework.Content { internal class SongReader : ContentTypeReader { -#if ANDROID - static string[] supportedExtensions = new string[] { ".mp3", ".ogg", ".mid" }; -#else - static string[] supportedExtensions = new string[] { ".mp3" }; -#endif - - internal static string Normalize(string fileName) - { - return Normalize(fileName, supportedExtensions); - } - protected internal override Song Read(ContentReader input, Song existingInstance) { var path = input.ReadString(); diff --git a/MonoGame.Framework/Content/ContentReaders/SoundEffectReader.cs b/MonoGame.Framework/Content/ContentReaders/SoundEffectReader.cs index 459d9de1c38..19450e2cb3f 100644 --- a/MonoGame.Framework/Content/ContentReaders/SoundEffectReader.cs +++ b/MonoGame.Framework/Content/ContentReaders/SoundEffectReader.cs @@ -1,71 +1,18 @@ -// #region License -// /* -// Microsoft Public License (Ms-PL) -// MonoGame - Copyright © 2009 The MonoGame Team -// -// All rights reserved. -// -// This license governs use of the accompanying software. If you use the software, you accept this license. If you do not -// accept the license, do not use the software. -// -// 1. Definitions -// The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under -// U.S. copyright law. -// -// A "contribution" is the original software, or any additions or changes to the software. -// A "contributor" is any person that distributes its contribution under this license. -// "Licensed patents" are a contributor's patent claims that read directly on its contribution. -// -// 2. Grant of Rights -// (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, -// each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. -// (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, -// each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. -// -// 3. Conditions and Limitations -// (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. -// (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, -// your patent license from such contributor to the software ends automatically. -// (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution -// notices that are present in the software. -// (D) If you distribute any portion of the software in source code form, you may do so only under this license by including -// a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object -// code form, you may only do so under a license that complies with this license. -// (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees -// or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent -// permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular -// purpose and non-infringement. -// */ -// #endregion License -// +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. using System; -using System.IO; - using Microsoft.Xna.Framework.Audio; -#if WINRT -using SharpDX.XAudio2; -#endif namespace Microsoft.Xna.Framework.Content { internal class SoundEffectReader : ContentTypeReader { -#if ANDROID - static string[] supportedExtensions = new string[] { ".wav", ".mp3", ".ogg", ".mid" }; -#else - static string[] supportedExtensions = new string[] { ".wav", ".aiff", ".ac3", ".mp3" }; -#endif - - internal static string Normalize(string fileName) - { - return Normalize(fileName, supportedExtensions); - } - protected internal override SoundEffect Read(ContentReader input, SoundEffect existingInstance) { - // NXB format for SoundEffect... + // XNB format for SoundEffect... // // Byte [format size] Format WAVEFORMATEX structure // UInt32 Data size @@ -74,9 +21,9 @@ protected internal override SoundEffect Read(ContentReader input, SoundEffect ex // Int32 Loop length In bytes (length must be format block aligned) // Int32 Duration In milliseconds - // WAVEFORMATEX structure... + // The header containss the WAVEFORMATEX header structure + // defined as the following... // - //typedef struct { // WORD wFormatTag; // byte[0] +2 // WORD nChannels; // byte[2] +2 // DWORD nSamplesPerSec; // byte[4] +4 @@ -84,74 +31,29 @@ protected internal override SoundEffect Read(ContentReader input, SoundEffect ex // WORD nBlockAlign; // byte[12] +2 // WORD wBitsPerSample; // byte[14] +2 // WORD cbSize; // byte[16] +2 - //} WAVEFORMATEX; - - byte[] header = input.ReadBytes(input.ReadInt32()); - byte[] data = input.ReadBytes(input.ReadInt32()); - int loopStart = input.ReadInt32(); - int loopLength = input.ReadInt32(); - input.ReadInt32(); + // + // We let the sound effect deal with parsing this based + // on what format the audio data actually is. + + var headerSize = input.ReadInt32(); + var header = input.ReadBytes(headerSize); -#if DIRECTX - var count = data.Length; - var format = (int)BitConverter.ToUInt16(header, 0); - var sampleRate = (int)BitConverter.ToUInt16(header, 4); - var channels = BitConverter.ToUInt16(header, 2); - //var avgBPS = (int)BitConverter.ToUInt16(header, 8); - var blockAlignment = (int)BitConverter.ToUInt16(header, 12); - //var bps = (int)BitConverter.ToUInt16(header, 14); + // Read the audio data buffer. + var dataSize = input.ReadInt32(); + var data = input.ContentManager.GetScratchBuffer(dataSize); + input.Read(data, 0, dataSize); - SharpDX.Multimedia.WaveFormat waveFormat; - if (format == 1) - waveFormat = new SharpDX.Multimedia.WaveFormat(sampleRate, channels); - else if (format == 2) - waveFormat = new SharpDX.Multimedia.WaveFormatAdpcm(sampleRate, channels, blockAlignment); - else - throw new NotSupportedException("Unsupported wave format!"); + var loopStart = input.ReadInt32(); + var loopLength = input.ReadInt32(); + var durationMs = input.ReadInt32(); - return new SoundEffect(data, 0, count, sampleRate, (AudioChannels)channels, loopStart, loopLength) - { - _format = waveFormat, - Name = input.AssetName, - }; -#else - if(loopStart == loopLength) - { - // do nothing. just killing the warning for non-DirectX path - } - if (header[0] == 2 && header[1] == 0) - { - // We've found MSADPCM data! Let's decode it here. - using (MemoryStream origDataStream = new MemoryStream(data)) - { - using (BinaryReader reader = new BinaryReader(origDataStream)) - { - byte[] newData = MSADPCMToPCM.MSADPCM_TO_PCM( - reader, - header[2], - (short) ((header[12] / header[2]) - 22) - ); - data = newData; - } - } - - // This is PCM data now! - header[0] = 1; - } - - int sampleRate = ( - (header[4]) + - (header[5] << 8) + - (header[6] << 16) + - (header[7] << 24) - ); + // Create the effect. + var effect = new SoundEffect(header, data, dataSize, durationMs, loopStart, loopLength); - var channels = (header[2] == 2) ? AudioChannels.Stereo : AudioChannels.Mono; - return new SoundEffect(data, sampleRate, channels) - { - Name = input.AssetName - }; -#endif - } + // Store the original asset name for debugging later. + effect.Name = input.AssetName; + + return effect; + } } } diff --git a/MonoGame.Framework/Content/ContentReaders/SpriteFontReader.cs b/MonoGame.Framework/Content/ContentReaders/SpriteFontReader.cs index 32e596e96b3..6323e1362fe 100644 --- a/MonoGame.Framework/Content/ContentReaders/SpriteFontReader.cs +++ b/MonoGame.Framework/Content/ContentReaders/SpriteFontReader.cs @@ -1,30 +1,6 @@ -#region License -/* -MIT License -Copyright © 2006 The Mono.Xna Team - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -#endregion License - +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. using System; using System.Collections.Generic; @@ -40,13 +16,6 @@ internal SpriteFontReader() { } - static string[] supportedExtensions = new string[] { ".spritefont" }; - - internal static string Normalize(string fileName) - { - return Normalize(fileName, supportedExtensions); - } - protected internal override SpriteFont Read(ContentReader input, SpriteFont existingInstance) { if (existingInstance != null) diff --git a/MonoGame.Framework/Content/ContentReaders/Texture2DReader.cs b/MonoGame.Framework/Content/ContentReaders/Texture2DReader.cs index b9c957638f4..6a2c3c76fe9 100644 --- a/MonoGame.Framework/Content/ContentReaders/Texture2DReader.cs +++ b/MonoGame.Framework/Content/ContentReaders/Texture2DReader.cs @@ -17,17 +17,6 @@ internal Texture2DReader() // Do nothing } -#if ANDROID - static string[] supportedExtensions = new string[] { ".jpg", ".bmp", ".jpeg", ".png", ".gif" }; -#else - static string[] supportedExtensions = new string[] { ".jpg", ".bmp", ".jpeg", ".png", ".gif", ".pict", ".tga" }; -#endif - - internal static string Normalize(string fileName) - { - return Normalize(fileName, supportedExtensions); - } - protected internal override Texture2D Read(ContentReader reader, Texture2D existingInstance) { Texture2D texture = null; @@ -76,99 +65,116 @@ protected internal override Texture2D Read(ContentReader reader, Texture2D exist } texture = existingInstance ?? new Texture2D(reader.GraphicsDevice, width, height, levelCountOutput > 1, convertedFormat); - - for (int level = 0; level < levelCount; level++) - { - var levelDataSizeInBytes = reader.ReadInt32(); - var levelData = reader.ContentManager.GetScratchBuffer(levelDataSizeInBytes); - reader.Read(levelData, 0, levelDataSizeInBytes); - int levelWidth = width >> level; - int levelHeight = height >> level; +#if OPENGL + Threading.BlockOnUIThread(() => + { +#endif + for (int level = 0; level < levelCount; level++) + { + var levelDataSizeInBytes = reader.ReadInt32(); + var levelData = reader.ContentManager.GetScratchBuffer(levelDataSizeInBytes); + reader.Read(levelData, 0, levelDataSizeInBytes); + int levelWidth = width >> level; + int levelHeight = height >> level; - if (level >= levelCountOutput) - continue; + if (level >= levelCountOutput) + continue; - //Convert the image data if required - switch (surfaceFormat) - { - case SurfaceFormat.Dxt1: - case SurfaceFormat.Dxt1SRgb: - case SurfaceFormat.Dxt1a: - if (!reader.GraphicsDevice.GraphicsCapabilities.SupportsDxt1 && convertedFormat == SurfaceFormat.Color) - levelData = DxtUtil.DecompressDxt1(levelData, levelWidth, levelHeight); - break; - case SurfaceFormat.Dxt3: - case SurfaceFormat.Dxt3SRgb: - if (!reader.GraphicsDevice.GraphicsCapabilities.SupportsS3tc) - if (!reader.GraphicsDevice.GraphicsCapabilities.SupportsS3tc && convertedFormat == SurfaceFormat.Color) - levelData = DxtUtil.DecompressDxt3(levelData, levelWidth, levelHeight); - break; - case SurfaceFormat.Dxt5: - case SurfaceFormat.Dxt5SRgb: - if (!reader.GraphicsDevice.GraphicsCapabilities.SupportsS3tc) - if (!reader.GraphicsDevice.GraphicsCapabilities.SupportsS3tc && convertedFormat == SurfaceFormat.Color) - levelData = DxtUtil.DecompressDxt5(levelData, levelWidth, levelHeight); - break; - case SurfaceFormat.Bgra5551: - { -#if OPENGL - // Shift the channels to suit OpenGL - int offset = 0; - for (int y = 0; y < levelHeight; y++) + //Convert the image data if required + switch (surfaceFormat) + { + case SurfaceFormat.Dxt1: + case SurfaceFormat.Dxt1SRgb: + case SurfaceFormat.Dxt1a: + if (!reader.GraphicsDevice.GraphicsCapabilities.SupportsDxt1 && convertedFormat == SurfaceFormat.Color) + { + levelData = DxtUtil.DecompressDxt1(levelData, levelWidth, levelHeight); + levelDataSizeInBytes = levelData.Length; + } + break; + case SurfaceFormat.Dxt3: + case SurfaceFormat.Dxt3SRgb: + if (!reader.GraphicsDevice.GraphicsCapabilities.SupportsS3tc) + if (!reader.GraphicsDevice.GraphicsCapabilities.SupportsS3tc && + convertedFormat == SurfaceFormat.Color) + { + levelData = DxtUtil.DecompressDxt3(levelData, levelWidth, levelHeight); + levelDataSizeInBytes = levelData.Length; + } + break; + case SurfaceFormat.Dxt5: + case SurfaceFormat.Dxt5SRgb: + if (!reader.GraphicsDevice.GraphicsCapabilities.SupportsS3tc) + if (!reader.GraphicsDevice.GraphicsCapabilities.SupportsS3tc && + convertedFormat == SurfaceFormat.Color) + { + levelData = DxtUtil.DecompressDxt5(levelData, levelWidth, levelHeight); + levelDataSizeInBytes = levelData.Length; + } + break; + case SurfaceFormat.Bgra5551: { - for (int x = 0; x < levelWidth; x++) +#if OPENGL + // Shift the channels to suit OpenGL + int offset = 0; + for (int y = 0; y < levelHeight; y++) { - ushort pixel = BitConverter.ToUInt16(levelData, offset); - pixel = (ushort)(((pixel & 0x7FFF) << 1) | ((pixel & 0x8000) >> 15)); - levelData[offset] = (byte)(pixel); - levelData[offset + 1] = (byte)(pixel >> 8); - offset += 2; + for (int x = 0; x < levelWidth; x++) + { + ushort pixel = BitConverter.ToUInt16(levelData, offset); + pixel = (ushort)(((pixel & 0x7FFF) << 1) | ((pixel & 0x8000) >> 15)); + levelData[offset] = (byte)(pixel); + levelData[offset + 1] = (byte)(pixel >> 8); + offset += 2; + } } - } #endif - } - break; - case SurfaceFormat.Bgra4444: - { + } + break; + case SurfaceFormat.Bgra4444: + { #if OPENGL - // Shift the channels to suit OpenGL - int offset = 0; - for (int y = 0; y < levelHeight; y++) - { - for (int x = 0; x < levelWidth; x++) - { - ushort pixel = BitConverter.ToUInt16(levelData, offset); - pixel = (ushort)(((pixel & 0x0FFF) << 4) | ((pixel & 0xF000) >> 12)); - levelData[offset] = (byte)(pixel); - levelData[offset + 1] = (byte)(pixel >> 8); - offset += 2; - } - } + // Shift the channels to suit OpenGL + int offset = 0; + for (int y = 0; y < levelHeight; y++) + { + for (int x = 0; x < levelWidth; x++) + { + ushort pixel = BitConverter.ToUInt16(levelData, offset); + pixel = (ushort)(((pixel & 0x0FFF) << 4) | ((pixel & 0xF000) >> 12)); + levelData[offset] = (byte)(pixel); + levelData[offset + 1] = (byte)(pixel >> 8); + offset += 2; + } + } #endif - } - break; - case SurfaceFormat.NormalizedByte4: - { - int bytesPerPixel = surfaceFormat.GetSize(); - int pitch = levelWidth * bytesPerPixel; - for (int y = 0; y < levelHeight; y++) - { - for (int x = 0; x < levelWidth; x++) - { - int color = BitConverter.ToInt32(levelData, y * pitch + x * bytesPerPixel); - levelData[y * pitch + x * 4] = (byte)(((color >> 16) & 0xff)); //R:=W - levelData[y * pitch + x * 4 + 1] = (byte)(((color >> 8) & 0xff)); //G:=V - levelData[y * pitch + x * 4 + 2] = (byte)(((color) & 0xff)); //B:=U - levelData[y * pitch + x * 4 + 3] = (byte)(((color >> 24) & 0xff)); //A:=Q - } - } - } - break; - } + } + break; + case SurfaceFormat.NormalizedByte4: + { + int bytesPerPixel = surfaceFormat.GetSize(); + int pitch = levelWidth * bytesPerPixel; + for (int y = 0; y < levelHeight; y++) + { + for (int x = 0; x < levelWidth; x++) + { + int color = BitConverter.ToInt32(levelData, y * pitch + x * bytesPerPixel); + levelData[y * pitch + x * 4] = (byte)(((color >> 16) & 0xff)); //R:=W + levelData[y * pitch + x * 4 + 1] = (byte)(((color >> 8) & 0xff)); //G:=V + levelData[y * pitch + x * 4 + 2] = (byte)(((color) & 0xff)); //B:=U + levelData[y * pitch + x * 4 + 3] = (byte)(((color >> 24) & 0xff)); //A:=Q + } + } + } + break; + } - texture.SetData(level, null, levelData, 0, levelDataSizeInBytes); - } - + texture.SetData(level, null, levelData, 0, levelDataSizeInBytes); + } +#if OPENGL + }); +#endif + return texture; } } diff --git a/MonoGame.Framework/Content/ContentReaders/Texture3DReader.cs b/MonoGame.Framework/Content/ContentReaders/Texture3DReader.cs index 4aa43e94516..c5f11f5d8ad 100644 --- a/MonoGame.Framework/Content/ContentReaders/Texture3DReader.cs +++ b/MonoGame.Framework/Content/ContentReaders/Texture3DReader.cs @@ -23,19 +23,26 @@ protected internal override Texture3D Read(ContentReader reader, Texture3D exist texture = new Texture3D(reader.GraphicsDevice, width, height, depth, levelCount > 1, format); else texture = existingInstance; - - for (int i = 0; i < levelCount; i++) + +#if OPENGL + Threading.BlockOnUIThread(() => { - int dataSize = reader.ReadInt32(); - byte[] data = reader.ContentManager.GetScratchBuffer(dataSize); - reader.Read(data, 0, dataSize); - texture.SetData(i, 0, 0, width, height, 0, depth, data, 0, dataSize); +#endif + for (int i = 0; i < levelCount; i++) + { + int dataSize = reader.ReadInt32(); + byte[] data = reader.ContentManager.GetScratchBuffer(dataSize); + reader.Read(data, 0, dataSize); + texture.SetData(i, 0, 0, width, height, 0, depth, data, 0, dataSize); - // Calculate dimensions of next mip level. - width = Math.Max(width >> 1, 1); - height = Math.Max(height >> 1, 1); - depth = Math.Max(depth >> 1, 1); - } + // Calculate dimensions of next mip level. + width = Math.Max(width >> 1, 1); + height = Math.Max(height >> 1, 1); + depth = Math.Max(depth >> 1, 1); + } +#if OPENGL + }); +#endif return texture; } diff --git a/MonoGame.Framework/Content/ContentReaders/TextureCubeReader.cs b/MonoGame.Framework/Content/ContentReaders/TextureCubeReader.cs index d1432467f74..30b626602db 100644 --- a/MonoGame.Framework/Content/ContentReaders/TextureCubeReader.cs +++ b/MonoGame.Framework/Content/ContentReaders/TextureCubeReader.cs @@ -23,18 +23,25 @@ protected internal override TextureCube Read(ContentReader reader, TextureCube e else textureCube = existingInstance; - for (int face = 0; face < 6; face++) +#if OPENGL + Threading.BlockOnUIThread(() => { - for (int i=0; i((CubeMapFace)face, i, null, faceData, 0, faceSize); + for (int i = 0; i < levels; i++) + { + int faceSize = reader.ReadInt32(); + byte[] faceData = reader.ContentManager.GetScratchBuffer(faceSize); + reader.Read(faceData, 0, faceSize); + textureCube.SetData((CubeMapFace)face, i, null, faceData, 0, faceSize); + } } - } - - return textureCube; +#if OPENGL + }); +#endif + + return textureCube; } } } diff --git a/MonoGame.Framework/Content/ContentReaders/VideoReader.cs b/MonoGame.Framework/Content/ContentReaders/VideoReader.cs index 9b6e539a1f1..0a0b1cdd3de 100644 --- a/MonoGame.Framework/Content/ContentReaders/VideoReader.cs +++ b/MonoGame.Framework/Content/ContentReaders/VideoReader.cs @@ -11,21 +11,6 @@ namespace Microsoft.Xna.Framework.Content { internal class VideoReader : ContentTypeReader