From 03d7594f6592a038aaff2e2e89cd76032f3b3d13 Mon Sep 17 00:00:00 2001 From: Drewol Date: Fri, 13 May 2022 20:35:12 +0200 Subject: [PATCH 1/4] Add .editorconfig and format every source file --- .editorconfig | 15 + .gitignore | 2 + Audio/include/Audio/Audio.hpp | 44 +- Audio/include/Audio/AudioBase.hpp | 144 +- Audio/include/Audio/AudioOutput.hpp | 30 +- Audio/include/Audio/AudioStream.hpp | 24 +- Audio/include/Audio/Audio_Impl.hpp | 66 +- Audio/include/Audio/DSP.hpp | 330 +- Audio/include/Audio/Sample.hpp | 22 +- Audio/src/Audio.cpp | 290 +- Audio/src/AudioBase.cpp | 110 +- Audio/src/AudioStream.cpp | 82 +- Audio/src/AudioStreamBase.cpp | 364 +- Audio/src/AudioStreamBase.hpp | 120 +- Audio/src/AudioStreamMa.cpp | 210 +- Audio/src/AudioStreamMa.hpp | 32 +- Audio/src/AudioStreamMp3.cpp | 466 +- Audio/src/AudioStreamMp3.hpp | 46 +- Audio/src/AudioStreamOgg.cpp | 392 +- Audio/src/AudioStreamOgg.hpp | 36 +- Audio/src/AudioStreamPcm.cpp | 22 +- Audio/src/AudioStreamPcm.hpp | 12 +- Audio/src/AudioStreamWav.cpp | 830 +- Audio/src/AudioStreamWav.hpp | 78 +- Audio/src/DSP.cpp | 1167 +-- Audio/src/Sample.cpp | 248 +- Audio/src/Unix/AudioOutput_SDL.cpp | 164 +- Audio/src/Windows/AudioOutput_WASAPI.cpp | 899 ++- Audio/stdafx.cpp | 2 +- Beatmap/include/Beatmap/AudioEffects.hpp | 426 +- Beatmap/include/Beatmap/Beatmap.hpp | 254 +- Beatmap/include/Beatmap/BeatmapObjects.hpp | 414 +- Beatmap/include/Beatmap/BeatmapPlayback.hpp | 318 +- Beatmap/include/Beatmap/Database.hpp | 68 +- Beatmap/include/Beatmap/EffectTimeline.hpp | 92 +- Beatmap/include/Beatmap/KShootMap.hpp | 96 +- Beatmap/include/Beatmap/LineGraph.hpp | 4 +- Beatmap/include/Beatmap/MapDatabase.hpp | 444 +- Beatmap/include/Beatmap/PlaybackOptions.hpp | 40 +- Beatmap/include/Beatmap/TinySHA1.hpp | 340 +- Beatmap/src/AudioEffects.cpp | 296 +- Beatmap/src/Beatmap.cpp | 951 +-- Beatmap/src/BeatmapFromKSH.cpp | 2827 +++---- Beatmap/src/BeatmapObjects.cpp | 277 +- Beatmap/src/BeatmapPlayback.cpp | 1232 +-- Beatmap/src/ChallengeIndex.cpp | 726 +- Beatmap/src/Database.cpp | 189 +- Beatmap/src/EffectTimeline.cpp | 2 +- Beatmap/src/KShootMap.cpp | 677 +- Beatmap/src/LineGraph.cpp | 34 +- Beatmap/src/MapDatabase.cpp | 4535 ++++++----- Beatmap/src/PlaybackOptions.cpp | 42 +- Beatmap/stdafx.cpp | 2 +- Beatmap/stdafx.h | 2 +- CMakeLists.txt | 14 +- GUI/include/GUI/nanovg_lua.h | 1602 ++-- GUI/stdafx.cpp | 2 +- GUI/stdafx.h | 2 +- Graphics/include/Graphics/Font.hpp | 82 +- Graphics/include/Graphics/GL.hpp | 4 +- Graphics/include/Graphics/Gamepad.hpp | 22 +- Graphics/include/Graphics/Image.hpp | 88 +- Graphics/include/Graphics/ImageLoader.hpp | 28 +- Graphics/include/Graphics/Keys.hpp | 164 +- Graphics/include/Graphics/Material.hpp | 166 +- Graphics/include/Graphics/Mesh.hpp | 82 +- Graphics/include/Graphics/MeshGenerators.hpp | 52 +- Graphics/include/Graphics/OpenGL.hpp | 66 +- Graphics/include/Graphics/ParticleEmitter.hpp | 94 +- .../include/Graphics/ParticleParameter.hpp | 392 +- .../include/Graphics/ParticleParameters.hpp | 8 +- Graphics/include/Graphics/ParticleSystem.hpp | 38 +- Graphics/include/Graphics/RenderQueue.hpp | 126 +- Graphics/include/Graphics/RenderState.hpp | 28 +- .../include/Graphics/ResourceManagers.hpp | 78 +- Graphics/include/Graphics/ResourceTypes.hpp | 40 +- Graphics/include/Graphics/Shader.hpp | 74 +- Graphics/include/Graphics/Texture.hpp | 86 +- Graphics/include/Graphics/VertexFormat.hpp | 146 +- Graphics/include/Graphics/Window.hpp | 306 +- Graphics/src/Font.cpp | 751 +- Graphics/src/Gamepad_Impl.cpp | 122 +- Graphics/src/Gamepad_Impl.hpp | 42 +- Graphics/src/Image.cpp | 432 +- Graphics/src/ImageLoader.cpp | 302 +- Graphics/src/Material.cpp | 941 +-- Graphics/src/Mesh.cpp | 253 +- Graphics/src/MeshGenerators.cpp | 138 +- Graphics/src/OpenGL.cpp | 361 +- Graphics/src/ParticleSystem.cpp | 596 +- Graphics/src/RenderQueue.cpp | 474 +- Graphics/src/ResourceManagers.cpp | 140 +- Graphics/src/Shader.cpp | 508 +- Graphics/src/SpriteMap.cpp | 381 +- Graphics/src/Texture.cpp | 469 +- Graphics/src/VertexFormat.cpp | 10 +- Graphics/src/Window.cpp | 1613 ++-- Graphics/stdafx.cpp | 2 +- Main/CMakeLists.txt | 1 - Main/include/Application.hpp | 318 +- Main/include/ApplicationTickable.hpp | 50 +- Main/include/AsyncAssetLoader.hpp | 34 +- Main/include/AsyncLoadable.hpp | 26 +- Main/include/Audio/AudioPlayback.hpp | 206 +- Main/include/Audio/OffsetComputer.hpp | 96 +- Main/include/Background.hpp | 14 +- Main/include/BaseGameSettingsDialog.hpp | 4 +- Main/include/BasicDefinitions.hpp | 84 +- Main/include/CalibrationScreen.hpp | 62 +- Main/include/Camera.hpp | 330 +- Main/include/ChallengeResult.hpp | 4 +- Main/include/ChallengeSelect.hpp | 412 +- Main/include/ChatOverlay.hpp | 76 +- Main/include/CollectionDialog.hpp | 72 +- Main/include/DBUpdateScreen.hpp | 26 +- Main/include/DownloadScreen.hpp | 86 +- Main/include/FastGUI/FastGuiGame.hpp | 108 +- Main/include/FilterSelection.hpp | 8 +- Main/include/Game.hpp | 199 +- Main/include/GameConfig.hpp | 552 +- Main/include/GameFailCondition.hpp | 124 +- Main/include/GameplaySettingsDialog.hpp | 4 +- Main/include/Gauge.hpp | 164 +- Main/include/GuiUtils.hpp | 168 +- Main/include/HitStat.hpp | 130 +- Main/include/Input.hpp | 186 +- Main/include/ItemSelectionWheel.hpp | 963 +-- Main/include/LaserTrackBuilder.hpp | 90 +- Main/include/LuaRequests.hpp | 12 +- Main/include/MultiplayerScreen.hpp | 394 +- Main/include/PracticeModeSettingsDialog.hpp | 92 +- Main/include/PreviewPlayer.hpp | 42 +- Main/include/Replay.hpp | 486 +- Main/include/ScoreScreen.hpp | 10 +- Main/include/Scoring.hpp | 646 +- Main/include/Search.hpp | 172 +- Main/include/SettingsPage.hpp | 400 +- Main/include/SettingsScreen.hpp | 20 +- Main/include/ShadedMesh.hpp | 116 +- Main/include/SkinConfig.hpp | 182 +- Main/include/SkinHttp.hpp | 36 +- Main/include/SkinIR.hpp | 28 +- Main/include/SongFilter.hpp | 84 +- Main/include/SongSelect.hpp | 76 +- Main/include/SongSort.hpp | 228 +- Main/include/TCPSocket.hpp | 142 +- Main/include/Test.hpp | 14 +- Main/include/TitleScreen.hpp | 10 +- Main/include/Track.hpp | 376 +- Main/include/TransitionScreen.hpp | 22 +- Main/include/nuklear/nuklear_sdl_gl3.h | 114 +- Main/nuklear/nuklear_sdl_gles2.h | 116 +- Main/resource.h | Bin 814 -> 784 bytes Main/src/Application.cpp | 4736 ++++++----- Main/src/ApplicationTickable.cpp | 32 +- Main/src/AsyncAssetLoader.cpp | 190 +- Main/src/Audio/AudioPlayback.cpp | 874 +- Main/src/Audio/GameAudioEffects.cpp | 362 +- Main/src/Audio/OffsetComputer.cpp | 548 +- Main/src/Background.cpp | 750 +- Main/src/BaseGameSettingsDialog.cpp | 99 +- Main/src/BasicDefinitions.cpp | 4 +- Main/src/CalibrationScreen.cpp | 522 +- Main/src/Camera.cpp | 683 +- Main/src/ChallengeResult.cpp | 854 +- Main/src/ChallengeSelect.cpp | 3505 ++++---- Main/src/ChatOverlay.cpp | 465 +- Main/src/CollectionDialog.cpp | 665 +- Main/src/DBUpdateScreen.cpp | 7 +- Main/src/DownloadScreen.cpp | 1117 +-- Main/src/FastGUI/FastGuiGame.cpp | 630 +- Main/src/Game.cpp | 7040 +++++++++-------- Main/src/GameConfig.cpp | 735 +- Main/src/GameFailCondition.cpp | 55 +- Main/src/GameplaySettingsDialog.cpp | 219 +- Main/src/Gauge.cpp | 154 +- Main/src/GuiUtils.cpp | 722 +- Main/src/HitStat.cpp | 69 +- Main/src/IR.cpp | 151 +- Main/src/Input.cpp | 747 +- Main/src/LaserTrackBuilder.cpp | 522 +- Main/src/Main.cpp | 22 +- Main/src/MultiplayerScreen.cpp | 2440 +++--- Main/src/PracticeModeSettingsDialog.cpp | 266 +- Main/src/Replay.cpp | 514 +- Main/src/ScoreScreen.cpp | 2472 +++--- Main/src/Scoring.cpp | 3002 +++---- Main/src/Search.cpp | 93 +- Main/src/SettingsPage.cpp | 1089 +-- Main/src/SettingsScreen.cpp | 3062 +++---- Main/src/ShadedMesh.cpp | 1010 +-- Main/src/SkinConfig.cpp | 516 +- Main/src/SkinHttp.cpp | 338 +- Main/src/SkinIR.cpp | 279 +- Main/src/SongFilter.cpp | 102 +- Main/src/SongSelect.cpp | 3406 ++++---- Main/src/SongSort.cpp | 530 +- Main/src/TCPSocket.cpp | 815 +- Main/src/Test.cpp | 80 +- Main/src/TitleScreen.cpp | 405 +- Main/src/Track.cpp | 1394 ++-- Main/src/TrackEffects.cpp | 243 +- Main/src/TransitionScreen.cpp | 871 +- Main/stdafx.cpp | 156 +- Main/stdafx.h | 6 +- Shared/include/Shared/Action.hpp | 202 +- Shared/include/Shared/BinaryStream.hpp | 346 +- Shared/include/Shared/Bindable.hpp | 58 +- Shared/include/Shared/Buffer.hpp | 28 +- Shared/include/Shared/Color.hpp | 73 +- .../include/Shared/CompressedFileStream.hpp | 78 +- Shared/include/Shared/Config.hpp | 328 +- Shared/include/Shared/ConfigEntry.hpp | 144 +- Shared/include/Shared/Debug.hpp | 32 +- Shared/include/Shared/Delegate.hpp | 240 +- Shared/include/Shared/Enum.hpp | 198 +- Shared/include/Shared/File.hpp | 74 +- Shared/include/Shared/FileStream.hpp | 26 +- Shared/include/Shared/FileSystem.hpp | 2 +- Shared/include/Shared/Files.hpp | 50 +- Shared/include/Shared/Interpolation.hpp | 86 +- Shared/include/Shared/Jobs.hpp | 120 +- Shared/include/Shared/List.hpp | 108 +- Shared/include/Shared/Log.hpp | 80 +- Shared/include/Shared/LuaBindable.hpp | 112 +- Shared/include/Shared/Macro.hpp | 4 +- Shared/include/Shared/Map.hpp | 170 +- Shared/include/Shared/Margin.hpp | 164 +- Shared/include/Shared/Math.hpp | 186 +- Shared/include/Shared/MemoryStream.hpp | 26 +- Shared/include/Shared/Path.hpp | 152 +- Shared/include/Shared/Profiling.hpp | 22 +- Shared/include/Shared/Random.hpp | 10 +- Shared/include/Shared/Rect.hpp | 290 +- Shared/include/Shared/Ref.hpp | 10 +- Shared/include/Shared/ResourceManager.hpp | 126 +- Shared/include/Shared/Set.hpp | 28 +- Shared/include/Shared/Shared.hpp | 2 +- Shared/include/Shared/String.hpp | 486 +- Shared/include/Shared/StringEncoding.hpp | 48 +- .../Shared/StringEncodingConverter.hpp | 58 +- .../include/Shared/StringEncodingDetector.hpp | 42 +- .../Shared/StringEncodingHeuristic.hpp | 282 +- Shared/include/Shared/TextStream.hpp | 10 +- Shared/include/Shared/Thread.hpp | 14 +- Shared/include/Shared/Time.hpp | 80 +- Shared/include/Shared/Timer.hpp | 126 +- Shared/include/Shared/Transform.hpp | 70 +- Shared/include/Shared/TypeInfo.hpp | 36 +- Shared/include/Shared/Types.hpp | 6 +- Shared/include/Shared/Unique.hpp | 10 +- Shared/include/Shared/Utility.hpp | 90 +- Shared/include/Shared/Vector.hpp | 206 +- Shared/include/Shared/VectorMath.hpp | 766 +- Shared/src/Bezier.cpp | 242 +- Shared/src/BinaryStream.cpp | 76 +- Shared/src/Buffer.cpp | 36 +- Shared/src/Color.cpp | 87 +- Shared/src/Config.cpp | 165 +- Shared/src/ConfigEntry.cpp | 36 +- Shared/src/FileStream.cpp | 323 +- Shared/src/Interpolation.cpp | 164 +- Shared/src/Jobs.cpp | 404 +- Shared/src/Linux/Thread.cpp | 64 +- Shared/src/Log.cpp | 270 +- Shared/src/MacOS/Thread.cpp | 22 +- Shared/src/Math.cpp | 36 +- Shared/src/MemoryStream.cpp | 66 +- Shared/src/Path.cpp | 324 +- Shared/src/Random.cpp | 40 +- Shared/src/String.cpp | 40 +- Shared/src/StringEncodingConverter.cpp | 105 +- Shared/src/StringEncodingDetector.cpp | 170 +- Shared/src/StringEncodingHeuristic.cpp | 445 +- Shared/src/TextStream.cpp | 60 +- Shared/src/Transform.cpp | 397 +- Shared/src/UTF8.cpp | 234 +- Shared/src/Unix/Debug.cpp | 20 +- Shared/src/Unix/File.cpp | 146 +- Shared/src/Unix/Files.cpp | 214 +- Shared/src/Unix/Path.cpp | 142 +- Shared/src/VectorMath.cpp | 1 - Shared/src/Windows/Debug.cpp | 186 +- Shared/src/Windows/File.cpp | 227 +- Shared/src/Windows/Files.cpp | 215 +- Shared/src/Windows/Path.cpp | 248 +- Shared/src/Windows/Thread.cpp | 14 +- Shared/stdafx.h | 2 +- Tests.Game/src/GraphicsBase.cpp | 42 +- Tests.Game/src/GraphicsBase.hpp | 12 +- Tests.Game/src/Main.cpp | 54 +- Tests.Game/src/TestAudio.cpp | 511 +- Tests.Game/src/TestBeatmap.cpp | 556 +- Tests.Game/src/TestGUI.cpp | 2 +- Tests.Game/src/TestMusicPlayer.cpp | 30 +- Tests.Game/src/TestMusicPlayer.hpp | 16 +- Tests.Game/stdafx.cpp | 2 +- Tests.Game/stdafx.h | 2 +- Tests.Shared/src/Main.cpp | 4 +- Tests.Shared/src/TestCallbacks.cpp | 222 +- Tests.Shared/src/TestEnum.cpp | 24 +- Tests.Shared/src/TestFilesystem.cpp | 543 +- Tests.Shared/src/TestString.cpp | 54 +- Tests.Shared/src/TestStringEncoding.cpp | 352 +- Tests/include/Tests/TestManager.hpp | 82 +- Tests/include/Tests/Tests.hpp | 2 +- Tests/src/TestManager.cpp | 244 +- Tests/src/Tests.cpp | 4 +- Tests/stdafx.cpp | 2 +- Tests/stdafx.h | 2 +- updater/src/Downloader.cpp | 130 +- updater/src/Downloader.hpp | 32 +- updater/src/Extractor.cpp | 173 +- updater/src/Extractor.hpp | 14 +- updater/src/updater.cpp | 100 +- 315 files changed, 52223 insertions(+), 51957 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..bad6fd4e6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +trim_trailing_whitespace = true +end_of_line = lf +insert_final_newline = true +indent_style = space + +[*.{c,h,cpp,hpp}] +indent_size = 4 +cpp_space_pointer_reference_alignment = left +cpp_space_after_keywords_in_control_flow_statements = true diff --git a/.gitignore b/.gitignore index bdf32331e..320b81238 100755 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,8 @@ Release *.ipdb lib/ vcpkg_installed/ +build/ +.cmake/ # Profiling results *.vspx diff --git a/Audio/include/Audio/Audio.hpp b/Audio/include/Audio/Audio.hpp index ba6102fca..898c27cec 100644 --- a/Audio/include/Audio/Audio.hpp +++ b/Audio/include/Audio/Audio.hpp @@ -5,34 +5,34 @@ extern class Audio* g_audio; /* - Main audio manager - keeps track of active samples and audio streams - also handles mixing and DSP's on playing items + Main audio manager + keeps track of active samples and audio streams + also handles mixing and DSP's on playing items */ class Audio : Unique -{ +{ public: - Audio(); - ~Audio(); - // Initializes the audio device - bool Init(bool exclusive); - void SetGlobalVolume(float vol); + Audio(); + ~Audio(); + // Initializes the audio device + bool Init(bool exclusive); + void SetGlobalVolume(float vol); - // Opens a stream at path - // settings preload loads the whole file into memory before playing - Ref CreateStream(const String& path, bool preload = false); - // Open a wav file at path - Sample CreateSample(const String& path); + // Opens a stream at path + // settings preload loads the whole file into memory before playing + Ref CreateStream(const String& path, bool preload = false); + // Open a wav file at path + Sample CreateSample(const String& path); - // Target/Output sample rate - uint32 GetSampleRate() const; + // Target/Output sample rate + uint32 GetSampleRate() const; - // Private - class Audio_Impl* GetImpl(); + // Private + class Audio_Impl* GetImpl(); - // Calculated audio latency by the audio driver (currently unused) - int64 audioLatency; + // Calculated audio latency by the audio driver (currently unused) + int64 audioLatency; private: - bool m_initialized = false; -}; \ No newline at end of file + bool m_initialized = false; +}; diff --git a/Audio/include/Audio/AudioBase.hpp b/Audio/include/Audio/AudioBase.hpp index 293525637..ba2eaa96a 100644 --- a/Audio/include/Audio/AudioBase.hpp +++ b/Audio/include/Audio/AudioBase.hpp @@ -1,93 +1,93 @@ #pragma once /* - Base class for Digital Signal Processors + Base class for Digital Signal Processors */ class DSP { protected: - DSP() = default; // Abstract - DSP(const DSP &) = delete; + DSP() = default; // Abstract + DSP(const DSP&) = delete; - uint32 GetStartSample() const; - uint32 GetCurrentSample() const; + uint32 GetStartSample() const; + uint32 GetCurrentSample() const; - // Smpling rate of m_audio (not m_audioBase) - // Only use this for initializing parameters - uint32 m_sampleRate = 0; + // Smpling rate of m_audio (not m_audioBase) + // Only use this for initializing parameters + uint32 m_sampleRate = 0; - class AudioBase *m_audioBase = nullptr; + class AudioBase* m_audioBase = nullptr; public: - virtual ~DSP(); - static bool Sorter(DSP *&a, DSP *&b); - - void SetAudioBase(class AudioBase *audioBase); - inline void RemoveAudioBase() { m_audioBase = nullptr; } - inline void SetSampleRate(uint32 sampleRate) { m_sampleRate = sampleRate; } - - // Process amount of samples in stereo float format - virtual void Process(float *out, uint32 numSamples) = 0; - virtual const char *GetName() const = 0; - - float mix = 1.0f; - uint32 priority = 0; - uint32 startTime = 0; - uint32 endTime = 0; - int32 chartOffset = 0; - int32 lastTimingPoint = 0; + virtual ~DSP(); + static bool Sorter(DSP*& a, DSP*& b); + + void SetAudioBase(class AudioBase* audioBase); + inline void RemoveAudioBase() { m_audioBase = nullptr; } + inline void SetSampleRate(uint32 sampleRate) { m_sampleRate = sampleRate; } + + // Process amount of samples in stereo float format + virtual void Process(float* out, uint32 numSamples) = 0; + virtual const char* GetName() const = 0; + + float mix = 1.0f; + uint32 priority = 0; + uint32 startTime = 0; + uint32 endTime = 0; + int32 chartOffset = 0; + int32 lastTimingPoint = 0; }; /* - Base class for things that generate sound + Base class for things that generate sound */ class AudioBase { public: - virtual ~AudioBase(); - // Process amount of samples in stereo float format - virtual void Process(float *out, uint32 numSamples) = 0; - - // Gets the playback position in millisecond - virtual int32 GetPosition() const = 0; - - // Get the sample rate of this audio stream - virtual uint32 GetSampleRate() const = 0; - - // Get the exact playback position in samples - virtual uint64 GetSamplePos() const = 0; - - // Get the sample rate of the audio connected to this - uint32 GetAudioSampleRate() const; - - // Gets pcm data from a decoded stream, nullptr if not available - virtual float *GetPCM() = 0; - // Gets pcm sample count - virtual uint64 GetPCMCount() const = 0; - virtual void PreRenderDSPs(Vector &DSPs) = 0; - - void ProcessDSPs(float *out, uint32 numSamples); - // Adds a signal processor to the audio - void AddDSP(DSP *dsp); - // Removes a signal processor from the audio - void RemoveDSP(DSP *dsp); - - void Deregister(); - - // Stream volume from 0-1 - void SetVolume(float volume) - { - m_volume = volume; - } - float GetVolume() const - { - return m_volume; - } - - Vector DSPs; - float PlaybackSpeed = 1.0; - class Audio_Impl *audio = nullptr; + virtual ~AudioBase(); + // Process amount of samples in stereo float format + virtual void Process(float* out, uint32 numSamples) = 0; + + // Gets the playback position in millisecond + virtual int32 GetPosition() const = 0; + + // Get the sample rate of this audio stream + virtual uint32 GetSampleRate() const = 0; + + // Get the exact playback position in samples + virtual uint64 GetSamplePos() const = 0; + + // Get the sample rate of the audio connected to this + uint32 GetAudioSampleRate() const; + + // Gets pcm data from a decoded stream, nullptr if not available + virtual float* GetPCM() = 0; + // Gets pcm sample count + virtual uint64 GetPCMCount() const = 0; + virtual void PreRenderDSPs(Vector& DSPs) = 0; + + void ProcessDSPs(float* out, uint32 numSamples); + // Adds a signal processor to the audio + void AddDSP(DSP* dsp); + // Removes a signal processor from the audio + void RemoveDSP(DSP* dsp); + + void Deregister(); + + // Stream volume from 0-1 + void SetVolume(float volume) + { + m_volume = volume; + } + float GetVolume() const + { + return m_volume; + } + + Vector DSPs; + float PlaybackSpeed = 1.0; + class Audio_Impl* audio = nullptr; private: - float m_volume = 1.0f; -}; \ No newline at end of file + float m_volume = 1.0f; +}; diff --git a/Audio/include/Audio/AudioOutput.hpp b/Audio/include/Audio/AudioOutput.hpp index a18ae3cac..64acc864e 100644 --- a/Audio/include/Audio/AudioOutput.hpp +++ b/Audio/include/Audio/AudioOutput.hpp @@ -3,32 +3,32 @@ class IMixer { public: - virtual void Mix(void* data, uint32& numSamples) = 0; + virtual void Mix(void* data, uint32& numSamples) = 0; }; /* - Low level audio output + Low level audio output */ class AudioOutput : public Unique { public: - AudioOutput(); - ~AudioOutput(); + AudioOutput(); + ~AudioOutput(); - bool Init(bool exclusive); + bool Init(bool exclusive); - // Safe to start mixing - void Start(IMixer* mixer); - // Should stop mixing - void Stop(); + // Safe to start mixing + void Start(IMixer* mixer); + // Should stop mixing + void Stop(); - uint32_t GetNumChannels() const; - uint32_t GetSampleRate() const; + uint32_t GetNumChannels() const; + uint32_t GetSampleRate() const; - // The actual length of the buffer in seconds - double GetBufferLength() const; - bool IsIntegerFormat() const; + // The actual length of the buffer in seconds + double GetBufferLength() const; + bool IsIntegerFormat() const; private: - class AudioOutput_Impl* m_impl; + class AudioOutput_Impl* m_impl; }; diff --git a/Audio/include/Audio/AudioStream.hpp b/Audio/include/Audio/AudioStream.hpp index 0b758030e..28e46a91c 100644 --- a/Audio/include/Audio/AudioStream.hpp +++ b/Audio/include/Audio/AudioStream.hpp @@ -4,20 +4,20 @@ class Audio; /* - Audio stream object, currently only supports .ogg format - The data is pre-loaded into memory and streamed from there + Audio stream object, currently only supports .ogg format + The data is pre-loaded into memory and streamed from there */ class AudioStream : public AudioBase { public: - static Ref Create(Audio *audio, const String &path, bool preload); - static Ref Clone(Audio *audio, Ref source); - virtual ~AudioStream() = default; - // Starts playback of the stream or continues a paused stream - virtual void Play() = 0; - virtual void Pause() = 0; - virtual bool HasEnded() const = 0; - // Sets the playback position in milliseconds - // negative time alowed, which will produce no audio for a certain amount of time - virtual void SetPosition(int32 pos) = 0; + static Ref Create(Audio* audio, const String& path, bool preload); + static Ref Clone(Audio* audio, Ref source); + virtual ~AudioStream() = default; + // Starts playback of the stream or continues a paused stream + virtual void Play() = 0; + virtual void Pause() = 0; + virtual bool HasEnded() const = 0; + // Sets the playback position in milliseconds + // negative time alowed, which will produce no audio for a certain amount of time + virtual void SetPosition(int32 pos) = 0; }; diff --git a/Audio/include/Audio/Audio_Impl.hpp b/Audio/include/Audio/Audio_Impl.hpp index 252103b4b..d757a33f8 100644 --- a/Audio/include/Audio/Audio_Impl.hpp +++ b/Audio/include/Audio/Audio_Impl.hpp @@ -13,50 +13,50 @@ using std::mutex; class Audio_Impl : public IMixer { public: - Audio_Impl(); + Audio_Impl(); - void Start(); - void Stop(); - // Get samples - virtual void Mix(void* data, uint32& numSamples) override; - // Registers an AudioBase to be rendered - void Register(AudioBase* audio); - // Removes an AudioBase so it is no longer rendered - void Deregister(AudioBase* audio); + void Start(); + void Stop(); + // Get samples + virtual void Mix(void* data, uint32& numSamples) override; + // Registers an AudioBase to be rendered + void Register(AudioBase* audio); + // Removes an AudioBase so it is no longer rendered + void Deregister(AudioBase* audio); - uint32 GetSampleRate() const; - double GetSecondsPerSample() const; + uint32 GetSampleRate() const; + double GetSecondsPerSample() const; - float globalVolume = 1.0f; + float globalVolume = 1.0f; - mutex lock; - Vector itemsToRender; - Vector globalDSPs; + mutex lock; + Vector itemsToRender; + Vector globalDSPs; - class LimiterDSP* limiter = nullptr; - uint32 m_remainingSamples = 0; + class LimiterDSP* limiter = nullptr; + uint32 m_remainingSamples = 0; - thread audioThread; - bool runAudioThread = false; - AudioOutput* output = nullptr; + thread audioThread; + bool runAudioThread = false; + AudioOutput* output = nullptr; protected: - // Used to limit rendering to a fixed number of samples - constexpr static uint32 m_sampleBufferLength = 384; - std::array m_sampleBuffer; - + // Used to limit rendering to a fixed number of samples + constexpr static uint32 m_sampleBufferLength = 384; + std::array m_sampleBuffer; + private: - alignas(sizeof(float)) - std::array m_itemBuffer; + alignas(sizeof(float)) + std::array m_itemBuffer; - // Check memory corruption during filling m_itemBuffer + // Check memory corruption during filling m_itemBuffer #if _DEBUG - constexpr static uint32 m_guardBandSize = 256; + constexpr static uint32 m_guardBandSize = 256; - alignas(1) - std::array m_guard; + alignas(1) + std::array m_guard; - void InitMemoryGuard(); - void CheckMemoryGuard(); + void InitMemoryGuard(); + void CheckMemoryGuard(); #endif -}; \ No newline at end of file +}; diff --git a/Audio/include/Audio/DSP.hpp b/Audio/include/Audio/DSP.hpp index 3de82c54e..f4e16496c 100644 --- a/Audio/include/Audio/DSP.hpp +++ b/Audio/include/Audio/DSP.hpp @@ -1,5 +1,5 @@ /* - This file contains DSP's that can be applied to audio samples of streams to modify the output + This file contains DSP's that can be applied to audio samples of streams to modify the output */ #pragma once #include "AudioBase.hpp" @@ -11,299 +11,299 @@ class BQF { public: - float b0 = 1.0f; - float b1 = 0.0f; - float b2 = 0.0f; - float a0 = 1.0f; - float a1 = 0.0f; - float a2 = 0.0f; - - void SetLowPass(float q, float freq, float sampleRate); - void SetHighPass(float q, float freq, float sampleRate); - void SetAllPass(float q, float freq, float sampleRate); - void SetPeaking(float q, float freq, float gain, float sampleRate); - void SetHighShelf(float q, float freq, float gain, float sampleRate); - float Update(float in); + float b0 = 1.0f; + float b1 = 0.0f; + float b2 = 0.0f; + float a0 = 1.0f; + float a1 = 0.0f; + float a2 = 0.0f; + + void SetLowPass(float q, float freq, float sampleRate); + void SetHighPass(float q, float freq, float sampleRate); + void SetAllPass(float q, float freq, float sampleRate); + void SetPeaking(float q, float freq, float gain, float sampleRate); + void SetHighShelf(float q, float freq, float gain, float sampleRate); + float Update(float in); private: - // FIR Delay buffers - float zb[2]{}; - // IIR Delay buffers - float za[2]{}; + // FIR Delay buffers + float zb[2]{}; + // IIR Delay buffers + float za[2]{}; }; class PanDSP : public DSP { public: - // -1 to 1 LR pan value - float panning = 0.0f; - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "PanDSP"; } + // -1 to 1 LR pan value + float panning = 0.0f; + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "PanDSP"; } }; class BQFDSP : public DSP { public: - BQFDSP(uint32 sampleRate); + BQFDSP(uint32 sampleRate); - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "BQFDSP"; } + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "BQFDSP"; } - // Sets the filter parameters - void SetPeaking(float q, float freq, float gain); - void SetLowPass(float q, float freq); - void SetHighPass(float q, float freq); + // Sets the filter parameters + void SetPeaking(float q, float freq, float gain); + void SetLowPass(float q, float freq); + void SetHighPass(float q, float freq); private: - BQF m_filters[2]; + BQF m_filters[2]; }; // Combinded Low/High-pass and Peaking filter class CombinedFilterDSP : public DSP { public: - CombinedFilterDSP(uint32 sampleRate); - void SetLowPass(float q, float freq, float peakQ, float peakGain); - void SetHighPass(float q, float freq, float peakQ, float peakGain); - virtual const char *GetName() const { return "CombinedFilterDSP"; } + CombinedFilterDSP(uint32 sampleRate); + void SetLowPass(float q, float freq, float peakQ, float peakGain); + void SetHighPass(float q, float freq, float peakQ, float peakGain); + virtual const char* GetName() const { return "CombinedFilterDSP"; } - virtual void Process(float *out, uint32 numSamples); + virtual void Process(float* out, uint32 numSamples); private: - BQFDSP a; - BQFDSP peak; + BQFDSP a; + BQFDSP peak; }; // Basic limiter class LimiterDSP : public DSP { public: - LimiterDSP(uint32 sampleRate); + LimiterDSP(uint32 sampleRate); - float releaseTime = 0.1f; - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "LimiterDSP"; } + float releaseTime = 0.1f; + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "LimiterDSP"; } private: - float m_currentMaxVolume = 1.0f; - float m_currentReleaseTimer = releaseTime; + float m_currentMaxVolume = 1.0f; + float m_currentReleaseTimer = releaseTime; }; class BitCrusherDSP : public DSP { public: - BitCrusherDSP(uint32 sampleRate); + BitCrusherDSP(uint32 sampleRate); - // Duration of samples, <1 = disable - void SetPeriod(float period = 0); - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "BitCrusherDSP"; } + // Duration of samples, <1 = disable + void SetPeriod(float period = 0); + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "BitCrusherDSP"; } private: - uint32 m_period = 1; - uint32 m_increment = 0; - float m_sampleBuffer[2] = {0.0f}; - uint32 m_currentDuration = 0; + uint32 m_period = 1; + uint32 m_increment = 0; + float m_sampleBuffer[2] = { 0.0f }; + uint32 m_currentDuration = 0; }; class GateDSP : public DSP { public: - GateDSP(uint32 sampleRate); + GateDSP(uint32 sampleRate); - // The amount of time for a single cycle in samples - void SetLength(double length); - void SetGating(float gating); + // The amount of time for a single cycle in samples + void SetLength(double length); + void SetGating(float gating); - // Low volume - float low = 0.1f; + // Low volume + float low = 0.1f; - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "GateDSP"; } + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "GateDSP"; } private: - float m_gating = 0.5f; - uint32 m_length = 0; - uint32 m_fadeIn = 0; // Fade In mark - uint32 m_fadeOut = 0; // Fade Out mark - uint32 m_halfway{}; // Halfway mark - uint32 m_currentSample = 0; + float m_gating = 0.5f; + uint32 m_length = 0; + uint32 m_fadeIn = 0; // Fade In mark + uint32 m_fadeOut = 0; // Fade Out mark + uint32 m_halfway{}; // Halfway mark + uint32 m_currentSample = 0; }; class TapeStopDSP : public DSP { public: - TapeStopDSP(uint32 sampleRate); + TapeStopDSP(uint32 sampleRate); - void SetLength(double length); + void SetLength(double length); - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "TapeStopDSP"; } + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "TapeStopDSP"; } private: - uint32 m_length = 0; - Vector m_sampleBuffer; - float m_sampleIdx = 0.0f; - uint32 m_currentSample = 0; + uint32 m_length = 0; + Vector m_sampleBuffer; + float m_sampleIdx = 0.0f; + uint32 m_currentSample = 0; }; class RetriggerDSP : public DSP { public: - RetriggerDSP(uint32 sampleRate); + RetriggerDSP(uint32 sampleRate); - void SetLength(double length); - void SetResetDuration(uint32 resetDuration); - void SetGating(float gating); - void SetMaxLength(uint32 length); + void SetLength(double length); + void SetResetDuration(uint32 resetDuration); + void SetGating(float gating); + void SetMaxLength(uint32 length); - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "RetriggerDSP"; } + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "RetriggerDSP"; } private: - float m_gating = 0.75f; - uint32 m_length = 0; - uint32 m_gateLength = 0; - uint32 m_resetDuration = 0; - Vector m_sampleBuffer; - uint32 m_currentSample = 0; - bool m_bufferReserved = false; + float m_gating = 0.75f; + uint32 m_length = 0; + uint32 m_gateLength = 0; + uint32 m_resetDuration = 0; + Vector m_sampleBuffer; + uint32 m_currentSample = 0; + bool m_bufferReserved = false; }; class WobbleDSP : public DSP { public: - WobbleDSP(uint32 sampleRate); + WobbleDSP(uint32 sampleRate); - void SetLength(double length); + void SetLength(double length); - // Frequency range - float fmin = 500.0f; - float fmax = 20000.0f; - float q = 1.414f; + // Frequency range + float fmin = 500.0f; + float fmax = 20000.0f; + float q = 1.414f; - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "WobbleDSP"; } + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "WobbleDSP"; } private: - BQF m_filters[2]; - uint32 m_length{}; - uint32 m_currentSample = 0; + BQF m_filters[2]; + uint32 m_length{}; + uint32 m_currentSample = 0; }; class PhaserDSP : public DSP { public: - PhaserDSP(uint32 sampleRate); + PhaserDSP(uint32 sampleRate); - // Frequency range - float fmin = 1500.0f; - float fmax = 20000.0f; - float q = 0.707f; - float feedback = 0.35f; - float stereoWidth = 0.0f; - float hiCutGain = -8.0f; + // Frequency range + float fmin = 1500.0f; + float fmax = 20000.0f; + float q = 0.707f; + float feedback = 0.35f; + float stereoWidth = 0.0f; + float hiCutGain = -8.0f; - void SetLength(double length); - void SetStage(uint32 stage); + void SetLength(double length); + void SetStage(uint32 stage); - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "PhaserDSP"; } + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "PhaserDSP"; } private: - uint32 m_length = 0; - uint32 m_stage = 6; - BQF m_apf[12][2]; - BQF m_hiShelf[2]; - float za[2] = {0.0f}; - uint32 m_currentSample = 0; + uint32 m_length = 0; + uint32 m_stage = 6; + BQF m_apf[12][2]; + BQF m_hiShelf[2]; + float za[2] = { 0.0f }; + uint32 m_currentSample = 0; }; class FlangerDSP : public DSP { public: - FlangerDSP(uint32 sampleRate); + FlangerDSP(uint32 sampleRate); - void SetLength(double length); - void SetDelayRange(uint32 min, uint32 max); - void SetFeedback(float feedback); - void SetStereoWidth(float stereoWidth); - void SetVolume(float volume); + void SetLength(double length); + void SetDelayRange(uint32 min, uint32 max); + void SetFeedback(float feedback); + void SetStereoWidth(float stereoWidth); + void SetVolume(float volume); - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "FlangerDSP"; } + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "FlangerDSP"; } private: - uint32 m_length = 0; + uint32 m_length = 0; - // Delay range - uint32 m_min = 0; - uint32 m_max = 0; + // Delay range + uint32 m_min = 0; + uint32 m_max = 0; - float m_feedback = 0.f; - float m_stereoWidth = 0.f; - float m_volume = 0.f; + float m_feedback = 0.f; + float m_stereoWidth = 0.f; + float m_volume = 0.f; - Vector m_sampleBuffer; - uint32 m_time = 0; - uint32 m_bufferLength = 0; - size_t m_bufferOffset = 0; + Vector m_sampleBuffer; + uint32 m_time = 0; + uint32 m_bufferLength = 0; + size_t m_bufferOffset = 0; }; class EchoDSP : public DSP { public: - EchoDSP(uint32 sampleRate); + EchoDSP(uint32 sampleRate); - void SetLength(double length); + void SetLength(double length); - float feedback = 0.6f; + float feedback = 0.6f; - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "EchoDSP"; } + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "EchoDSP"; } private: - uint32 m_bufferLength = 0; - size_t m_bufferOffset = 0; - uint32 m_numLoops = 0; - Vector m_sampleBuffer; + uint32 m_bufferLength = 0; + size_t m_bufferOffset = 0; + uint32 m_numLoops = 0; + Vector m_sampleBuffer; }; class SidechainDSP : public DSP { public: - SidechainDSP(uint32 sampleRate); + SidechainDSP(uint32 sampleRate); - void SetLength(double length); - void SetAttackTime(double length); - void SetHoldTime(double length); - void SetReleaseTime(double length); + void SetLength(double length); + void SetAttackTime(double length); + void SetHoldTime(double length); + void SetReleaseTime(double length); - float ratio = 5.0f; + float ratio = 5.0f; - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "SidechainDSP"; } + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "SidechainDSP"; } private: - uint32 m_length = 0; - uint32 m_attackTime = 0; - uint32 m_holdTime = 0; - uint32 m_releaseTime = 0; - size_t m_time = 0; + uint32 m_length = 0; + uint32 m_attackTime = 0; + uint32 m_holdTime = 0; + uint32 m_releaseTime = 0; + size_t m_time = 0; }; class PitchShiftDSP : public DSP { public: - PitchShiftDSP(uint32 sampleRate); + PitchShiftDSP(uint32 sampleRate); - // Pitch change amount - float amount = 0.0f; + // Pitch change amount + float amount = 0.0f; - ~PitchShiftDSP(); + ~PitchShiftDSP(); - virtual void Process(float *out, uint32 numSamples); - virtual const char *GetName() const { return "PitchShiftDSP"; } + virtual void Process(float* out, uint32 numSamples); + virtual const char* GetName() const { return "PitchShiftDSP"; } private: - class PitchShiftDSP_Impl *m_impl; + class PitchShiftDSP_Impl* m_impl; }; diff --git a/Audio/include/Audio/Sample.hpp b/Audio/include/Audio/Sample.hpp index fb9688c6b..405598ef5 100644 --- a/Audio/include/Audio/Sample.hpp +++ b/Audio/include/Audio/Sample.hpp @@ -2,23 +2,23 @@ #include "AudioBase.hpp" /* - Audio sample, only supports wav files in signed 16 bit stereo or mono + Audio sample, only supports wav files in signed 16 bit stereo or mono */ class SampleRes : public AudioBase { public: - static Ref Create(class Audio* audio, const String& path); - virtual ~SampleRes() = default; + static Ref Create(class Audio* audio, const String& path); + virtual ~SampleRes() = default; public: - virtual const Buffer& GetData() const = 0; - virtual uint32 GetBitsPerSample() const = 0; - virtual uint32 GetNumChannels() const = 0; + virtual const Buffer& GetData() const = 0; + virtual uint32 GetBitsPerSample() const = 0; + virtual uint32 GetNumChannels() const = 0; - // Plays this sample from the start - virtual void Play(bool looping = false) = 0; - virtual void Stop() = 0; - virtual bool IsPlaying() const = 0; + // Plays this sample from the start + virtual void Play(bool looping = false) = 0; + virtual void Stop() = 0; + virtual bool IsPlaying() const = 0; }; -typedef Ref Sample; \ No newline at end of file +typedef Ref Sample; diff --git a/Audio/src/Audio.cpp b/Audio/src/Audio.cpp index 4f8a169b1..60e9b3350 100644 --- a/Audio/src/Audio.cpp +++ b/Audio/src/Audio.cpp @@ -5,215 +5,215 @@ #include "AudioOutput.hpp" #include "DSP.hpp" -Audio *g_audio = nullptr; +Audio* g_audio = nullptr; static Audio_Impl g_impl; Audio_Impl::Audio_Impl() { #if _DEBUG - InitMemoryGuard(); + InitMemoryGuard(); #endif } -void Audio_Impl::Mix(void *data, uint32 &numSamples) -{ - double adv = GetSecondsPerSample(); - - uint32 outputChannels = this->output->GetNumChannels(); - if (output->IsIntegerFormat()) - { - memset(data, 0, numSamples * sizeof(int16) * outputChannels); - } - else - { - memset(data, 0, numSamples * sizeof(float) * outputChannels); - } - - uint32 currentNumberOfSamples = 0; - while (currentNumberOfSamples < numSamples) - { - // Generate new sample - if (m_remainingSamples <= 0) - { - // Clear sample buffer storing a fixed amount of samples - m_sampleBuffer.fill(0); - - // Render items - lock.lock(); - for (auto &item : itemsToRender) - { - // Clear per-channel data - m_itemBuffer.fill(0); - item->Process(m_itemBuffer.data(), m_sampleBufferLength); +void Audio_Impl::Mix(void* data, uint32& numSamples) +{ + double adv = GetSecondsPerSample(); + + uint32 outputChannels = this->output->GetNumChannels(); + if (output->IsIntegerFormat()) + { + memset(data, 0, numSamples * sizeof(int16) * outputChannels); + } + else + { + memset(data, 0, numSamples * sizeof(float) * outputChannels); + } + + uint32 currentNumberOfSamples = 0; + while (currentNumberOfSamples < numSamples) + { + // Generate new sample + if (m_remainingSamples <= 0) + { + // Clear sample buffer storing a fixed amount of samples + m_sampleBuffer.fill(0); + + // Render items + lock.lock(); + for (auto& item : itemsToRender) + { + // Clear per-channel data + m_itemBuffer.fill(0); + item->Process(m_itemBuffer.data(), m_sampleBufferLength); #if _DEBUG - CheckMemoryGuard(); + CheckMemoryGuard(); #endif - item->ProcessDSPs(m_itemBuffer.data(), m_sampleBufferLength); + item->ProcessDSPs(m_itemBuffer.data(), m_sampleBufferLength); #if _DEBUG - CheckMemoryGuard(); + CheckMemoryGuard(); #endif - // Mix into buffer and apply volume scaling - for (uint32 i = 0; i < m_sampleBufferLength; i++) - { - m_sampleBuffer[i * 2 + 0] += m_itemBuffer[i * 2] * item->GetVolume(); - m_sampleBuffer[i * 2 + 1] += m_itemBuffer[i * 2 + 1] * item->GetVolume(); - } - } - - // Process global DSPs - for (auto dsp : globalDSPs) - { - dsp->Process(m_sampleBuffer.data(), m_sampleBufferLength); - } - lock.unlock(); - - // Apply volume levels - for (uint32 i = 0; i < m_sampleBufferLength; i++) - { - m_sampleBuffer[i * 2 + 0] *= globalVolume; - m_sampleBuffer[i * 2 + 1] *= globalVolume; - // Safety clamp to [-1, 1] that should help protect speakers a bit in case of corruption - // this will clip, but so will values outside [-1, 1] anyway - m_sampleBuffer[i * 2 + 0] = fmin(fmax(m_sampleBuffer[i * 2 + 0], -1.f), 1.f); - m_sampleBuffer[i * 2 + 1] = fmin(fmax(m_sampleBuffer[i * 2 + 1], -1.f), 1.f); - } - - // Set new remaining buffer data - m_remainingSamples = m_sampleBufferLength; - } - - // Copy samples from sample buffer - uint32 sampleOffset = m_sampleBufferLength - m_remainingSamples; - uint32 maxSamples = Math::Min(numSamples - currentNumberOfSamples, m_remainingSamples); - for (uint32 c = 0; c < outputChannels; c++) - { - if (c < 2) - { - for (uint32 i = 0; i < maxSamples; i++) - { - if (output->IsIntegerFormat()) - { - ((int16 *)data)[(currentNumberOfSamples + i) * outputChannels + c] = (int16)(0x7FFF * Math::Clamp(m_sampleBuffer[(sampleOffset + i) * 2 + c], -1.f, 1.f)); - } - else - { - ((float *)data)[(currentNumberOfSamples + i) * outputChannels + c] = m_sampleBuffer[(sampleOffset + i) * 2 + c]; - } - } - } - // TODO: Mix to surround channels as well? - } - m_remainingSamples -= maxSamples; - currentNumberOfSamples += maxSamples; - } + // Mix into buffer and apply volume scaling + for (uint32 i = 0; i < m_sampleBufferLength; i++) + { + m_sampleBuffer[i * 2 + 0] += m_itemBuffer[i * 2] * item->GetVolume(); + m_sampleBuffer[i * 2 + 1] += m_itemBuffer[i * 2 + 1] * item->GetVolume(); + } + } + + // Process global DSPs + for (auto dsp : globalDSPs) + { + dsp->Process(m_sampleBuffer.data(), m_sampleBufferLength); + } + lock.unlock(); + + // Apply volume levels + for (uint32 i = 0; i < m_sampleBufferLength; i++) + { + m_sampleBuffer[i * 2 + 0] *= globalVolume; + m_sampleBuffer[i * 2 + 1] *= globalVolume; + // Safety clamp to [-1, 1] that should help protect speakers a bit in case of corruption + // this will clip, but so will values outside [-1, 1] anyway + m_sampleBuffer[i * 2 + 0] = fmin(fmax(m_sampleBuffer[i * 2 + 0], -1.f), 1.f); + m_sampleBuffer[i * 2 + 1] = fmin(fmax(m_sampleBuffer[i * 2 + 1], -1.f), 1.f); + } + + // Set new remaining buffer data + m_remainingSamples = m_sampleBufferLength; + } + + // Copy samples from sample buffer + uint32 sampleOffset = m_sampleBufferLength - m_remainingSamples; + uint32 maxSamples = Math::Min(numSamples - currentNumberOfSamples, m_remainingSamples); + for (uint32 c = 0; c < outputChannels; c++) + { + if (c < 2) + { + for (uint32 i = 0; i < maxSamples; i++) + { + if (output->IsIntegerFormat()) + { + ((int16*)data)[(currentNumberOfSamples + i) * outputChannels + c] = (int16)(0x7FFF * Math::Clamp(m_sampleBuffer[(sampleOffset + i) * 2 + c], -1.f, 1.f)); + } + else + { + ((float*)data)[(currentNumberOfSamples + i) * outputChannels + c] = m_sampleBuffer[(sampleOffset + i) * 2 + c]; + } + } + } + // TODO: Mix to surround channels as well? + } + m_remainingSamples -= maxSamples; + currentNumberOfSamples += maxSamples; + } } void Audio_Impl::Start() { - limiter = new LimiterDSP(GetSampleRate()); - limiter->releaseTime = 0.2f; + limiter = new LimiterDSP(GetSampleRate()); + limiter->releaseTime = 0.2f; - globalDSPs.Add(limiter); - output->Start(this); + globalDSPs.Add(limiter); + output->Start(this); } void Audio_Impl::Stop() { - output->Stop(); - globalDSPs.Remove(limiter); + output->Stop(); + globalDSPs.Remove(limiter); - delete limiter; - limiter = nullptr; + delete limiter; + limiter = nullptr; } -void Audio_Impl::Register(AudioBase *audio) +void Audio_Impl::Register(AudioBase* audio) { - if (audio) - { - lock.lock(); - itemsToRender.AddUnique(audio); - audio->audio = this; - lock.unlock(); - } + if (audio) + { + lock.lock(); + itemsToRender.AddUnique(audio); + audio->audio = this; + lock.unlock(); + } } -void Audio_Impl::Deregister(AudioBase *audio) +void Audio_Impl::Deregister(AudioBase* audio) { - lock.lock(); - itemsToRender.Remove(audio); - audio->audio = nullptr; - lock.unlock(); + lock.lock(); + itemsToRender.Remove(audio); + audio->audio = nullptr; + lock.unlock(); } uint32 Audio_Impl::GetSampleRate() const { - return output->GetSampleRate(); + return output->GetSampleRate(); } double Audio_Impl::GetSecondsPerSample() const { - return 1.0 / (double)GetSampleRate(); + return 1.0 / (double)GetSampleRate(); } Audio::Audio() { - // Enforce single instance - assert(g_audio == nullptr); - g_audio = this; + // Enforce single instance + assert(g_audio == nullptr); + g_audio = this; } Audio::~Audio() { - if (m_initialized) - { - g_impl.Stop(); - delete g_impl.output; - g_impl.output = nullptr; - } + if (m_initialized) + { + g_impl.Stop(); + delete g_impl.output; + g_impl.output = nullptr; + } - assert(g_audio == this); - g_audio = nullptr; + assert(g_audio == this); + g_audio = nullptr; } bool Audio::Init(bool exclusive) { - audioLatency = 0; + audioLatency = 0; - g_impl.output = new AudioOutput(); - if (!g_impl.output->Init(exclusive)) - { - delete g_impl.output; - g_impl.output = nullptr; - return false; - } + g_impl.output = new AudioOutput(); + if (!g_impl.output->Init(exclusive)) + { + delete g_impl.output; + g_impl.output = nullptr; + return false; + } - g_impl.Start(); + g_impl.Start(); - return m_initialized = true; + return m_initialized = true; } void Audio::SetGlobalVolume(float vol) { - g_impl.globalVolume = vol; + g_impl.globalVolume = vol; } uint32 Audio::GetSampleRate() const { - return g_impl.output->GetSampleRate(); + return g_impl.output->GetSampleRate(); } -class Audio_Impl *Audio::GetImpl() +class Audio_Impl* Audio::GetImpl() { - return &g_impl; + return &g_impl; } -Ref Audio::CreateStream(const String &path, bool preload) +Ref Audio::CreateStream(const String& path, bool preload) { - return AudioStream::Create(this, path, preload); + return AudioStream::Create(this, path, preload); } -Sample Audio::CreateSample(const String &path) +Sample Audio::CreateSample(const String& path) { - return SampleRes::Create(this, path); + return SampleRes::Create(this, path); } #if _DEBUG void Audio_Impl::InitMemoryGuard() { - m_guard.fill(0); + m_guard.fill(0); } void Audio_Impl::CheckMemoryGuard() { - for (auto x : m_guard) - assert(x == 0); + for (auto x : m_guard) + assert(x == 0); } -#endif \ No newline at end of file +#endif diff --git a/Audio/src/AudioBase.cpp b/Audio/src/AudioBase.cpp index efa6730c8..657fab0d0 100644 --- a/Audio/src/AudioBase.cpp +++ b/Audio/src/AudioBase.cpp @@ -5,94 +5,94 @@ uint32 DSP::GetStartSample() const { - return static_cast(startTime * static_cast(m_audioBase->GetSampleRate()) / 1000.0); + return static_cast(startTime * static_cast(m_audioBase->GetSampleRate()) / 1000.0); } uint32 DSP::GetCurrentSample() const { - return static_cast(m_audioBase->GetSamplePos()); + return static_cast(m_audioBase->GetSamplePos()); } DSP::~DSP() { - // Make sure this is removed from parent - assert(!m_audioBase); + // Make sure this is removed from parent + assert(!m_audioBase); } -bool DSP::Sorter(DSP *&a, DSP *&b) +bool DSP::Sorter(DSP*& a, DSP*& b) { - if (a->priority == b->priority) - { - return a->startTime < b->startTime; - } - return a->priority < b->priority; + if (a->priority == b->priority) + { + return a->startTime < b->startTime; + } + return a->priority < b->priority; } -void DSP::SetAudioBase(class AudioBase *audioBase) +void DSP::SetAudioBase(class AudioBase* audioBase) { - if (!audioBase) - { - m_audioBase = nullptr; + if (!audioBase) + { + m_audioBase = nullptr; - return; - } + return; + } - m_audioBase = audioBase; + m_audioBase = audioBase; } AudioBase::~AudioBase() { - // Check this to make sure the audio is not being destroyed while it is still registered - assert(!audio); - assert(DSPs.empty()); + // Check this to make sure the audio is not being destroyed while it is still registered + assert(!audio); + assert(DSPs.empty()); } uint32 AudioBase::GetAudioSampleRate() const { - return audio->GetSampleRate(); + return audio->GetSampleRate(); } -void AudioBase::ProcessDSPs(float *out, uint32 numSamples) +void AudioBase::ProcessDSPs(float* out, uint32 numSamples) { - for (DSP *dsp : DSPs) - { - dsp->Process(out, numSamples); - } + for (DSP* dsp : DSPs) + { + dsp->Process(out, numSamples); + } } -void AudioBase::AddDSP(DSP *dsp) +void AudioBase::AddDSP(DSP* dsp) { - audio->lock.lock(); - DSPs.AddUnique(dsp); - // Sort by priority - DSPs.Sort([](DSP *l, DSP *r) { - if (l->priority == r->priority) - return l < r; - return l->priority < r->priority; - }); - dsp->SetAudioBase(this); - audio->lock.unlock(); + audio->lock.lock(); + DSPs.AddUnique(dsp); + // Sort by priority + DSPs.Sort([](DSP* l, DSP* r) + { + if (l->priority == r->priority) + return l < r; + return l->priority < r->priority; }); + dsp->SetAudioBase(this); + audio->lock.unlock(); } -void AudioBase::RemoveDSP(DSP *dsp) +void AudioBase::RemoveDSP(DSP* dsp) { - assert(DSPs.Contains(dsp)); + assert(DSPs.Contains(dsp)); - audio->lock.lock(); - DSPs.Remove(dsp); - dsp->SetAudioBase(nullptr); - audio->lock.unlock(); + audio->lock.lock(); + DSPs.Remove(dsp); + dsp->SetAudioBase(nullptr); + audio->lock.unlock(); } void AudioBase::Deregister() { - // Remove from audio manager - if (audio) - { - audio->Deregister(this); - } + // Remove from audio manager + if (audio) + { + audio->Deregister(this); + } - // Unbind DSP's - // It is safe to do here since the audio won't be rendered again after a call to deregister - for (DSP *dsp : DSPs) - { - dsp->RemoveAudioBase(); - } - DSPs.clear(); + // Unbind DSP's + // It is safe to do here since the audio won't be rendered again after a call to deregister + for (DSP* dsp : DSPs) + { + dsp->RemoveAudioBase(); + } + DSPs.clear(); } diff --git a/Audio/src/AudioStream.cpp b/Audio/src/AudioStream.cpp index 5ec5efca1..ba62f5e66 100644 --- a/Audio/src/AudioStream.cpp +++ b/Audio/src/AudioStream.cpp @@ -7,54 +7,54 @@ #include "AudioStreamPcm.hpp" #include -using CreateFunc = Ref(Audio *, const String &, bool); +using CreateFunc = Ref(Audio*, const String&, bool); -static std::unordered_map decoders = { - {"mp3", AudioStreamMp3::Create}, - {"ogg", AudioStreamOgg::Create}, - {"wav", AudioStreamMa::Create}, - //{"wav", AudioStreamWav::Create}, +static std::unordered_map decoders = { + {"mp3", AudioStreamMp3::Create}, + {"ogg", AudioStreamOgg::Create}, + {"wav", AudioStreamMa::Create}, + //{"wav", AudioStreamWav::Create}, }; -static Ref FindImplementation(Audio *audio, const String &path, bool preload) +static Ref FindImplementation(Audio* audio, const String& path, bool preload) { - Ref impl; - // Try decoder based on extension - auto fav = decoders.find(Path::GetExtension(path)); - if (fav != decoders.end()) - { - impl = fav->second(audio, path, preload); - if (impl) - { - return impl; - } - } - // Fallback on trying each other method - for (auto it = decoders.begin(); it != decoders.end(); it++) - { - if (fav == it) - continue; - impl = it->second(audio, path, preload); - if (impl) - { - return impl; - } - } - return impl; + Ref impl; + // Try decoder based on extension + auto fav = decoders.find(Path::GetExtension(path)); + if (fav != decoders.end()) + { + impl = fav->second(audio, path, preload); + if (impl) + { + return impl; + } + } + // Fallback on trying each other method + for (auto it = decoders.begin(); it != decoders.end(); it++) + { + if (fav == it) + continue; + impl = it->second(audio, path, preload); + if (impl) + { + return impl; + } + } + return impl; } -Ref AudioStream::Create(Audio *audio, const String &path, bool preload) +Ref AudioStream::Create(Audio* audio, const String& path, bool preload) { - Ref impl = FindImplementation(audio, path, preload); - if (impl) - audio->GetImpl()->Register(impl.get()); - return impl; + Ref impl = FindImplementation(audio, path, preload); + if (impl) + audio->GetImpl()->Register(impl.get()); + return impl; } -Ref AudioStream::Clone(Audio *audio, Ref source) +Ref AudioStream::Clone(Audio* audio, Ref source) { - auto clone = AudioStreamPcm::Create(audio, source); - if (clone) - audio->GetImpl()->Register(clone.get()); - return clone; -} \ No newline at end of file + auto clone = AudioStreamPcm::Create(audio, source); + if (clone) + audio->GetImpl()->Register(clone.get()); + return clone; +} diff --git a/Audio/src/AudioStreamBase.cpp b/Audio/src/AudioStreamBase.cpp index 5b35842c7..0b96a555d 100644 --- a/Audio/src/AudioStreamBase.cpp +++ b/Audio/src/AudioStreamBase.cpp @@ -5,235 +5,235 @@ // Fixed point format for resampling const uint64 AudioStreamBase::fp_sampleStep = 1ull << 48; -BinaryStream &AudioStreamBase::m_reader() +BinaryStream& AudioStreamBase::m_reader() { - return m_preloaded ? (BinaryStream &)m_memoryReader : (BinaryStream &)m_fileReader; + return m_preloaded ? (BinaryStream&)m_memoryReader : (BinaryStream&)m_fileReader; } -bool AudioStreamBase::Init(Audio *audio, const String &path, bool preload) +bool AudioStreamBase::Init(Audio* audio, const String& path, bool preload) { - m_audio = audio; + m_audio = audio; - if (!m_file.OpenRead(path)) - return false; + if (!m_file.OpenRead(path)) + return false; - if (preload) - { - m_data.resize(m_file.GetSize()); - m_file.Read(m_data.data(), m_data.size()); - m_memoryReader = MemoryReader(m_data); - m_preloaded = preload; - } - else - { - m_fileReader = FileReader(m_file); - m_preloaded = false; - } + if (preload) + { + m_data.resize(m_file.GetSize()); + m_file.Read(m_data.data(), m_data.size()); + m_memoryReader = MemoryReader(m_data); + m_preloaded = preload; + } + else + { + m_fileReader = FileReader(m_file); + m_preloaded = false; + } - return true; + return true; } void AudioStreamBase::m_initSampling(uint32 sampleRate) { - // Calculate the sample step if the rate is not the same as the output rate - double sampleStep = (double)sampleRate / (double)m_audio->GetSampleRate(); - m_sampleStepIncrement = (uint64)(sampleStep * (double)fp_sampleStep); - double stepCheck = (double)m_sampleStepIncrement / (double)fp_sampleStep; - // TODO: for practice mode: m_sampleStepIncrement *= playback_speed; - m_numChannels = 2; - m_readBuffer = new float *[m_numChannels]; - for (uint32 c = 0; c < m_numChannels; c++) - { - m_readBuffer[c] = new float[m_bufferSize]; - } + // Calculate the sample step if the rate is not the same as the output rate + double sampleStep = (double)sampleRate / (double)m_audio->GetSampleRate(); + m_sampleStepIncrement = (uint64)(sampleStep * (double)fp_sampleStep); + double stepCheck = (double)m_sampleStepIncrement / (double)fp_sampleStep; + // TODO: for practice mode: m_sampleStepIncrement *= playback_speed; + m_numChannels = 2; + m_readBuffer = new float* [m_numChannels]; + for (uint32 c = 0; c < m_numChannels; c++) + { + m_readBuffer[c] = new float[m_bufferSize]; + } } void AudioStreamBase::Play() { - if (!m_playing) - { - m_playing = true; - } - if (m_paused) - { - m_paused = false; - m_restartTiming(); - } + if (!m_playing) + { + m_playing = true; + } + if (m_paused) + { + m_paused = false; + m_restartTiming(); + } } void AudioStreamBase::Pause() { - if (!m_paused) - { - // Store time the stream was paused - m_streamTimeOffset = m_streamTimer.SecondsAsDouble(); - m_paused = true; - } - else - { - m_paused = false; - m_restartTiming(); - } + if (!m_paused) + { + // Store time the stream was paused + m_streamTimeOffset = m_streamTimer.SecondsAsDouble(); + m_paused = true; + } + else + { + m_paused = false; + m_restartTiming(); + } } bool AudioStreamBase::HasEnded() const { - return m_ended; + return m_ended; } uint64 AudioStreamBase::m_secondsToSamples(double s) const { - return (uint64)(s * (double)const_cast(this)->GetStreamRate_Internal()); + return (uint64)(s * (double)const_cast(this)->GetStreamRate_Internal()); } double AudioStreamBase::SamplesToSeconds(int64 s) const { - return (double)s / (double)const_cast(this)->GetStreamRate_Internal(); + return (double)s / (double)const_cast(this)->GetStreamRate_Internal(); } double AudioStreamBase::m_getPositionSeconds(bool allowFreezeSkip /*= true*/) const { - double samplePosTime = SamplesToSeconds(m_samplePos); - if (m_paused || m_samplePos < 0) - return samplePosTime; - else - { - double ret = m_streamTimeOffset + m_streamTimer.SecondsAsDouble() - m_offsetCorrection; - if (allowFreezeSkip && (ret - samplePosTime) > 0.2f) // Prevent time from running of when the application freezes - return samplePosTime; - return ret; - } + double samplePosTime = SamplesToSeconds(m_samplePos); + if (m_paused || m_samplePos < 0) + return samplePosTime; + else + { + double ret = m_streamTimeOffset + m_streamTimer.SecondsAsDouble() - m_offsetCorrection; + if (allowFreezeSkip && (ret - samplePosTime) > 0.2f) // Prevent time from running of when the application freezes + return samplePosTime; + return ret; + } } uint64 AudioStreamBase::GetSamplePos() const { - return m_samplePos; + return m_samplePos; } int32 AudioStreamBase::GetPosition() const { - return (int32)(m_getPositionSeconds() * 1000.0); + return (int32)(m_getPositionSeconds() * 1000.0); } void AudioStreamBase::SetPosition(int32 pos) { - m_lock.lock(); - m_remainingBufferData = 0; - m_samplePos = m_secondsToSamples((double)pos / 1000.0); - SetPosition_Internal((int32)m_samplePos); - m_ended = false; - m_lock.unlock(); + m_lock.lock(); + m_remainingBufferData = 0; + m_samplePos = m_secondsToSamples((double)pos / 1000.0); + SetPosition_Internal((int32)m_samplePos); + m_ended = false; + m_lock.unlock(); } -float *AudioStreamBase::GetPCM() +float* AudioStreamBase::GetPCM() { - return GetPCM_Internal(); + return GetPCM_Internal(); } uint64 AudioStreamBase::GetPCMCount() const { - return GetSampleCount_Internal(); + return GetSampleCount_Internal(); } uint32 AudioStreamBase::GetSampleRate() const { - return GetSampleRate_Internal(); + return GetSampleRate_Internal(); } void AudioStreamBase::m_restartTiming() { - m_streamTimeOffset = SamplesToSeconds(m_samplePos); // Add audio latency to this offset - m_samplePos = 0; - m_streamTimer.Restart(); - m_offsetCorrection = 0.0f; - m_deltaSum = 0; - m_deltaSamples = 0; -} -void AudioStreamBase::PreRenderDSPs(Vector &DSPs) -{ - PreRenderDSPs_Internal(DSPs); -} -void AudioStreamBase::Process(float *out, uint32 numSamples) -{ - if (!m_playing || m_paused) - return; - - m_lock.lock(); - - uint32 outCount = 0; - while (outCount < numSamples) - { - if (m_remainingBufferData > 0) - { - uint32 idxStart = (m_currentBufferSize - m_remainingBufferData); - uint32 readOffset = 0; // Offset from the start to read from - for (uint32 i = 0; outCount < numSamples && readOffset < m_remainingBufferData; i++) - { - if (m_samplePos < 0) - { - out[outCount * 2] = 0.0f; - out[outCount * 2 + 1] = 0.0f; - } - else - { - out[outCount * 2] = m_readBuffer[0][idxStart + readOffset]; - out[outCount * 2 + 1] = m_readBuffer[1][idxStart + readOffset]; - } - outCount++; - - // Increment source sample with resampling - m_sampleStep += static_cast(m_sampleStepIncrement * PlaybackSpeed); - while (m_sampleStep >= fp_sampleStep) - { - m_sampleStep -= fp_sampleStep; - if (m_samplePos >= 0) - readOffset++; - m_samplePos++; - } - } - m_remainingBufferData -= readOffset; - } - - if (outCount >= numSamples) - break; - - // Read more data - if (DecodeData_Internal() <= 0) - { - // Ended - Log("Audio stream ended", Logger::Severity::Info); - m_ended = true; - m_playing = false; - break; - } - } - - // Store timing info - if (m_samplePos > 0) - { - m_samplePos = GetStreamPosition_Internal() - (int64)m_remainingBufferData; - } - - if (m_samplePos > 0) - { - if ((uint64)m_samplePos >= m_samplesTotal) - { - if (!m_ended) - { - // Ended - Log("Audio stream ended", Logger::Severity::Info); - m_ended = true; - } - } - - double timingDelta = m_getPositionSeconds(false) - SamplesToSeconds(m_samplePos); - m_deltaSum += timingDelta; - m_deltaSamples += 1; - - double avgDelta = m_deltaSum / (double)m_deltaSamples; - if (abs(timingDelta - avgDelta) > 0.2) - { - Logf("Timing restart, delta = %f", Logger::Severity::Info, avgDelta); - m_restartTiming(); - } - else - { - if (fabs(avgDelta) > 0.001f) - { - // Fine tune timing - double step = abs(avgDelta) * 0.1f; - step = Math::Min(step, fabs(timingDelta)) * Math::Sign(timingDelta); - m_offsetCorrection += step; - } - } - } - - m_lock.unlock(); + m_streamTimeOffset = SamplesToSeconds(m_samplePos); // Add audio latency to this offset + m_samplePos = 0; + m_streamTimer.Restart(); + m_offsetCorrection = 0.0f; + m_deltaSum = 0; + m_deltaSamples = 0; +} +void AudioStreamBase::PreRenderDSPs(Vector& DSPs) +{ + PreRenderDSPs_Internal(DSPs); +} +void AudioStreamBase::Process(float* out, uint32 numSamples) +{ + if (!m_playing || m_paused) + return; + + m_lock.lock(); + + uint32 outCount = 0; + while (outCount < numSamples) + { + if (m_remainingBufferData > 0) + { + uint32 idxStart = (m_currentBufferSize - m_remainingBufferData); + uint32 readOffset = 0; // Offset from the start to read from + for (uint32 i = 0; outCount < numSamples && readOffset < m_remainingBufferData; i++) + { + if (m_samplePos < 0) + { + out[outCount * 2] = 0.0f; + out[outCount * 2 + 1] = 0.0f; + } + else + { + out[outCount * 2] = m_readBuffer[0][idxStart + readOffset]; + out[outCount * 2 + 1] = m_readBuffer[1][idxStart + readOffset]; + } + outCount++; + + // Increment source sample with resampling + m_sampleStep += static_cast(m_sampleStepIncrement * PlaybackSpeed); + while (m_sampleStep >= fp_sampleStep) + { + m_sampleStep -= fp_sampleStep; + if (m_samplePos >= 0) + readOffset++; + m_samplePos++; + } + } + m_remainingBufferData -= readOffset; + } + + if (outCount >= numSamples) + break; + + // Read more data + if (DecodeData_Internal() <= 0) + { + // Ended + Log("Audio stream ended", Logger::Severity::Info); + m_ended = true; + m_playing = false; + break; + } + } + + // Store timing info + if (m_samplePos > 0) + { + m_samplePos = GetStreamPosition_Internal() - (int64)m_remainingBufferData; + } + + if (m_samplePos > 0) + { + if ((uint64)m_samplePos >= m_samplesTotal) + { + if (!m_ended) + { + // Ended + Log("Audio stream ended", Logger::Severity::Info); + m_ended = true; + } + } + + double timingDelta = m_getPositionSeconds(false) - SamplesToSeconds(m_samplePos); + m_deltaSum += timingDelta; + m_deltaSamples += 1; + + double avgDelta = m_deltaSum / (double)m_deltaSamples; + if (abs(timingDelta - avgDelta) > 0.2) + { + Logf("Timing restart, delta = %f", Logger::Severity::Info, avgDelta); + m_restartTiming(); + } + else + { + if (fabs(avgDelta) > 0.001f) + { + // Fine tune timing + double step = abs(avgDelta) * 0.1f; + step = Math::Min(step, fabs(timingDelta)) * Math::Sign(timingDelta); + m_offsetCorrection += step; + } + } + } + + m_lock.unlock(); } diff --git a/Audio/src/AudioStreamBase.hpp b/Audio/src/AudioStreamBase.hpp index 0877e8545..d166b8357 100644 --- a/Audio/src/AudioStreamBase.hpp +++ b/Audio/src/AudioStreamBase.hpp @@ -6,75 +6,75 @@ class AudioStreamBase : public AudioStream { protected: - // Fixed point format for sample positions (used in resampling) - static const uint64 fp_sampleStep; + // Fixed point format for sample positions (used in resampling) + static const uint64 fp_sampleStep; - Audio *m_audio; - File m_file; - Buffer m_data; - MemoryReader m_memoryReader; - FileReader m_fileReader; - bool m_preloaded = false; - BinaryStream &m_reader(); + Audio* m_audio; + File m_file; + Buffer m_data; + MemoryReader m_memoryReader; + FileReader m_fileReader; + bool m_preloaded = false; + BinaryStream& m_reader(); - mutex m_lock; + mutex m_lock; - float **m_readBuffer = nullptr; - uint32 m_bufferSize = 4096; - uint32 m_numChannels = 0; - uint32 m_currentBufferSize = 0; - uint32 m_remainingBufferData = 0; + float** m_readBuffer = nullptr; + uint32 m_bufferSize = 4096; + uint32 m_numChannels = 0; + uint32 m_currentBufferSize = 0; + uint32 m_remainingBufferData = 0; - int64 m_samplePos = 0; - uint64 m_samplesTotal = 0; // Total pcm length of audio stream + int64 m_samplePos = 0; + uint64 m_samplesTotal = 0; // Total pcm length of audio stream - // Resampling values - uint64 m_sampleStep = 0; - uint64 m_sampleStepIncrement = 0; + // Resampling values + uint64 m_sampleStep = 0; + uint64 m_sampleStepIncrement = 0; - Timer m_deltaTimer; - Timer m_streamTimer; - double m_streamTimeOffset = 0.0f; - double m_offsetCorrection = 0.0f; + Timer m_deltaTimer; + Timer m_streamTimer; + double m_streamTimeOffset = 0.0f; + double m_offsetCorrection = 0.0f; - double m_deltaSum = 0.0f; - int32 m_deltaSamples = 0; + double m_deltaSum = 0.0f; + int32 m_deltaSamples = 0; - bool m_paused = false; - bool m_playing = false; - bool m_ended = false; + bool m_paused = false; + bool m_playing = false; + bool m_ended = false; - float m_volume = 0.8f; - void m_initSampling(uint32 sampleRate); - uint64 m_secondsToSamples(double s) const; - void m_restartTiming(); - double m_getPositionSeconds(bool allowFreezeSkip = true) const; + float m_volume = 0.8f; + void m_initSampling(uint32 sampleRate); + uint64 m_secondsToSamples(double s) const; + void m_restartTiming(); + double m_getPositionSeconds(bool allowFreezeSkip = true) const; - // Implementation specific set position - virtual void SetPosition_Internal(int32 pos) = 0; - virtual int32 GetStreamPosition_Internal() = 0; - virtual float *GetPCM_Internal() = 0; - virtual uint32 GetSampleRate_Internal() const = 0; - virtual uint64 GetSampleCount_Internal() const = 0; - virtual void PreRenderDSPs_Internal(Vector &DSPs){}; - // Internal sample rate - virtual int32 GetStreamRate_Internal() = 0; - // Implementation specific decode - // return negative for end of stream or failure - virtual int32 DecodeData_Internal() = 0; - virtual bool Init(Audio *audio, const String &path, bool preload); + // Implementation specific set position + virtual void SetPosition_Internal(int32 pos) = 0; + virtual int32 GetStreamPosition_Internal() = 0; + virtual float* GetPCM_Internal() = 0; + virtual uint32 GetSampleRate_Internal() const = 0; + virtual uint64 GetSampleCount_Internal() const = 0; + virtual void PreRenderDSPs_Internal(Vector& DSPs) {}; + // Internal sample rate + virtual int32 GetStreamRate_Internal() = 0; + // Implementation specific decode + // return negative for end of stream or failure + virtual int32 DecodeData_Internal() = 0; + virtual bool Init(Audio* audio, const String& path, bool preload); public: - virtual void Play() override; - virtual void Pause() override; - virtual bool HasEnded() const override; - double SamplesToSeconds(int64 s) const; - virtual int32 GetPosition() const override; - virtual uint64 GetSamplePos() const override; - virtual void SetPosition(int32 pos) override; - virtual float *GetPCM() override; - virtual uint64 GetPCMCount() const override; - virtual uint32 GetSampleRate() const override; - virtual void PreRenderDSPs(Vector &DSPs) override; - virtual void Process(float *out, uint32 numSamples) override; -}; \ No newline at end of file + virtual void Play() override; + virtual void Pause() override; + virtual bool HasEnded() const override; + double SamplesToSeconds(int64 s) const; + virtual int32 GetPosition() const override; + virtual uint64 GetSamplePos() const override; + virtual void SetPosition(int32 pos) override; + virtual float* GetPCM() override; + virtual uint64 GetPCMCount() const override; + virtual uint32 GetSampleRate() const override; + virtual void PreRenderDSPs(Vector& DSPs) override; + virtual void Process(float* out, uint32 numSamples) override; +}; diff --git a/Audio/src/AudioStreamMa.cpp b/Audio/src/AudioStreamMa.cpp index 0dc38bb32..31a51dbcf 100644 --- a/Audio/src/AudioStreamMa.cpp +++ b/Audio/src/AudioStreamMa.cpp @@ -6,150 +6,150 @@ #define DR_FLAC_IMPLEMENTATION #include "extras/dr_flac.h" // Enables FLAC decoding. #define DR_MP3_IMPLEMENTATION -#include "extras/dr_mp3.h" // Enables MP3 decoding. +#include "extras/dr_mp3.h" // Enables MP3 decoding. #define STB_VORBIS_HEADER_ONLY -#include "extras/stb_vorbis.c" // Enables Vorbis decoding. +#include "extras/stb_vorbis.c" // Enables Vorbis decoding. #define MINIAUDIO_IMPLEMENTATION #include "miniaudio.h" AudioStreamMa::~AudioStreamMa() { - Deregister(); - if (m_preloaded) - { - if (m_pcm) - { - ma_free(m_pcm); - m_pcm = nullptr; - } - } - else - { - ma_decoder_uninit(&m_decoder); - } + Deregister(); + if (m_preloaded) + { + if (m_pcm) + { + ma_free(m_pcm); + m_pcm = nullptr; + } + } + else + { + ma_decoder_uninit(&m_decoder); + } - for (size_t i = 0; i < m_numChannels; i++) - { - delete[] m_readBuffer[i]; - } - delete[] m_readBuffer; + for (size_t i = 0; i < m_numChannels; i++) + { + delete[] m_readBuffer[i]; + } + delete[] m_readBuffer; } -bool AudioStreamMa::Init(Audio *audio, const String &path, bool preload) +bool AudioStreamMa::Init(Audio* audio, const String& path, bool preload) { - if (!AudioStreamBase::Init(audio, path, preload)) // Always preload for now - return false; + if (!AudioStreamBase::Init(audio, path, preload)) // Always preload for now + return false; - ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 2, sample_rate); - ma_result result; + ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 2, sample_rate); + ma_result result; - if (m_preloaded) - { - result = ma_decode_memory((void *)m_data.data(), m_file.GetSize(), &config, &m_samplesTotal, (void **)&m_pcm); - } - else - { - result = ma_decoder_init_file(*path, &config, &m_decoder); - } + if (m_preloaded) + { + result = ma_decode_memory((void*)m_data.data(), m_file.GetSize(), &config, &m_samplesTotal, (void**)&m_pcm); + } + else + { + result = ma_decoder_init_file(*path, &config, &m_decoder); + } - if (result != MA_SUCCESS) - { - return false; - } + if (result != MA_SUCCESS) + { + return false; + } - m_initSampling(sample_rate); - return true; + m_initSampling(sample_rate); + return true; } int32 AudioStreamMa::GetStreamPosition_Internal() { - return m_playbackPointer; + return m_playbackPointer; } int32 AudioStreamMa::GetStreamRate_Internal() { - return sample_rate; + return sample_rate; } void AudioStreamMa::SetPosition_Internal(int32 pos) { - if (pos < 0) - m_playbackPointer = 0; - else - m_playbackPointer = pos; + if (pos < 0) + m_playbackPointer = 0; + else + m_playbackPointer = pos; - if (!m_preloaded) - { - //seek_to_pcm_frame is very slow - //we want to use ma_decoder_seek_bytes_64 - //but we require some stuff to do that properly + if (!m_preloaded) + { + // seek_to_pcm_frame is very slow + // we want to use ma_decoder_seek_bytes_64 + // but we require some stuff to do that properly - //ma_decoder_seek_to_pcm_frame(&m_decoder, pos); - return; - } + // ma_decoder_seek_to_pcm_frame(&m_decoder, pos); + return; + } } int32 AudioStreamMa::DecodeData_Internal() { - if (m_preloaded) - { - uint32 samplesPerRead = 128; - int actualRead = 0; - for (size_t i = 0; i < samplesPerRead; i++) - { - if (m_playbackPointer >= (int64)m_samplesTotal) - { - m_currentBufferSize = samplesPerRead; - m_remainingBufferData = samplesPerRead; - return i; - } - m_readBuffer[0][i] = m_pcm[m_playbackPointer * 2]; - m_readBuffer[1][i] = m_pcm[m_playbackPointer * 2 + 1]; - m_playbackPointer++; - actualRead++; - } - m_currentBufferSize = samplesPerRead; - m_remainingBufferData = samplesPerRead; - return samplesPerRead; - } - else - { - uint32 samplesPerRead = 128; - float decodeBuffer[256]; + if (m_preloaded) + { + uint32 samplesPerRead = 128; + int actualRead = 0; + for (size_t i = 0; i < samplesPerRead; i++) + { + if (m_playbackPointer >= (int64)m_samplesTotal) + { + m_currentBufferSize = samplesPerRead; + m_remainingBufferData = samplesPerRead; + return i; + } + m_readBuffer[0][i] = m_pcm[m_playbackPointer * 2]; + m_readBuffer[1][i] = m_pcm[m_playbackPointer * 2 + 1]; + m_playbackPointer++; + actualRead++; + } + m_currentBufferSize = samplesPerRead; + m_remainingBufferData = samplesPerRead; + return samplesPerRead; + } + else + { + uint32 samplesPerRead = 128; + float decodeBuffer[256]; - int totalRead = ma_decoder_read_pcm_frames(&m_decoder, decodeBuffer, samplesPerRead); - for (int i = 0; i < totalRead; i++) - { - m_readBuffer[0][i] = decodeBuffer[i * 2]; - m_readBuffer[1][i] = decodeBuffer[i * 2 + 1]; - } - m_currentBufferSize = totalRead; - m_remainingBufferData = totalRead; - return totalRead; - } - return 0; + int totalRead = ma_decoder_read_pcm_frames(&m_decoder, decodeBuffer, samplesPerRead); + for (int i = 0; i < totalRead; i++) + { + m_readBuffer[0][i] = decodeBuffer[i * 2]; + m_readBuffer[1][i] = decodeBuffer[i * 2 + 1]; + } + m_currentBufferSize = totalRead; + m_remainingBufferData = totalRead; + return totalRead; + } + return 0; } -float *AudioStreamMa::GetPCM_Internal() +float* AudioStreamMa::GetPCM_Internal() { - return m_pcm; + return m_pcm; } uint32 AudioStreamMa::GetSampleRate_Internal() const { - return sample_rate; + return sample_rate; } uint64 AudioStreamMa::GetSampleCount_Internal() const { - if (m_preloaded) - { - return m_samplesTotal; - } - return 0; + if (m_preloaded) + { + return m_samplesTotal; + } + return 0; } -Ref AudioStreamMa::Create(class Audio *audio, const String &path, bool preload) +Ref AudioStreamMa::Create(class Audio* audio, const String& path, bool preload) { - AudioStreamMa *impl = new AudioStreamMa(); - if (!impl->Init(audio, path, preload)) - { - delete impl; - impl = nullptr; - } - return Utility::CastRef(Ref(impl)); + AudioStreamMa* impl = new AudioStreamMa(); + if (!impl->Init(audio, path, preload)) + { + delete impl; + impl = nullptr; + } + return Utility::CastRef(Ref(impl)); } diff --git a/Audio/src/AudioStreamMa.hpp b/Audio/src/AudioStreamMa.hpp index 25f6a7831..75e2d911e 100644 --- a/Audio/src/AudioStreamMa.hpp +++ b/Audio/src/AudioStreamMa.hpp @@ -5,24 +5,24 @@ class AudioStreamMa : public AudioStreamBase { private: - Buffer m_Internaldata; - float *m_pcm = nullptr; - int64 m_playbackPointer = 0; - const int sample_rate = 48000; - ma_decoder m_decoder = {}; + Buffer m_Internaldata; + float* m_pcm = nullptr; + int64 m_playbackPointer = 0; + const int sample_rate = 48000; + ma_decoder m_decoder = {}; protected: - bool Init(Audio *audio, const String &path, bool preload) override; - int32 GetStreamPosition_Internal() override; - int32 GetStreamRate_Internal() override; - void SetPosition_Internal(int32 pos) override; - int32 DecodeData_Internal() override; - float *GetPCM_Internal() override; - uint64 GetSampleCount_Internal() const override; - uint32 GetSampleRate_Internal() const override; + bool Init(Audio* audio, const String& path, bool preload) override; + int32 GetStreamPosition_Internal() override; + int32 GetStreamRate_Internal() override; + void SetPosition_Internal(int32 pos) override; + int32 DecodeData_Internal() override; + float* GetPCM_Internal() override; + uint64 GetSampleCount_Internal() const override; + uint32 GetSampleRate_Internal() const override; public: - AudioStreamMa() = default; - ~AudioStreamMa(); - static Ref Create(class Audio *audio, const String &path, bool preload); + AudioStreamMa() = default; + ~AudioStreamMa(); + static Ref Create(class Audio* audio, const String& path, bool preload); }; diff --git a/Audio/src/AudioStreamMp3.cpp b/Audio/src/AudioStreamMp3.cpp index 25670bfbc..f839e3611 100644 --- a/Audio/src/AudioStreamMp3.cpp +++ b/Audio/src/AudioStreamMp3.cpp @@ -4,293 +4,293 @@ // https://en.wikipedia.org/wiki/Synchsafe int AudioStreamMp3::m_unsynchsafe(int in) { - int out = 0, mask = 0x7F000000; + int out = 0, mask = 0x7F000000; - while (mask) - { - out >>= 1; - out |= in & mask; - mask >>= 8; - } + while (mask) + { + out >>= 1; + out |= in & mask; + mask >>= 8; + } - return out; + return out; } int AudioStreamMp3::m_toLittleEndian(int num) { - return ((num >> 24) & 0xff) | // move byte 3 to byte 0 - ((num << 8) & 0xff0000) | // move byte 1 to byte 2 - ((num >> 8) & 0xff00) | // move byte 2 to byte 1 - ((num << 24) & 0xff000000); // byte 0 to byte 3 + return ((num >> 24) & 0xff) | // move byte 3 to byte 0 + ((num << 8) & 0xff0000) | // move byte 1 to byte 2 + ((num >> 8) & 0xff00) | // move byte 2 to byte 1 + ((num << 24) & 0xff000000); // byte 0 to byte 3 } AudioStreamMp3::~AudioStreamMp3() { - Deregister(); - mp3_done(m_decoder); + Deregister(); + mp3_done(m_decoder); - for (size_t i = 0; i < m_numChannels; i++) - { - delete[] m_readBuffer[i]; - } - delete[] m_readBuffer; + for (size_t i = 0; i < m_numChannels; i++) + { + delete[] m_readBuffer[i]; + } + delete[] m_readBuffer; } -bool AudioStreamMp3::Init(Audio *audio, const String &path, bool preload) +bool AudioStreamMp3::Init(Audio* audio, const String& path, bool preload) { - ///TODO: Write non-preload functions - if (!AudioStreamBase::Init(audio, path, true)) // Always preload for now - return false; + /// TODO: Write non-preload functions + if (!AudioStreamBase::Init(audio, path, true)) // Always preload for now + return false; - // Always use preloaded data - m_mp3dataLength = m_reader().GetSize(); - m_dataSource = m_data.data(); - int32 tagSize = 0; + // Always use preloaded data + m_mp3dataLength = m_reader().GetSize(); + m_dataSource = m_data.data(); + int32 tagSize = 0; - String tag = "tag"; - for (size_t i = 0; i < 3; i++) - { - tag[i] = m_dataSource[i]; - } - while (tag == "ID3") - { - tagSize += m_unsynchsafe(m_toLittleEndian(*(int32 *)(m_dataSource + 6 + tagSize))) + 10; - for (size_t i = 0; i < 3; i++) - { - tag[i] = m_dataSource[i + tagSize]; - } - if (tag == "3DI") - { - tagSize += 10; - } - for (size_t i = 0; i < 3; i++) - { - tag[i] = m_dataSource[i + tagSize]; - } - } - // Scan MP3 frame offsets - uint32 sampleOffset = 0; - for (size_t i = tagSize; i < m_mp3dataLength;) - { - if (m_dataSource[i] == 0xFF) - { - if (i + 1 > m_mp3dataLength) - continue; - if ((m_dataSource[i + 1] & 0xE0) == 0xE0) // Frame Sync - { - uint8 version = (m_dataSource[i + 1] & 0x18) >> 3; - uint8 layer = (m_dataSource[i + 1] & 0x06) >> 1; - bool crc = (m_dataSource[i + 1] & 0x01) != 0; - uint8 bitrateIndex = (m_dataSource[i + 2] & 0xF0) >> 4; - uint8 rateIndex = (m_dataSource[i + 2] & 0x0C) >> 2; - bool paddingEnabled = ((m_dataSource[i + 2] & 0x02) >> 1) != 0; - uint8 channelFlags = ((m_dataSource[i + 3] & 0xC0) >> 6); - if (bitrateIndex == 0xF || rateIndex > 2) // bad - { - return false; - i++; - continue; - } + String tag = "tag"; + for (size_t i = 0; i < 3; i++) + { + tag[i] = m_dataSource[i]; + } + while (tag == "ID3") + { + tagSize += m_unsynchsafe(m_toLittleEndian(*(int32*)(m_dataSource + 6 + tagSize))) + 10; + for (size_t i = 0; i < 3; i++) + { + tag[i] = m_dataSource[i + tagSize]; + } + if (tag == "3DI") + { + tagSize += 10; + } + for (size_t i = 0; i < 3; i++) + { + tag[i] = m_dataSource[i + tagSize]; + } + } + // Scan MP3 frame offsets + uint32 sampleOffset = 0; + for (size_t i = tagSize; i < m_mp3dataLength;) + { + if (m_dataSource[i] == 0xFF) + { + if (i + 1 > m_mp3dataLength) + continue; + if ((m_dataSource[i + 1] & 0xE0) == 0xE0) // Frame Sync + { + uint8 version = (m_dataSource[i + 1] & 0x18) >> 3; + uint8 layer = (m_dataSource[i + 1] & 0x06) >> 1; + bool crc = (m_dataSource[i + 1] & 0x01) != 0; + uint8 bitrateIndex = (m_dataSource[i + 2] & 0xF0) >> 4; + uint8 rateIndex = (m_dataSource[i + 2] & 0x0C) >> 2; + bool paddingEnabled = ((m_dataSource[i + 2] & 0x02) >> 1) != 0; + uint8 channelFlags = ((m_dataSource[i + 3] & 0xC0) >> 6); + if (bitrateIndex == 0xF || rateIndex > 2) // bad + { + return false; + i++; + continue; + } - uint8 channels = ((channelFlags & 0x3) == 0x3) ? 1 : 2; + uint8 channels = ((channelFlags & 0x3) == 0x3) ? 1 : 2; - uint32 linearVersion = version == 0x03 ? 0 : 1; // Version 1/2 - uint32 bitrate = mp3_bitrate_tab[linearVersion][bitrateIndex] * 1000; - uint32 sampleRate = mp3_freq_tab[rateIndex]; - uint32 padding = paddingEnabled ? 1 : 0; + uint32 linearVersion = version == 0x03 ? 0 : 1; // Version 1/2 + uint32 bitrate = mp3_bitrate_tab[linearVersion][bitrateIndex] * 1000; + uint32 sampleRate = mp3_freq_tab[rateIndex]; + uint32 padding = paddingEnabled ? 1 : 0; - uint32 frameLength = 144 * bitrate / sampleRate + padding; - if (frameLength == 0) - { - return false; - i++; - continue; - } + uint32 frameLength = 144 * bitrate / sampleRate + padding; + if (frameLength == 0) + { + return false; + i++; + continue; + } - i += frameLength; - uint32 frameSamples = (linearVersion == 0) ? 1152 : 576; - m_frameIndices.Add((int32)sampleOffset, i); - sampleOffset += frameSamples; - continue; // Skip header - } - } - i++; - } + i += frameLength; + uint32 frameSamples = (linearVersion == 0) ? 1152 : 576; + m_frameIndices.Add((int32)sampleOffset, i); + sampleOffset += frameSamples; + continue; // Skip header + } + } + i++; + } - // No mp3 frames found - if (m_frameIndices.empty()) - { - Logf("No valid mp3 frames found in file \"%s\"", Logger::Severity::Warning, path); - return false; - } + // No mp3 frames found + if (m_frameIndices.empty()) + { + Logf("No valid mp3 frames found in file \"%s\"", Logger::Severity::Warning, path); + return false; + } - // Total sample - m_samplesTotal = sampleOffset; + // Total sample + m_samplesTotal = sampleOffset; - m_decoder = (mp3_decoder_t *)mp3_create(); - m_preloaded = false; - SetPosition_Internal(-400000); - int32 r = DecodeData_Internal(); - if (r <= 0) - return false; + m_decoder = (mp3_decoder_t*)mp3_create(); + m_preloaded = false; + SetPosition_Internal(-400000); + int32 r = DecodeData_Internal(); + if (r <= 0) + return false; - if (preload) - { - int totalSamples = 0; - while (r > 0) - { - for (int32 i = 0; i < r; i++) - { - m_pcm.Add(m_readBuffer[0][i]); - m_pcm.Add(m_readBuffer[1][i]); - } - totalSamples += r; - r = DecodeData_Internal(); - } - m_data.clear(); - m_dataSource = nullptr; - m_samplesTotal = totalSamples; - } - m_preloaded = preload; - m_playPos = 0; + if (preload) + { + int totalSamples = 0; + while (r > 0) + { + for (int32 i = 0; i < r; i++) + { + m_pcm.Add(m_readBuffer[0][i]); + m_pcm.Add(m_readBuffer[1][i]); + } + totalSamples += r; + r = DecodeData_Internal(); + } + m_data.clear(); + m_dataSource = nullptr; + m_samplesTotal = totalSamples; + } + m_preloaded = preload; + m_playPos = 0; - return true; + return true; } void AudioStreamMp3::SetPosition_Internal(int32 pos) { - if (m_preloaded) - { - if (pos < 0) - m_playPos = 0; - else - m_playPos = pos; + if (m_preloaded) + { + if (pos < 0) + m_playPos = 0; + else + m_playPos = pos; - return; - } + return; + } - auto it = m_frameIndices.lower_bound(pos); - if (it == m_frameIndices.end()) - { - --it; // Take the last frame - } - if (it != m_frameIndices.end()) - { - m_mp3samplePosition = it->first; - m_mp3dataOffset = it->second; - } + auto it = m_frameIndices.lower_bound(pos); + if (it == m_frameIndices.end()) + { + --it; // Take the last frame + } + if (it != m_frameIndices.end()) + { + m_mp3samplePosition = it->first; + m_mp3dataOffset = it->second; + } } int32 AudioStreamMp3::GetStreamPosition_Internal() { - if (m_preloaded) - return m_playPos; + if (m_preloaded) + return m_playPos; - return m_mp3samplePosition; + return m_mp3samplePosition; } int32 AudioStreamMp3::GetStreamRate_Internal() { - return (int32)m_samplingRate; + return (int32)m_samplingRate; } uint32 AudioStreamMp3::GetSampleRate_Internal() const { - return m_samplingRate; + return m_samplingRate; } uint64 AudioStreamMp3::GetSampleCount_Internal() const { - if (m_preloaded) - { - return m_samplesTotal; - } - return 0; + if (m_preloaded) + { + return m_samplesTotal; + } + return 0; } -float *AudioStreamMp3::GetPCM_Internal() +float* AudioStreamMp3::GetPCM_Internal() { - if (m_preloaded) - return &m_pcm.front(); + if (m_preloaded) + return &m_pcm.front(); - return nullptr; + return nullptr; } int32 AudioStreamMp3::DecodeData_Internal() { - if (m_preloaded) - { - uint32 samplesPerRead = 128; + if (m_preloaded) + { + uint32 samplesPerRead = 128; - for (size_t i = 0; i < samplesPerRead; i++) - { - if (m_playPos < 0) - { - m_readBuffer[0][i] = 0; - m_readBuffer[1][i] = 0; - m_playPos++; - continue; - } - else if ((uint64)m_playPos >= m_samplesTotal) - { - m_currentBufferSize = samplesPerRead; - m_remainingBufferData = samplesPerRead; - m_playing = false; - return i; - } - m_readBuffer[0][i] = m_pcm[m_playPos * 2]; - m_readBuffer[1][i] = m_pcm[m_playPos * 2 + 1]; - m_playPos++; - } - m_currentBufferSize = samplesPerRead; - m_remainingBufferData = samplesPerRead; - return samplesPerRead; - } + for (size_t i = 0; i < samplesPerRead; i++) + { + if (m_playPos < 0) + { + m_readBuffer[0][i] = 0; + m_readBuffer[1][i] = 0; + m_playPos++; + continue; + } + else if ((uint64)m_playPos >= m_samplesTotal) + { + m_currentBufferSize = samplesPerRead; + m_remainingBufferData = samplesPerRead; + m_playing = false; + return i; + } + m_readBuffer[0][i] = m_pcm[m_playPos * 2]; + m_readBuffer[1][i] = m_pcm[m_playPos * 2 + 1]; + m_playPos++; + } + m_currentBufferSize = samplesPerRead; + m_remainingBufferData = samplesPerRead; + return samplesPerRead; + } - int16 buffer[MP3_MAX_SAMPLES_PER_FRAME]; - mp3_info_t info; - int32 readData = 0; - while (true) - { - readData = mp3_decode(m_decoder, (uint8 *)m_dataSource + m_mp3dataOffset, (int)(m_mp3dataLength - m_mp3dataOffset), buffer, &info); - m_mp3dataOffset += readData; - if (m_mp3dataOffset >= m_mp3dataLength) // EOF - return -1; - if (readData <= 0) - return -1; - if (info.audio_bytes >= 0) - break; - } + int16 buffer[MP3_MAX_SAMPLES_PER_FRAME]; + mp3_info_t info; + int32 readData = 0; + while (true) + { + readData = mp3_decode(m_decoder, (uint8*)m_dataSource + m_mp3dataOffset, (int)(m_mp3dataLength - m_mp3dataOffset), buffer, &info); + m_mp3dataOffset += readData; + if (m_mp3dataOffset >= m_mp3dataLength) // EOF + return -1; + if (readData <= 0) + return -1; + if (info.audio_bytes >= 0) + break; + } - int32 samplesGotten = info.audio_bytes / (info.channels * sizeof(short)); - m_mp3samplePosition += samplesGotten; + int32 samplesGotten = info.audio_bytes / (info.channels * sizeof(short)); + m_mp3samplePosition += samplesGotten; - if (m_firstFrame) - { - m_bufferSize = MP3_MAX_SAMPLES_PER_FRAME / 2; - m_initSampling(m_samplingRate = info.sample_rate); - m_firstFrame = false; - } + if (m_firstFrame) + { + m_bufferSize = MP3_MAX_SAMPLES_PER_FRAME / 2; + m_initSampling(m_samplingRate = info.sample_rate); + m_firstFrame = false; + } - // Copy data to read buffer - for (int32 i = 0; i < samplesGotten; i++) - { - if (info.channels == 1) - { - m_readBuffer[0][i] = (float)buffer[i] / (float)0x7FFF; - m_readBuffer[1][i] = m_readBuffer[0][i]; - } - else if (info.channels == 2) - { - m_readBuffer[0][i] = (float)buffer[i * 2 + 0] / (float)0x7FFF; - m_readBuffer[1][i] = (float)buffer[i * 2 + 1] / (float)0x7FFF; - } - } - m_currentBufferSize = samplesGotten; - m_remainingBufferData = samplesGotten; - return samplesGotten; + // Copy data to read buffer + for (int32 i = 0; i < samplesGotten; i++) + { + if (info.channels == 1) + { + m_readBuffer[0][i] = (float)buffer[i] / (float)0x7FFF; + m_readBuffer[1][i] = m_readBuffer[0][i]; + } + else if (info.channels == 2) + { + m_readBuffer[0][i] = (float)buffer[i * 2 + 0] / (float)0x7FFF; + m_readBuffer[1][i] = (float)buffer[i * 2 + 1] / (float)0x7FFF; + } + } + m_currentBufferSize = samplesGotten; + m_remainingBufferData = samplesGotten; + return samplesGotten; } -Ref AudioStreamMp3::Create(Audio *audio, const String &path, bool preload) +Ref AudioStreamMp3::Create(Audio* audio, const String& path, bool preload) { - AudioStreamMp3 *impl = new AudioStreamMp3(); - if (!impl->Init(audio, path, preload)) - { - delete impl; - impl = nullptr; - return Ref(); - } - return Ref(impl); + AudioStreamMp3* impl = new AudioStreamMp3(); + if (!impl->Init(audio, path, preload)) + { + delete impl; + impl = nullptr; + return Ref(); + } + return Ref(impl); } diff --git a/Audio/src/AudioStreamMp3.hpp b/Audio/src/AudioStreamMp3.hpp index 4f8f55238..f1b8bbf3e 100644 --- a/Audio/src/AudioStreamMp3.hpp +++ b/Audio/src/AudioStreamMp3.hpp @@ -7,33 +7,33 @@ extern "C" class AudioStreamMp3 : public AudioStreamBase { - mp3_decoder_t *m_decoder = nullptr; - size_t m_mp3dataOffset = 0; - size_t m_mp3dataLength = 0; - int32 m_mp3samplePosition = 0; - int32 m_samplingRate = 0; - uint8 *m_dataSource = 0; + mp3_decoder_t* m_decoder = nullptr; + size_t m_mp3dataOffset = 0; + size_t m_mp3dataLength = 0; + int32 m_mp3samplePosition = 0; + int32 m_samplingRate = 0; + uint8* m_dataSource = 0; - Map m_frameIndices; - Vector m_pcm; - int64 m_playPos; + Map m_frameIndices; + Vector m_pcm; + int64 m_playPos; - bool m_firstFrame = true; - int m_unsynchsafe(int in); - int m_toLittleEndian(int num); + bool m_firstFrame = true; + int m_unsynchsafe(int in); + int m_toLittleEndian(int num); protected: - bool Init(Audio *audio, const String &path, bool preload) override; - void SetPosition_Internal(int32 pos) override; - int32 GetStreamPosition_Internal() override; - int32 GetStreamRate_Internal() override; - uint32 GetSampleRate_Internal() const override; - uint64 GetSampleCount_Internal() const override; - float *GetPCM_Internal() override; - int32 DecodeData_Internal() override; + bool Init(Audio* audio, const String& path, bool preload) override; + void SetPosition_Internal(int32 pos) override; + int32 GetStreamPosition_Internal() override; + int32 GetStreamRate_Internal() override; + uint32 GetSampleRate_Internal() const override; + uint64 GetSampleCount_Internal() const override; + float* GetPCM_Internal() override; + int32 DecodeData_Internal() override; public: - AudioStreamMp3() = default; - ~AudioStreamMp3(); - static Ref Create(Audio *audio, const String &path, bool preload); + AudioStreamMp3() = default; + ~AudioStreamMp3(); + static Ref Create(Audio* audio, const String& path, bool preload); }; diff --git a/Audio/src/AudioStreamOgg.cpp b/Audio/src/AudioStreamOgg.cpp index 88de06f31..36b7bf0ac 100644 --- a/Audio/src/AudioStreamOgg.cpp +++ b/Audio/src/AudioStreamOgg.cpp @@ -4,243 +4,243 @@ AudioStreamOgg::~AudioStreamOgg() { - Deregister(); - - for (size_t i = 0; i < m_numChannels; i++) - { - delete[] m_readBuffer[i]; - } - delete[] m_readBuffer; - - if (!m_preloaded) - { - ov_clear(&m_ovf); - } + Deregister(); + + for (size_t i = 0; i < m_numChannels; i++) + { + delete[] m_readBuffer[i]; + } + delete[] m_readBuffer; + + if (!m_preloaded) + { + ov_clear(&m_ovf); + } } -bool AudioStreamOgg::Init(Audio *audio, const String &path, bool preload) +bool AudioStreamOgg::Init(Audio* audio, const String& path, bool preload) { - if (!AudioStreamBase::Init(audio, path, preload)) - return false; - - int32 r = ov_open_callbacks(this, &m_ovf, 0, 0, { - (decltype(ov_callbacks::read_func)) & AudioStreamOgg::m_Read, - (decltype(ov_callbacks::seek_func)) & AudioStreamOgg::m_Seek, - nullptr, // Close - (decltype(ov_callbacks::tell_func)) & AudioStreamOgg::m_Tell, - }); - if (r != 0) - { - Logf("ov_open_callbacks failed with code %d", Logger::Severity::Error, r); - return false; - } - - vorbis_comment *comments = ov_comment(&m_ovf, 0); - vorbis_info *infoPtr = ov_info(&m_ovf, 0); - if (!infoPtr) - return false; - m_info = *infoPtr; - m_samplesTotal = ov_pcm_total(&m_ovf, 0); - - if (preload) - { - float **readBuffer; - int r; - int totalRead = 0; - while (true) - { - r = ov_read_float(&m_ovf, &readBuffer, 1024, 0); - if (r < 0) - continue; - if (r == 0) - break; - else - { - if (m_info.channels == 2) - { - for (int i = 0; i < r; i++) - { - m_pcm.Add(readBuffer[0][i]); - m_pcm.Add(readBuffer[1][i]); - } - } - else if (m_info.channels == 1) - { - for (int i = 0; i < r; i++) - { - m_pcm.Add(readBuffer[0][i]); - m_pcm.Add(readBuffer[0][i]); - } - } - totalRead += r; - } - } - m_samplesTotal = totalRead; - ov_clear(&m_ovf); - m_playPos = 0; - } - m_initSampling(m_info.rate); - return true; + if (!AudioStreamBase::Init(audio, path, preload)) + return false; + + int32 r = ov_open_callbacks(this, &m_ovf, 0, 0, { + (decltype(ov_callbacks::read_func))&AudioStreamOgg::m_Read, + (decltype(ov_callbacks::seek_func))&AudioStreamOgg::m_Seek, + nullptr, // Close + (decltype(ov_callbacks::tell_func))&AudioStreamOgg::m_Tell, + }); + if (r != 0) + { + Logf("ov_open_callbacks failed with code %d", Logger::Severity::Error, r); + return false; + } + + vorbis_comment* comments = ov_comment(&m_ovf, 0); + vorbis_info* infoPtr = ov_info(&m_ovf, 0); + if (!infoPtr) + return false; + m_info = *infoPtr; + m_samplesTotal = ov_pcm_total(&m_ovf, 0); + + if (preload) + { + float** readBuffer; + int r; + int totalRead = 0; + while (true) + { + r = ov_read_float(&m_ovf, &readBuffer, 1024, 0); + if (r < 0) + continue; + if (r == 0) + break; + else + { + if (m_info.channels == 2) + { + for (int i = 0; i < r; i++) + { + m_pcm.Add(readBuffer[0][i]); + m_pcm.Add(readBuffer[1][i]); + } + } + else if (m_info.channels == 1) + { + for (int i = 0; i < r; i++) + { + m_pcm.Add(readBuffer[0][i]); + m_pcm.Add(readBuffer[0][i]); + } + } + totalRead += r; + } + } + m_samplesTotal = totalRead; + ov_clear(&m_ovf); + m_playPos = 0; + } + m_initSampling(m_info.rate); + return true; } void AudioStreamOgg::SetPosition_Internal(int32 pos) { - if (m_preloaded) - { - if (pos < 0) - m_playPos = 0; - else - m_playPos = pos; - - return; - } - ov_pcm_seek(&m_ovf, pos); + if (m_preloaded) + { + if (pos < 0) + m_playPos = 0; + else + m_playPos = pos; + + return; + } + ov_pcm_seek(&m_ovf, pos); } int32 AudioStreamOgg::GetStreamPosition_Internal() { - if (m_preloaded) - return m_playPos; + if (m_preloaded) + return m_playPos; - return (int32)ov_pcm_tell(&m_ovf); + return (int32)ov_pcm_tell(&m_ovf); } int32 AudioStreamOgg::GetStreamRate_Internal() { - return (int32)m_info.rate; + return (int32)m_info.rate; } -float *AudioStreamOgg::GetPCM_Internal() +float* AudioStreamOgg::GetPCM_Internal() { - if (m_preloaded) - return &m_pcm.front(); - return nullptr; + if (m_preloaded) + return &m_pcm.front(); + return nullptr; } uint64 AudioStreamOgg::GetSampleCount_Internal() const { - if (m_preloaded) - { - return m_samplesTotal; - } - return 0; + if (m_preloaded) + { + return m_samplesTotal; + } + return 0; } uint32 AudioStreamOgg::GetSampleRate_Internal() const { - return m_info.rate; + return m_info.rate; } int32 AudioStreamOgg::DecodeData_Internal() { - if (m_preloaded) - { - uint32 samplesPerRead = 128; - int32 retVal = samplesPerRead; - bool earlyOut = false; - for (size_t i = 0; i < samplesPerRead; i++) - { - if (m_playPos < 0) - { - m_readBuffer[0][i] = 0; - m_readBuffer[1][i] = 0; - m_playPos++; - continue; - } - else if ((uint64)m_playPos >= m_samplesTotal) - { - m_currentBufferSize = m_bufferSize; - m_remainingBufferData = m_bufferSize; - m_readBuffer[0][i] = 0; - m_readBuffer[1][i] = 0; - if (!earlyOut) - { - retVal = i; - earlyOut = true; - } - continue; - } - m_readBuffer[0][i] = m_pcm[m_playPos * 2]; - m_readBuffer[1][i] = m_pcm[m_playPos * 2 + 1]; - m_playPos++; - } - m_currentBufferSize = samplesPerRead; - m_remainingBufferData = samplesPerRead; - return retVal; - } - - float **readBuffer; - int32 r = ov_read_float(&m_ovf, &readBuffer, m_bufferSize, 0); - if (r > 0) - { - if (m_info.channels == 1) - { - // Copy mono to read buffer - for (int32 i = 0; i < r; i++) - { - m_readBuffer[0][i] = readBuffer[0][i]; - m_readBuffer[1][i] = readBuffer[0][i]; - } - } - else - { - // Copy data to read buffer - for (int32 i = 0; i < r; i++) - { - m_readBuffer[0][i] = readBuffer[0][i]; - m_readBuffer[1][i] = readBuffer[1][i]; - } - } - m_currentBufferSize = r; - m_remainingBufferData = r; - return r; - } - else if (r == 0) - { - // EOF - m_playing = false; - return -1; - } - else - { - // Error - m_playing = false; - Logf("Ogg Stream error %d", Logger::Severity::Warning, r); - return -1; - } + if (m_preloaded) + { + uint32 samplesPerRead = 128; + int32 retVal = samplesPerRead; + bool earlyOut = false; + for (size_t i = 0; i < samplesPerRead; i++) + { + if (m_playPos < 0) + { + m_readBuffer[0][i] = 0; + m_readBuffer[1][i] = 0; + m_playPos++; + continue; + } + else if ((uint64)m_playPos >= m_samplesTotal) + { + m_currentBufferSize = m_bufferSize; + m_remainingBufferData = m_bufferSize; + m_readBuffer[0][i] = 0; + m_readBuffer[1][i] = 0; + if (!earlyOut) + { + retVal = i; + earlyOut = true; + } + continue; + } + m_readBuffer[0][i] = m_pcm[m_playPos * 2]; + m_readBuffer[1][i] = m_pcm[m_playPos * 2 + 1]; + m_playPos++; + } + m_currentBufferSize = samplesPerRead; + m_remainingBufferData = samplesPerRead; + return retVal; + } + + float** readBuffer; + int32 r = ov_read_float(&m_ovf, &readBuffer, m_bufferSize, 0); + if (r > 0) + { + if (m_info.channels == 1) + { + // Copy mono to read buffer + for (int32 i = 0; i < r; i++) + { + m_readBuffer[0][i] = readBuffer[0][i]; + m_readBuffer[1][i] = readBuffer[0][i]; + } + } + else + { + // Copy data to read buffer + for (int32 i = 0; i < r; i++) + { + m_readBuffer[0][i] = readBuffer[0][i]; + m_readBuffer[1][i] = readBuffer[1][i]; + } + } + m_currentBufferSize = r; + m_remainingBufferData = r; + return r; + } + else if (r == 0) + { + // EOF + m_playing = false; + return -1; + } + else + { + // Error + m_playing = false; + Logf("Ogg Stream error %d", Logger::Severity::Warning, r); + return -1; + } } -size_t AudioStreamOgg::m_Read(void *ptr, size_t size, size_t nmemb, AudioStreamOgg *self) +size_t AudioStreamOgg::m_Read(void* ptr, size_t size, size_t nmemb, AudioStreamOgg* self) { - return self->m_reader().Serialize(ptr, nmemb * size); + return self->m_reader().Serialize(ptr, nmemb * size); } -int AudioStreamOgg::m_Seek(AudioStreamOgg *self, int64 offset, int whence) +int AudioStreamOgg::m_Seek(AudioStreamOgg* self, int64 offset, int whence) { - if (whence == SEEK_SET) - self->m_reader().Seek((size_t)offset); - else if (whence == SEEK_CUR) - self->m_reader().Skip((size_t)offset); - else if (whence == SEEK_END) - self->m_reader().SeekReverse((size_t)offset); - else - assert(false); - return 0; + if (whence == SEEK_SET) + self->m_reader().Seek((size_t)offset); + else if (whence == SEEK_CUR) + self->m_reader().Skip((size_t)offset); + else if (whence == SEEK_END) + self->m_reader().SeekReverse((size_t)offset); + else + assert(false); + return 0; } -long AudioStreamOgg::m_Tell(AudioStreamOgg *self) +long AudioStreamOgg::m_Tell(AudioStreamOgg* self) { - return (long)self->m_reader().Tell(); + return (long)self->m_reader().Tell(); } -Ref AudioStreamOgg::Create(class Audio *audio, const String &path, bool preload) +Ref AudioStreamOgg::Create(class Audio* audio, const String& path, bool preload) { - AudioStreamOgg *impl = new AudioStreamOgg(); - if (!impl->Init(audio, path, preload)) - { - delete impl; - impl = nullptr; - return Ref(); - } - return Ref(impl); + AudioStreamOgg* impl = new AudioStreamOgg(); + if (!impl->Init(audio, path, preload)) + { + delete impl; + impl = nullptr; + return Ref(); + } + return Ref(impl); } diff --git a/Audio/src/AudioStreamOgg.hpp b/Audio/src/AudioStreamOgg.hpp index 3c5396b73..fe5ef24cc 100644 --- a/Audio/src/AudioStreamOgg.hpp +++ b/Audio/src/AudioStreamOgg.hpp @@ -6,27 +6,27 @@ class AudioStreamOgg : public AudioStreamBase { protected: - OggVorbis_File m_ovf; - vorbis_info m_info; - Vector m_pcm; - int64 m_playPos; + OggVorbis_File m_ovf; + vorbis_info m_info; + Vector m_pcm; + int64 m_playPos; - bool Init(Audio *audio, const String &path, bool preload) override; - void SetPosition_Internal(int32 pos) override; - int32 GetStreamPosition_Internal() override; - int32 GetStreamRate_Internal() override; - float *GetPCM_Internal() override; - uint32 GetSampleRate_Internal() const override; - uint64 GetSampleCount_Internal() const override; - int32 DecodeData_Internal() override; + bool Init(Audio* audio, const String& path, bool preload) override; + void SetPosition_Internal(int32 pos) override; + int32 GetStreamPosition_Internal() override; + int32 GetStreamRate_Internal() override; + float* GetPCM_Internal() override; + uint32 GetSampleRate_Internal() const override; + uint64 GetSampleCount_Internal() const override; + int32 DecodeData_Internal() override; private: - static size_t m_Read(void *ptr, size_t size, size_t nmemb, AudioStreamOgg *self); - static int m_Seek(AudioStreamOgg *self, int64 offset, int whence); - static long m_Tell(AudioStreamOgg *self); + static size_t m_Read(void* ptr, size_t size, size_t nmemb, AudioStreamOgg* self); + static int m_Seek(AudioStreamOgg* self, int64 offset, int whence); + static long m_Tell(AudioStreamOgg* self); public: - AudioStreamOgg() = default; - ~AudioStreamOgg(); - static Ref Create(class Audio *audio, const String &path, bool preload); + AudioStreamOgg() = default; + ~AudioStreamOgg(); + static Ref Create(class Audio* audio, const String& path, bool preload); }; diff --git a/Audio/src/AudioStreamPcm.cpp b/Audio/src/AudioStreamPcm.cpp index cacc867b1..6b9d6fa0c 100644 --- a/Audio/src/AudioStreamPcm.cpp +++ b/Audio/src/AudioStreamPcm.cpp @@ -1,7 +1,7 @@ #include "stdafx.h" #include "AudioStreamPcm.hpp" -bool AudioStreamPcm::Init(Audio *audio, const String &path, bool preload) +bool AudioStreamPcm::Init(Audio* audio, const String& path, bool preload) { AudioStreamBase::Init(audio, path, preload); m_initSampling(m_sampleRate); @@ -9,8 +9,8 @@ bool AudioStreamPcm::Init(Audio *audio, const String &path, bool preload) } void AudioStreamPcm::SetPosition_Internal(int32 pos) { - //negative pos is causing issues somewhere - //TODO: Investigate more + // negative pos is causing issues somewhere + // TODO: Investigate more m_playPos = Math::Max(0, pos); } int32 AudioStreamPcm::GetStreamPosition_Internal() @@ -21,7 +21,7 @@ int32 AudioStreamPcm::GetStreamRate_Internal() { return m_sampleRate; } -float *AudioStreamPcm::GetPCM_Internal() +float* AudioStreamPcm::GetPCM_Internal() { return m_pcm; } @@ -69,10 +69,10 @@ uint64 AudioStreamPcm::GetSampleCount_Internal() const { return m_samplesTotal; } -void AudioStreamPcm::PreRenderDSPs_Internal(Vector &DSPs) +void AudioStreamPcm::PreRenderDSPs_Internal(Vector& DSPs) { auto originalPlayPos = m_playPos; - for (auto &&dsp : DSPs) + for (auto&& dsp : DSPs) { m_playPos = ((uint64)dsp->startTime * (uint64)m_sampleRate) / 1000; m_samplePos = m_playPos; @@ -85,7 +85,7 @@ void AudioStreamPcm::PreRenderDSPs_Internal(Vector &DSPs) } uint32 numSamples = endSamplePos - m_playPos; - float *buffer = new float[numSamples * 2]; + float* buffer = new float[numSamples * 2]; memcpy(buffer, m_pcm + m_playPos * 2, numSamples * 2 * sizeof(float)); dsp->Process(buffer, numSamples); memcpy(m_pcm + m_playPos * 2, buffer, numSamples * 2 * sizeof(float)); @@ -105,11 +105,11 @@ AudioStreamPcm::~AudioStreamPcm() } } -Ref AudioStreamPcm::Create(class Audio *audio, const Ref &other) +Ref AudioStreamPcm::Create(class Audio* audio, const Ref& other) { - AudioStreamPcm *impl = new AudioStreamPcm(); + AudioStreamPcm* impl = new AudioStreamPcm(); impl->m_pcm = nullptr; - float *source = other->GetPCM(); + float* source = other->GetPCM(); uint64 sampleCount = other->GetPCMCount(); if (source == nullptr || sampleCount == 0) { @@ -126,4 +126,4 @@ Ref AudioStreamPcm::Create(class Audio *audio, const RefInit(audio, "", false); } return Ref(impl); -} \ No newline at end of file +} diff --git a/Audio/src/AudioStreamPcm.hpp b/Audio/src/AudioStreamPcm.hpp index c47b7d312..17b51f8a1 100644 --- a/Audio/src/AudioStreamPcm.hpp +++ b/Audio/src/AudioStreamPcm.hpp @@ -5,16 +5,16 @@ class AudioStreamPcm : public AudioStreamBase { protected: - float *m_pcm; + float* m_pcm; uint32 m_sampleRate; int64 m_playPos; - bool Init(Audio *audio, const String &path, bool preload) override; + bool Init(Audio* audio, const String& path, bool preload) override; void SetPosition_Internal(int32 pos) override; int32 GetStreamPosition_Internal() override; int32 GetStreamRate_Internal() override; - float *GetPCM_Internal() override; - void PreRenderDSPs_Internal(Vector &DSPs) override; + float* GetPCM_Internal() override; + void PreRenderDSPs_Internal(Vector& DSPs) override; uint32 GetSampleRate_Internal() const override; uint64 GetSampleCount_Internal() const override; int32 DecodeData_Internal() override; @@ -22,5 +22,5 @@ class AudioStreamPcm : public AudioStreamBase public: AudioStreamPcm() = default; ~AudioStreamPcm(); - static Ref Create(class Audio *audio, const Ref &other); -}; \ No newline at end of file + static Ref Create(class Audio* audio, const Ref& other); +}; diff --git a/Audio/src/AudioStreamWav.cpp b/Audio/src/AudioStreamWav.cpp index bdd494d88..02213d9f2 100644 --- a/Audio/src/AudioStreamWav.cpp +++ b/Audio/src/AudioStreamWav.cpp @@ -1,452 +1,452 @@ #include "stdafx.h" #include "AudioStreamWav.hpp" -uint32 AudioStreamWav::m_decode_ms_adpcm(const Buffer &encoded, Buffer *decoded, uint64 pos) +uint32 AudioStreamWav::m_decode_ms_adpcm(const Buffer& encoded, Buffer* decoded, uint64 pos) { - int16 *pcm = (int16 *)decoded->data(); - - int8 *src = ((int8 *)encoded.data()) + pos; - int8 blockPredictors[] = {0, 0}; - int32 ideltas[] = {0, 0}; - int32 sample1[] = {0, 0}; - int32 sample2[] = {0, 0}; - - blockPredictors[0] = *src++; - blockPredictors[1] = *src++; - assert(blockPredictors[0] >= 0 && blockPredictors[0] < 7); - assert(blockPredictors[1] >= 0 && blockPredictors[0] < 7); - - int16 *src_16 = (int16 *)src; - ideltas[0] = *src_16++; - ideltas[1] = *src_16++; - - sample1[0] = *src_16++; - sample1[1] = *src_16++; - - sample2[0] = *src_16++; - sample2[1] = *src_16++; - - *pcm++ = sample2[0]; - *pcm++ = sample2[1]; - *pcm++ = sample1[0]; - *pcm++ = sample1[1]; - - src = (int8 *)src_16; - uint32 decodedCount = 2; - - int AdaptationTable[] = { - 230, 230, 230, 230, 307, 409, 512, 614, - 768, 614, 512, 409, 307, 230, 230, 230}; - int AdaptCoeff1[] = {256, 512, 0, 192, 240, 460, 392}; - int AdaptCoeff2[] = {0, -256, 0, 64, 0, -208, -232}; - - // Decode the rest of the data in the block - int remainingInBlock = m_format.nBlockAlign - 14; - while (remainingInBlock > 0) - { - int8 nibbleData = *src++; - - int8 nibbles[] = {0, 0}; - nibbles[0] = nibbleData >> 4; - nibbles[0] &= 0x0F; - nibbles[1] = nibbleData & 0x0F; - - int16 predictors[] = {0, 0}; - for (size_t i = 0; i < 2; i++) - { - - predictors[i] = ((sample1[i] * AdaptCoeff1[blockPredictors[i]]) + (sample2[i] * AdaptCoeff2[blockPredictors[i]])) / 256; - if (nibbles[i] & 0x08) - predictors[i] += (nibbles[i] - 0x10) * ideltas[i]; - else - predictors[i] += nibbles[i] * ideltas[i]; - *pcm++ = predictors[i]; - sample2[i] = sample1[i]; - sample1[i] = predictors[i]; - ideltas[i] = (AdaptationTable[nibbles[i]] * ideltas[i]) / 256; - if (ideltas[i] < 16) - ideltas[i] = 16; - } - decodedCount++; - remainingInBlock--; - } - return decodedCount; + int16* pcm = (int16*)decoded->data(); + + int8* src = ((int8*)encoded.data()) + pos; + int8 blockPredictors[] = { 0, 0 }; + int32 ideltas[] = { 0, 0 }; + int32 sample1[] = { 0, 0 }; + int32 sample2[] = { 0, 0 }; + + blockPredictors[0] = *src++; + blockPredictors[1] = *src++; + assert(blockPredictors[0] >= 0 && blockPredictors[0] < 7); + assert(blockPredictors[1] >= 0 && blockPredictors[0] < 7); + + int16* src_16 = (int16*)src; + ideltas[0] = *src_16++; + ideltas[1] = *src_16++; + + sample1[0] = *src_16++; + sample1[1] = *src_16++; + + sample2[0] = *src_16++; + sample2[1] = *src_16++; + + *pcm++ = sample2[0]; + *pcm++ = sample2[1]; + *pcm++ = sample1[0]; + *pcm++ = sample1[1]; + + src = (int8*)src_16; + uint32 decodedCount = 2; + + int AdaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 }; + int AdaptCoeff1[] = { 256, 512, 0, 192, 240, 460, 392 }; + int AdaptCoeff2[] = { 0, -256, 0, 64, 0, -208, -232 }; + + // Decode the rest of the data in the block + int remainingInBlock = m_format.nBlockAlign - 14; + while (remainingInBlock > 0) + { + int8 nibbleData = *src++; + + int8 nibbles[] = { 0, 0 }; + nibbles[0] = nibbleData >> 4; + nibbles[0] &= 0x0F; + nibbles[1] = nibbleData & 0x0F; + + int16 predictors[] = { 0, 0 }; + for (size_t i = 0; i < 2; i++) + { + + predictors[i] = ((sample1[i] * AdaptCoeff1[blockPredictors[i]]) + (sample2[i] * AdaptCoeff2[blockPredictors[i]])) / 256; + if (nibbles[i] & 0x08) + predictors[i] += (nibbles[i] - 0x10) * ideltas[i]; + else + predictors[i] += nibbles[i] * ideltas[i]; + *pcm++ = predictors[i]; + sample2[i] = sample1[i]; + sample1[i] = predictors[i]; + ideltas[i] = (AdaptationTable[nibbles[i]] * ideltas[i]) / 256; + if (ideltas[i] < 16) + ideltas[i] = 16; + } + decodedCount++; + remainingInBlock--; + } + return decodedCount; } AudioStreamWav::~AudioStreamWav() { - Deregister(); + Deregister(); - for (size_t i = 0; i < m_numChannels; i++) - { - delete[] m_readBuffer[i]; - } - delete[] m_readBuffer; + for (size_t i = 0; i < m_numChannels; i++) + { + delete[] m_readBuffer[i]; + } + delete[] m_readBuffer; } -bool AudioStreamWav::Init(Audio *audio, const String &path, bool preload) +bool AudioStreamWav::Init(Audio* audio, const String& path, bool preload) { - if (!AudioStreamBase::Init(audio, path, preload)) // Always preload for now - return false; - - if (preload) - { - WavHeader riff; - m_memoryReader << riff; - if (riff != "RIFF") - return false; - - char riffType[4]; - m_memoryReader.SerializeObject(riffType); - if (strncmp(riffType, "WAVE", 4) != 0) - return false; - - while (m_memoryReader.Tell() < m_memoryReader.GetSize()) - { - WavHeader chunkHdr; - m_memoryReader << chunkHdr; - if (chunkHdr == "fmt ") - { - m_memoryReader << m_format; - String format = "Unknown"; - if (m_format.nFormat == 1) - format = "PCM"; - else if (m_format.nFormat == 2) - format = "MS_ADPCM"; - - Logf("Sample format: %s", Logger::Severity::Info, format); - Logf("Channels: %d", Logger::Severity::Info, m_format.nChannels); - Logf("Sample rate: %d", Logger::Severity::Info, m_format.nSampleRate); - Logf("Bps: %d", Logger::Severity::Info, m_format.nBitsPerSample); - if (m_format.nFormat == 2) - { - uint16 cbSize; - m_memoryReader << cbSize; - m_memoryReader.Skip(cbSize); - } - } - else if (chunkHdr == "fact") - { - uint32 fh; - m_memoryReader << fh; - m_samplesTotal = fh; - } - else if (chunkHdr == "data") // data Chunk - { - // validate header - if (m_format.nFormat != 1 && m_format.nFormat != 2) - return false; - if (m_format.nChannels > 2 || m_format.nChannels == 0) - return false; - if (m_format.nBitsPerSample != 16 && m_format.nBitsPerSample != 4) - return false; - - // Read data - if (m_format.nFormat == 1) - { - m_samplesTotal = (chunkHdr.nLength / sizeof(short)) / m_format.nChannels; - m_Internaldata.resize(chunkHdr.nLength); - m_memoryReader.Serialize(m_Internaldata.data(), chunkHdr.nLength); - } - else if (m_format.nFormat == 2) - { - m_samplesTotal = chunkHdr.nLength; - m_Internaldata.resize(m_samplesTotal); - m_memoryReader.Serialize(m_Internaldata.data(), chunkHdr.nLength); - } - - //Decode to float - uint64 pos = 0; - m_pcm.resize(2 * m_samplesTotal * sizeof(float)); - if (m_format.nFormat == 1) - { - int16 *src = ((int16 *)m_Internaldata.data()); - if (m_format.nChannels == 2) - { - while (pos < m_samplesTotal) - { - m_pcm[pos * 2] = (float)src[pos * 2] / (float)0x7FFF; - m_pcm[pos * 2 + 1] = (float)src[pos * 2 + 1] / (float)0x7FFF; - pos++; - } - } - else if (m_format.nChannels == 1) - { - while (pos < m_samplesTotal) - { - m_pcm[pos * 2] = (float)src[pos] / (float)0x7FFF; - m_pcm[pos * 2 + 1] = (float)src[pos] / (float)0x7FFF; - pos++; - } - } - } - else if (m_format.nFormat == 2) - { - int decoded = 0; - Buffer decodedBuffer; - decodedBuffer.resize(m_format.nBlockAlign * m_format.nChannels * sizeof(short)); - - while (pos < m_samplesTotal) - { - uint32 newDecoded = m_decode_ms_adpcm(m_Internaldata, &decodedBuffer, pos); - pos += m_format.nBlockAlign; - int16 *src = ((int16 *)decodedBuffer.data()); - for (uint32 i = 0; i < newDecoded; i++) - { - m_pcm[(2 * decoded) + (i * 2)] = (float)src[i * 2] / (float)0x7FFF; - m_pcm[(2 * decoded) + (i * 2) + 1] = (float)src[i * 2 + 1] / (float)0x7FFF; - } - decoded += newDecoded; - } - - m_samplesTotal = decoded; - } - } - else - { - m_memoryReader.Skip(chunkHdr.nLength); - } - } - m_Internaldata.clear(); - } - else - { - WavHeader riff; - m_fileReader << riff; - if (riff != "RIFF") - return false; - - char riffType[4]; - m_fileReader.SerializeObject(riffType); - if (strncmp(riffType, "WAVE", 4) != 0) - return false; - - while (m_fileReader.Tell() < m_fileReader.GetSize()) - { - WavHeader chunkHdr; - m_fileReader << chunkHdr; - if (chunkHdr == "fmt ") - { - m_fileReader << m_format; - String format = "Unknown"; - if (m_format.nFormat == 1) - format = "PCM"; - else if (m_format.nFormat == 2) - format = "MS_ADPCM"; - - Logf("Sample format: %s", Logger::Severity::Info, format); - Logf("Channels: %d", Logger::Severity::Info, m_format.nChannels); - Logf("Sample rate: %d", Logger::Severity::Info, m_format.nSampleRate); - Logf("Bps: %d", Logger::Severity::Info, m_format.nBitsPerSample); - if (m_format.nFormat == 2) - { - uint16 cbSize; - m_fileReader << cbSize; - m_fileReader.Skip(cbSize); - } - } - else if (chunkHdr == "fact") - { - uint32 fh; - m_fileReader << fh; - m_samplesTotal = fh; - } - else if (chunkHdr == "data") // data Chunk - { - // validate header - if (m_format.nFormat != 1 && m_format.nFormat != 2) - return false; - if (m_format.nChannels > 2 || m_format.nChannels == 0) - return false; - if (m_format.nBitsPerSample != 16 && m_format.nBitsPerSample != 4) - return false; - - if (m_format.nFormat == 1) - { - m_samplesTotal = chunkHdr.nLength / sizeof(short); - } - else if (m_format.nFormat == 2) - { - m_samplesTotal = chunkHdr.nLength * 2; - } - m_dataPosition = m_fileReader.Tell(); - } - else - { - m_fileReader.Skip(chunkHdr.nLength); - } - } - } - - m_initSampling(m_format.nSampleRate); - m_playbackPointer = 0; - return true; + if (!AudioStreamBase::Init(audio, path, preload)) // Always preload for now + return false; + + if (preload) + { + WavHeader riff; + m_memoryReader << riff; + if (riff != "RIFF") + return false; + + char riffType[4]; + m_memoryReader.SerializeObject(riffType); + if (strncmp(riffType, "WAVE", 4) != 0) + return false; + + while (m_memoryReader.Tell() < m_memoryReader.GetSize()) + { + WavHeader chunkHdr; + m_memoryReader << chunkHdr; + if (chunkHdr == "fmt ") + { + m_memoryReader << m_format; + String format = "Unknown"; + if (m_format.nFormat == 1) + format = "PCM"; + else if (m_format.nFormat == 2) + format = "MS_ADPCM"; + + Logf("Sample format: %s", Logger::Severity::Info, format); + Logf("Channels: %d", Logger::Severity::Info, m_format.nChannels); + Logf("Sample rate: %d", Logger::Severity::Info, m_format.nSampleRate); + Logf("Bps: %d", Logger::Severity::Info, m_format.nBitsPerSample); + if (m_format.nFormat == 2) + { + uint16 cbSize; + m_memoryReader << cbSize; + m_memoryReader.Skip(cbSize); + } + } + else if (chunkHdr == "fact") + { + uint32 fh; + m_memoryReader << fh; + m_samplesTotal = fh; + } + else if (chunkHdr == "data") // data Chunk + { + // validate header + if (m_format.nFormat != 1 && m_format.nFormat != 2) + return false; + if (m_format.nChannels > 2 || m_format.nChannels == 0) + return false; + if (m_format.nBitsPerSample != 16 && m_format.nBitsPerSample != 4) + return false; + + // Read data + if (m_format.nFormat == 1) + { + m_samplesTotal = (chunkHdr.nLength / sizeof(short)) / m_format.nChannels; + m_Internaldata.resize(chunkHdr.nLength); + m_memoryReader.Serialize(m_Internaldata.data(), chunkHdr.nLength); + } + else if (m_format.nFormat == 2) + { + m_samplesTotal = chunkHdr.nLength; + m_Internaldata.resize(m_samplesTotal); + m_memoryReader.Serialize(m_Internaldata.data(), chunkHdr.nLength); + } + + // Decode to float + uint64 pos = 0; + m_pcm.resize(2 * m_samplesTotal * sizeof(float)); + if (m_format.nFormat == 1) + { + int16* src = ((int16*)m_Internaldata.data()); + if (m_format.nChannels == 2) + { + while (pos < m_samplesTotal) + { + m_pcm[pos * 2] = (float)src[pos * 2] / (float)0x7FFF; + m_pcm[pos * 2 + 1] = (float)src[pos * 2 + 1] / (float)0x7FFF; + pos++; + } + } + else if (m_format.nChannels == 1) + { + while (pos < m_samplesTotal) + { + m_pcm[pos * 2] = (float)src[pos] / (float)0x7FFF; + m_pcm[pos * 2 + 1] = (float)src[pos] / (float)0x7FFF; + pos++; + } + } + } + else if (m_format.nFormat == 2) + { + int decoded = 0; + Buffer decodedBuffer; + decodedBuffer.resize(m_format.nBlockAlign * m_format.nChannels * sizeof(short)); + + while (pos < m_samplesTotal) + { + uint32 newDecoded = m_decode_ms_adpcm(m_Internaldata, &decodedBuffer, pos); + pos += m_format.nBlockAlign; + int16* src = ((int16*)decodedBuffer.data()); + for (uint32 i = 0; i < newDecoded; i++) + { + m_pcm[(2 * decoded) + (i * 2)] = (float)src[i * 2] / (float)0x7FFF; + m_pcm[(2 * decoded) + (i * 2) + 1] = (float)src[i * 2 + 1] / (float)0x7FFF; + } + decoded += newDecoded; + } + + m_samplesTotal = decoded; + } + } + else + { + m_memoryReader.Skip(chunkHdr.nLength); + } + } + m_Internaldata.clear(); + } + else + { + WavHeader riff; + m_fileReader << riff; + if (riff != "RIFF") + return false; + + char riffType[4]; + m_fileReader.SerializeObject(riffType); + if (strncmp(riffType, "WAVE", 4) != 0) + return false; + + while (m_fileReader.Tell() < m_fileReader.GetSize()) + { + WavHeader chunkHdr; + m_fileReader << chunkHdr; + if (chunkHdr == "fmt ") + { + m_fileReader << m_format; + String format = "Unknown"; + if (m_format.nFormat == 1) + format = "PCM"; + else if (m_format.nFormat == 2) + format = "MS_ADPCM"; + + Logf("Sample format: %s", Logger::Severity::Info, format); + Logf("Channels: %d", Logger::Severity::Info, m_format.nChannels); + Logf("Sample rate: %d", Logger::Severity::Info, m_format.nSampleRate); + Logf("Bps: %d", Logger::Severity::Info, m_format.nBitsPerSample); + if (m_format.nFormat == 2) + { + uint16 cbSize; + m_fileReader << cbSize; + m_fileReader.Skip(cbSize); + } + } + else if (chunkHdr == "fact") + { + uint32 fh; + m_fileReader << fh; + m_samplesTotal = fh; + } + else if (chunkHdr == "data") // data Chunk + { + // validate header + if (m_format.nFormat != 1 && m_format.nFormat != 2) + return false; + if (m_format.nChannels > 2 || m_format.nChannels == 0) + return false; + if (m_format.nBitsPerSample != 16 && m_format.nBitsPerSample != 4) + return false; + + if (m_format.nFormat == 1) + { + m_samplesTotal = chunkHdr.nLength / sizeof(short); + } + else if (m_format.nFormat == 2) + { + m_samplesTotal = chunkHdr.nLength * 2; + } + m_dataPosition = m_fileReader.Tell(); + } + else + { + m_fileReader.Skip(chunkHdr.nLength); + } + } + } + + m_initSampling(m_format.nSampleRate); + m_playbackPointer = 0; + return true; } int32 AudioStreamWav::GetStreamPosition_Internal() { - return m_playbackPointer; + return m_playbackPointer; } int32 AudioStreamWav::GetStreamRate_Internal() { - if (m_format.nFormat == 2 && !m_preloaded) - return m_format.nByteRate; - else - return m_format.nSampleRate; + if (m_format.nFormat == 2 && !m_preloaded) + return m_format.nByteRate; + else + return m_format.nSampleRate; } void AudioStreamWav::SetPosition_Internal(int32 pos) { - if (pos < 0) - m_playbackPointer = 0; - else - m_playbackPointer = pos; - - if (!m_preloaded) - { - int filePos = 0; - if (m_format.nFormat == 1) - { - filePos = m_dataPosition + pos * sizeof(short); - } - else if (m_format.nFormat == 2) - { - filePos = m_dataPosition + pos - (pos % m_format.nBlockAlign); - //filePos -= filePos % m_format.nBlockAlign; - } - m_fileReader.Seek(filePos); - } + if (pos < 0) + m_playbackPointer = 0; + else + m_playbackPointer = pos; + + if (!m_preloaded) + { + int filePos = 0; + if (m_format.nFormat == 1) + { + filePos = m_dataPosition + pos * sizeof(short); + } + else if (m_format.nFormat == 2) + { + filePos = m_dataPosition + pos - (pos % m_format.nBlockAlign); + // filePos -= filePos % m_format.nBlockAlign; + } + m_fileReader.Seek(filePos); + } } int32 AudioStreamWav::DecodeData_Internal() { - if (!m_preloaded) - { - if (m_format.nFormat == 1) - { - uint32 samplesPerRead = 128; - Buffer readData; - readData.resize(samplesPerRead * m_format.nChannels * sizeof(short)); - m_fileReader.Serialize(readData.data(), samplesPerRead * m_format.nChannels * sizeof(short)); - int16 *src = ((int16 *)readData.data()); - if (m_format.nChannels == 2) - { - for (uint32 i = 0; i < samplesPerRead; i++) - { - if (m_playbackPointer >= (int64)m_samplesTotal) - { - m_currentBufferSize = samplesPerRead; - m_remainingBufferData = samplesPerRead; - return i; - } - - m_readBuffer[0][i] = (float)src[i * 2] / (float)0x7FFF; - m_readBuffer[1][i] = (float)src[i * 2 + 1] / (float)0x7FFF; - m_playbackPointer++; - } - } - else - { - // Mix mono sample - for (uint32 i = 0; i < samplesPerRead; i++) - { - if (m_playbackPointer >= (int64)m_samplesTotal) - { - m_currentBufferSize = samplesPerRead; - m_remainingBufferData = samplesPerRead; - return i; - } - m_readBuffer[0][i] = (float)src[0] / (float)0x7FFF; - m_readBuffer[1][i] = (float)src[0] / (float)0x7FFF; - m_playbackPointer++; - } - } - m_currentBufferSize = samplesPerRead; - m_remainingBufferData = samplesPerRead; - return samplesPerRead; - } - else if (m_format.nFormat == 2) - { - Buffer decoded; - decoded.resize(m_format.nBlockAlign * m_format.nChannels * sizeof(short)); - Buffer readData; - readData.resize(m_format.nBlockAlign); - int amountRead = m_fileReader.Serialize(readData.data(), m_format.nBlockAlign); - if (amountRead < m_format.nBlockAlign) - { - m_playing = false; - return 0; - } - uint32 decodedCount = m_decode_ms_adpcm(readData, &decoded, 0); - uint32 samplesInserted = 0; - uint64 bufferOffset = 0; - for (uint32 i = 0; i < decodedCount; i++) - { - int16 *src = ((int16 *)decoded.data()) + (i * 2); - m_readBuffer[0][i] = (float)src[0] / (float)0x7FFF; - m_readBuffer[1][i] = (float)src[1] / (float)0x7FFF; - } - - m_playbackPointer += m_format.nBlockAlign; - - m_currentBufferSize = decodedCount; - m_remainingBufferData = decodedCount; - return decodedCount; - } - } - else - { - uint32 samplesPerRead = 128; - for (uint32 i = 0; i < samplesPerRead; i++) - { - if (m_playbackPointer < 0) - { - m_readBuffer[0][i] = 0; - m_readBuffer[1][i] = 0; - m_playbackPointer++; - continue; - } - else if (m_playbackPointer >= (int64)m_samplesTotal) - { - m_currentBufferSize = samplesPerRead; - m_remainingBufferData = samplesPerRead; - return i; - } - - m_readBuffer[0][i] = m_pcm[m_playbackPointer * 2]; - m_readBuffer[1][i] = m_pcm[m_playbackPointer * 2 + 1]; - m_playbackPointer++; - } - m_currentBufferSize = samplesPerRead; - m_remainingBufferData = samplesPerRead; - return samplesPerRead; - } - return 0; + if (!m_preloaded) + { + if (m_format.nFormat == 1) + { + uint32 samplesPerRead = 128; + Buffer readData; + readData.resize(samplesPerRead * m_format.nChannels * sizeof(short)); + m_fileReader.Serialize(readData.data(), samplesPerRead * m_format.nChannels * sizeof(short)); + int16* src = ((int16*)readData.data()); + if (m_format.nChannels == 2) + { + for (uint32 i = 0; i < samplesPerRead; i++) + { + if (m_playbackPointer >= (int64)m_samplesTotal) + { + m_currentBufferSize = samplesPerRead; + m_remainingBufferData = samplesPerRead; + return i; + } + + m_readBuffer[0][i] = (float)src[i * 2] / (float)0x7FFF; + m_readBuffer[1][i] = (float)src[i * 2 + 1] / (float)0x7FFF; + m_playbackPointer++; + } + } + else + { + // Mix mono sample + for (uint32 i = 0; i < samplesPerRead; i++) + { + if (m_playbackPointer >= (int64)m_samplesTotal) + { + m_currentBufferSize = samplesPerRead; + m_remainingBufferData = samplesPerRead; + return i; + } + m_readBuffer[0][i] = (float)src[0] / (float)0x7FFF; + m_readBuffer[1][i] = (float)src[0] / (float)0x7FFF; + m_playbackPointer++; + } + } + m_currentBufferSize = samplesPerRead; + m_remainingBufferData = samplesPerRead; + return samplesPerRead; + } + else if (m_format.nFormat == 2) + { + Buffer decoded; + decoded.resize(m_format.nBlockAlign * m_format.nChannels * sizeof(short)); + Buffer readData; + readData.resize(m_format.nBlockAlign); + int amountRead = m_fileReader.Serialize(readData.data(), m_format.nBlockAlign); + if (amountRead < m_format.nBlockAlign) + { + m_playing = false; + return 0; + } + uint32 decodedCount = m_decode_ms_adpcm(readData, &decoded, 0); + uint32 samplesInserted = 0; + uint64 bufferOffset = 0; + for (uint32 i = 0; i < decodedCount; i++) + { + int16* src = ((int16*)decoded.data()) + (i * 2); + m_readBuffer[0][i] = (float)src[0] / (float)0x7FFF; + m_readBuffer[1][i] = (float)src[1] / (float)0x7FFF; + } + + m_playbackPointer += m_format.nBlockAlign; + + m_currentBufferSize = decodedCount; + m_remainingBufferData = decodedCount; + return decodedCount; + } + } + else + { + uint32 samplesPerRead = 128; + for (uint32 i = 0; i < samplesPerRead; i++) + { + if (m_playbackPointer < 0) + { + m_readBuffer[0][i] = 0; + m_readBuffer[1][i] = 0; + m_playbackPointer++; + continue; + } + else if (m_playbackPointer >= (int64)m_samplesTotal) + { + m_currentBufferSize = samplesPerRead; + m_remainingBufferData = samplesPerRead; + return i; + } + + m_readBuffer[0][i] = m_pcm[m_playbackPointer * 2]; + m_readBuffer[1][i] = m_pcm[m_playbackPointer * 2 + 1]; + m_playbackPointer++; + } + m_currentBufferSize = samplesPerRead; + m_remainingBufferData = samplesPerRead; + return samplesPerRead; + } + return 0; } -float *AudioStreamWav::GetPCM_Internal() +float* AudioStreamWav::GetPCM_Internal() { - if (m_preloaded) - return &m_pcm.front(); + if (m_preloaded) + return &m_pcm.front(); - return nullptr; + return nullptr; } uint64 AudioStreamWav::GetSampleCount_Internal() const { - if (m_preloaded) - { - return m_samplesTotal; - } - return 0; + if (m_preloaded) + { + return m_samplesTotal; + } + return 0; } uint32 AudioStreamWav::GetSampleRate_Internal() const { - return m_format.nSampleRate; + return m_format.nSampleRate; } -Ref AudioStreamWav::Create(class Audio *audio, const String &path, bool preload) +Ref AudioStreamWav::Create(class Audio* audio, const String& path, bool preload) { - AudioStreamWav *impl = new AudioStreamWav(); - if (!impl->Init(audio, path, preload)) - { - delete impl; - impl = nullptr; - } - return Ref(impl); + AudioStreamWav* impl = new AudioStreamWav(); + if (!impl->Init(audio, path, preload)) + { + delete impl; + impl = nullptr; + } + return Ref(impl); } diff --git a/Audio/src/AudioStreamWav.hpp b/Audio/src/AudioStreamWav.hpp index a0ee24f5b..75ed61386 100644 --- a/Audio/src/AudioStreamWav.hpp +++ b/Audio/src/AudioStreamWav.hpp @@ -4,49 +4,49 @@ class AudioStreamWav : public AudioStreamBase { private: - struct WavHeader - { - char id[4]; - uint32 nLength; + struct WavHeader + { + char id[4]; + uint32 nLength; - bool operator==(const char *rhs) const - { - return strncmp(id, rhs, 4) == 0; - } - bool operator!=(const char *rhs) const - { - return !(*this == rhs); - } - }; + bool operator==(const char* rhs) const + { + return strncmp(id, rhs, 4) == 0; + } + bool operator!=(const char* rhs) const + { + return !(*this == rhs); + } + }; - struct WavFormat - { - uint16 nFormat; - uint16 nChannels; - uint32 nSampleRate; - uint32 nByteRate; - uint16 nBlockAlign; - uint16 nBitsPerSample; - }; - Buffer m_Internaldata; - WavFormat m_format; - Vector m_pcm; - int64 m_playbackPointer = 0; - uint64 m_dataPosition = 0; - uint32 m_decode_ms_adpcm(const Buffer &encoded, Buffer *decoded, uint64 pos); + struct WavFormat + { + uint16 nFormat; + uint16 nChannels; + uint32 nSampleRate; + uint32 nByteRate; + uint16 nBlockAlign; + uint16 nBitsPerSample; + }; + Buffer m_Internaldata; + WavFormat m_format; + Vector m_pcm; + int64 m_playbackPointer = 0; + uint64 m_dataPosition = 0; + uint32 m_decode_ms_adpcm(const Buffer& encoded, Buffer* decoded, uint64 pos); protected: - bool Init(Audio *audio, const String &path, bool preload) override; - int32 GetStreamPosition_Internal() override; - int32 GetStreamRate_Internal() override; - void SetPosition_Internal(int32 pos) override; - int32 DecodeData_Internal() override; - float *GetPCM_Internal() override; - uint32 GetSampleRate_Internal() const override; - uint64 GetSampleCount_Internal() const override; + bool Init(Audio* audio, const String& path, bool preload) override; + int32 GetStreamPosition_Internal() override; + int32 GetStreamRate_Internal() override; + void SetPosition_Internal(int32 pos) override; + int32 DecodeData_Internal() override; + float* GetPCM_Internal() override; + uint32 GetSampleRate_Internal() const override; + uint64 GetSampleCount_Internal() const override; public: - AudioStreamWav() = default; - ~AudioStreamWav(); - static Ref Create(class Audio *audio, const String &path, bool preload); + AudioStreamWav() = default; + ~AudioStreamWav(); + static Ref Create(class Audio* audio, const String& path, bool preload); }; diff --git a/Audio/src/DSP.cpp b/Audio/src/DSP.cpp index 5c72c5ad1..2fc2a26fe 100644 --- a/Audio/src/DSP.cpp +++ b/Audio/src/DSP.cpp @@ -5,732 +5,739 @@ void BQF::SetLowPass(float q, float freq, float sampleRate) { - // Limit q - q = Math::Max(q, 0.01f); + // Limit q + q = Math::Max(q, 0.01f); - // Sampling frequency - double w0 = (2 * Math::pi * freq) / sampleRate; - double cw0 = cos(w0); - float alpha = (float)(sin(w0) / (2 * q)); + // Sampling frequency + double w0 = (2 * Math::pi * freq) / sampleRate; + double cw0 = cos(w0); + float alpha = (float)(sin(w0) / (2 * q)); - b0 = (float)((1 - cw0) / 2); - b1 = (float)(1 - cw0); - b2 = (float)((1 - cw0) / 2); - a0 = 1 + alpha; - a1 = (float)(-2 * cw0); - a2 = 1 - alpha; + b0 = (float)((1 - cw0) / 2); + b1 = (float)(1 - cw0); + b2 = (float)((1 - cw0) / 2); + a0 = 1 + alpha; + a1 = (float)(-2 * cw0); + a2 = 1 - alpha; } void BQF::SetHighPass(float q, float freq, float sampleRate) { - // Limit q - q = Math::Max(q, 0.01f); + // Limit q + q = Math::Max(q, 0.01f); - assert(freq < sampleRate); - double w0 = (2 * Math::pi * freq) / sampleRate; - double cw0 = cos(w0); - float alpha = (float)(sin(w0) / (2 * q)); + assert(freq < sampleRate); + double w0 = (2 * Math::pi * freq) / sampleRate; + double cw0 = cos(w0); + float alpha = (float)(sin(w0) / (2 * q)); - b0 = (float)((1 + cw0) / 2); - b1 = (float)-(1 + cw0); - b2 = float((1 + cw0) / 2); - a0 = 1 + alpha; - a1 = (float)(-2 * cw0); - a2 = 1 - alpha; + b0 = (float)((1 + cw0) / 2); + b1 = (float)-(1 + cw0); + b2 = float((1 + cw0) / 2); + a0 = 1 + alpha; + a1 = (float)(-2 * cw0); + a2 = 1 - alpha; } void BQF::SetAllPass(float q, float freq, float sampleRate) { - // Limit q - q = Math::Max(q, 0.01f); + // Limit q + q = Math::Max(q, 0.01f); - double w0 = (2 * Math::pi * freq) / sampleRate; - double cw0 = cos(w0); - float alpha = (float)(sin(w0) / (2 * q)); + double w0 = (2 * Math::pi * freq) / sampleRate; + double cw0 = cos(w0); + float alpha = (float)(sin(w0) / (2 * q)); - b0 = 1 - alpha; - b1 = (float)(-2 * cw0); - b2 = 1 + alpha; - a0 = 1 + alpha; - a1 = (float)(-2 * cw0); - a2 = 1 - alpha; + b0 = 1 - alpha; + b1 = (float)(-2 * cw0); + b2 = 1 + alpha; + a0 = 1 + alpha; + a1 = (float)(-2 * cw0); + a2 = 1 - alpha; } void BQF::SetPeaking(float q, float freq, float gain, float sampleRate) { - // Limit q - q = Math::Max(q, 0.01f); + // Limit q + q = Math::Max(q, 0.01f); - double w0 = (2 * Math::pi * freq) / sampleRate; - double cw0 = cos(w0); - float alpha = (float)(sin(w0) / (2 * q)); - double A = pow(10, (gain / 40)); + double w0 = (2 * Math::pi * freq) / sampleRate; + double cw0 = cos(w0); + float alpha = (float)(sin(w0) / (2 * q)); + double A = pow(10, (gain / 40)); - b0 = 1 + (float)(alpha * A); - b1 = -2 * (float)cw0; - b2 = 1 - (float)(alpha * A); - a0 = 1 + (float)(alpha / A); - a1 = -2 * (float)cw0; - a2 = 1 - (float)(alpha / A); + b0 = 1 + (float)(alpha * A); + b1 = -2 * (float)cw0; + b2 = 1 - (float)(alpha * A); + a0 = 1 + (float)(alpha / A); + a1 = -2 * (float)cw0; + a2 = 1 - (float)(alpha / A); } void BQF::SetHighShelf(float q, float freq, float gain, float sampleRate) { - // Limit q - q = Math::Max(q, 0.01f); + // Limit q + q = Math::Max(q, 0.01f); - double w0 = (2 * Math::pi * freq) / sampleRate; - double cw0 = cos(w0); - float alpha = (float)(sin(w0) / (2 * q)); - double A = pow(10, (gain / 40)); - double _2sqrtAalpha = 2 * sqrt(A) * alpha; + double w0 = (2 * Math::pi * freq) / sampleRate; + double cw0 = cos(w0); + float alpha = (float)(sin(w0) / (2 * q)); + double A = pow(10, (gain / 40)); + double _2sqrtAalpha = 2 * sqrt(A) * alpha; - b0 = A * ((A+1) + (A-1)*cw0 + _2sqrtAalpha); - b1 = -2 * A * ((A-1) + (A+1)*cw0); - b2 = A * ((A+1) + (A-1)*cw0 - _2sqrtAalpha); - a0 = (A+1) - (A-1)*cw0 + _2sqrtAalpha; - a1 = 2 * ((A-1) - (A+1)*cw0); - a2 = (A+1) - (A-1)*cw0 - _2sqrtAalpha; + b0 = A * ((A + 1) + (A - 1) * cw0 + _2sqrtAalpha); + b1 = -2 * A * ((A - 1) + (A + 1) * cw0); + b2 = A * ((A + 1) + (A - 1) * cw0 - _2sqrtAalpha); + a0 = (A + 1) - (A - 1) * cw0 + _2sqrtAalpha; + a1 = 2 * ((A - 1) - (A + 1) * cw0); + a2 = (A + 1) - (A - 1) * cw0 - _2sqrtAalpha; } float BQF::Update(float in) { - float filtered = - (b0 / a0) * in + - (b1 / a0) * zb[0] + - (b2 / a0) * zb[1] - - (a1 / a0) * za[0] - - (a2 / a0) * za[1]; + float filtered = + (b0 / a0) * in + + (b1 / a0) * zb[0] + + (b2 / a0) * zb[1] - + (a1 / a0) * za[0] - + (a2 / a0) * za[1]; - // Shift delay buffers - zb[1] = zb[0]; - zb[0] = in; + // Shift delay buffers + zb[1] = zb[0]; + zb[0] = in; - // Feedback the calculated value into the IIR delay buffers - za[1] = za[0]; - za[0] = filtered; + // Feedback the calculated value into the IIR delay buffers + za[1] = za[0]; + za[0] = filtered; - return filtered; + return filtered; } -void PanDSP::Process(float *out, uint32 numSamples) +void PanDSP::Process(float* out, uint32 numSamples) { - for (uint32 i = 0; i < numSamples; i++) - { - if (panning > 0) - out[i * 2 + 0] = (out[i * 2 + 0] * (1.0f - panning)) * mix + out[i * 2 + 0] * (1 - mix); - if (panning < 0) - out[i * 2 + 1] = (out[i * 2 + 1] * (1.0f + panning)) * mix + out[i * 2 + 1] * (1 - mix); - } + for (uint32 i = 0; i < numSamples; i++) + { + if (panning > 0) + out[i * 2 + 0] = (out[i * 2 + 0] * (1.0f - panning)) * mix + out[i * 2 + 0] * (1 - mix); + if (panning < 0) + out[i * 2 + 1] = (out[i * 2 + 1] * (1.0f + panning)) * mix + out[i * 2 + 1] * (1 - mix); + } } BQFDSP::BQFDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } -void BQFDSP::Process(float *out, uint32 numSamples) +void BQFDSP::Process(float* out, uint32 numSamples) { - for (uint32 c = 0; c < 2; c++) - { - for (uint32 i = 0; i < numSamples; i++) - { - out[i * 2 + c] = m_filters[c].Update(out[i * 2 + c]); - } - } + for (uint32 c = 0; c < 2; c++) + { + for (uint32 i = 0; i < numSamples; i++) + { + out[i * 2 + c] = m_filters[c].Update(out[i * 2 + c]); + } + } } void BQFDSP::SetLowPass(float q, float freq) { - for (uint32 c = 0; c < 2; c++) - { - m_filters[c].SetLowPass(q, freq, (float)m_sampleRate); - } + for (uint32 c = 0; c < 2; c++) + { + m_filters[c].SetLowPass(q, freq, (float)m_sampleRate); + } } void BQFDSP::SetHighPass(float q, float freq) { - for (uint32 c = 0; c < 2; c++) - { - m_filters[c].SetHighPass(q, freq, (float)m_sampleRate); - } + for (uint32 c = 0; c < 2; c++) + { + m_filters[c].SetHighPass(q, freq, (float)m_sampleRate); + } } void BQFDSP::SetPeaking(float q, float freq, float gain) { - for (uint32 c = 0; c < 2; c++) - { - m_filters[c].SetPeaking(q, freq, gain, (float)m_sampleRate); - } + for (uint32 c = 0; c < 2; c++) + { + m_filters[c].SetPeaking(q, freq, gain, (float)m_sampleRate); + } } CombinedFilterDSP::CombinedFilterDSP(uint32 sampleRate) : DSP(), a(sampleRate), peak(sampleRate) { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } void CombinedFilterDSP::SetLowPass(float q, float freq, float peakQ, float peakGain) { - a.SetLowPass(q, freq); - peak.SetPeaking(peakQ, freq, peakGain); + a.SetLowPass(q, freq); + peak.SetPeaking(peakQ, freq, peakGain); } void CombinedFilterDSP::SetHighPass(float q, float freq, float peakQ, float peakGain) { - a.SetHighPass(q, freq); - peak.SetPeaking(peakQ, freq, peakGain); + a.SetHighPass(q, freq); + peak.SetPeaking(peakQ, freq, peakGain); } -void CombinedFilterDSP::Process(float *out, uint32 numSamples) +void CombinedFilterDSP::Process(float* out, uint32 numSamples) { - a.mix = mix; - peak.mix = mix; - a.Process(out, numSamples); - peak.Process(out, numSamples); + a.mix = mix; + peak.mix = mix; + a.Process(out, numSamples); + peak.Process(out, numSamples); } LimiterDSP::LimiterDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); -} -void LimiterDSP::Process(float *out, uint32 numSamples) -{ - const float secondsPerSample = 1.0f / (float)m_sampleRate; - for (uint32 i = 0; i < numSamples; i++) - { - float currentGain = 1.0f; - if (m_currentReleaseTimer < releaseTime) - { - float t = (1.0f - m_currentReleaseTimer / releaseTime); - currentGain = (1.0f / m_currentMaxVolume) * t + (1.0f - t); - } - - float maxVolume = Math::Max(abs(out[i * 2]), abs(out[i * 2 + 1])); - out[i * 2] *= currentGain * 0.9f; - out[i * 2 + 1] *= currentGain * 0.9f; - - float currentMax = 1.0f / currentGain; - if (maxVolume > currentMax) - { - m_currentMaxVolume = maxVolume; - m_currentReleaseTimer = 0.0f; - } - else - { - m_currentReleaseTimer += secondsPerSample; - } - } + SetSampleRate(sampleRate); +} +void LimiterDSP::Process(float* out, uint32 numSamples) +{ + const float secondsPerSample = 1.0f / (float)m_sampleRate; + for (uint32 i = 0; i < numSamples; i++) + { + float currentGain = 1.0f; + if (m_currentReleaseTimer < releaseTime) + { + float t = (1.0f - m_currentReleaseTimer / releaseTime); + currentGain = (1.0f / m_currentMaxVolume) * t + (1.0f - t); + } + + float maxVolume = Math::Max(abs(out[i * 2]), abs(out[i * 2 + 1])); + out[i * 2] *= currentGain * 0.9f; + out[i * 2 + 1] *= currentGain * 0.9f; + + float currentMax = 1.0f / currentGain; + if (maxVolume > currentMax) + { + m_currentMaxVolume = maxVolume; + m_currentReleaseTimer = 0.0f; + } + else + { + m_currentReleaseTimer += secondsPerSample; + } + } } BitCrusherDSP::BitCrusherDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } void BitCrusherDSP::SetPeriod(float period /*= 0*/) { - // Scale period with sample rate - assert(m_sampleRate > 0); - double f = m_sampleRate / 44100.0; + // Scale period with sample rate + assert(m_sampleRate > 0); + double f = m_sampleRate / 44100.0; - m_increment = (uint32)((double)(1 << 16)); - m_period = (uint32)(f * period * (double)(1 << 16)); + m_increment = (uint32)((double)(1 << 16)); + m_period = (uint32)(f * period * (double)(1 << 16)); } -void BitCrusherDSP::Process(float *out, uint32 numSamples) +void BitCrusherDSP::Process(float* out, uint32 numSamples) { - for (uint32 i = 0; i < numSamples; i++) - { - m_currentDuration += m_increment; - if (m_currentDuration > m_period) - { - m_sampleBuffer[0] = out[i * 2]; - m_sampleBuffer[1] = out[i * 2 + 1]; - m_currentDuration -= m_period; - } + for (uint32 i = 0; i < numSamples; i++) + { + m_currentDuration += m_increment; + if (m_currentDuration > m_period) + { + m_sampleBuffer[0] = out[i * 2]; + m_sampleBuffer[1] = out[i * 2 + 1]; + m_currentDuration -= m_period; + } - out[i * 2] = m_sampleBuffer[0] * mix + out[i * 2] * (1.0f - mix); - out[i * 2 + 1] = m_sampleBuffer[1] * mix + out[i * 2 + 1] * (1.0f - mix); - } + out[i * 2] = m_sampleBuffer[0] * mix + out[i * 2] * (1.0f - mix); + out[i * 2 + 1] = m_sampleBuffer[1] * mix + out[i * 2 + 1] * (1.0f - mix); + } } GateDSP::GateDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } void GateDSP::SetLength(double length) { - double flength = length / 1000.0 * m_sampleRate; - m_length = (uint32)flength; - SetGating(m_gating); + double flength = length / 1000.0 * m_sampleRate; + m_length = (uint32)flength; + SetGating(m_gating); } void GateDSP::SetGating(float gating) { - gating = Math::Clamp(gating, 0.f, 1.f); - float flength = (float)m_length; - m_gating = gating; - m_halfway = (uint32)(flength * gating); - const float fadeDuration = Math::Min(0.05f, gating * 0.5f); - m_fadeIn = (uint32)((float)m_halfway * fadeDuration); - m_fadeOut = (uint32)((float)m_halfway * (1.0f - fadeDuration)); - m_currentSample = 0; -} -void GateDSP::Process(float *out, uint32 numSamples) -{ - if (m_length < 2) - return; - - const uint32 startSample = GetStartSample(); - const uint32 currentSample = GetCurrentSample(); - - for (uint32 i = 0; i < numSamples; i++) - { - if (currentSample + i < startSample) - { - continue; - } - float c = 1.0f; - if (m_currentSample < m_halfway) - { - // Fade out before silence - if (m_currentSample > m_fadeOut) - c = 1 - (float)(m_currentSample - m_fadeOut) / (float)m_fadeIn; - } - else - { - uint32 t = m_currentSample - m_halfway; - // Fade in again - if (t > m_fadeOut) - c = (float)(t - m_fadeOut) / (float)m_fadeIn; - else - c = 0.0f; - } - - // Multiply volume - c = (c * (1 - low) + low); // Range [low, 1] - c = c * mix + (1.0f - mix); - out[i * 2] *= c; - out[i * 2 + 1] *= c; - - m_currentSample++; - m_currentSample %= m_length; - } + gating = Math::Clamp(gating, 0.f, 1.f); + float flength = (float)m_length; + m_gating = gating; + m_halfway = (uint32)(flength * gating); + const float fadeDuration = Math::Min(0.05f, gating * 0.5f); + m_fadeIn = (uint32)((float)m_halfway * fadeDuration); + m_fadeOut = (uint32)((float)m_halfway * (1.0f - fadeDuration)); + m_currentSample = 0; +} +void GateDSP::Process(float* out, uint32 numSamples) +{ + if (m_length < 2) + return; + + const uint32 startSample = GetStartSample(); + const uint32 currentSample = GetCurrentSample(); + + for (uint32 i = 0; i < numSamples; i++) + { + if (currentSample + i < startSample) + { + continue; + } + float c = 1.0f; + if (m_currentSample < m_halfway) + { + // Fade out before silence + if (m_currentSample > m_fadeOut) + c = 1 - (float)(m_currentSample - m_fadeOut) / (float)m_fadeIn; + } + else + { + uint32 t = m_currentSample - m_halfway; + // Fade in again + if (t > m_fadeOut) + c = (float)(t - m_fadeOut) / (float)m_fadeIn; + else + c = 0.0f; + } + + // Multiply volume + c = (c * (1 - low) + low); // Range [low, 1] + c = c * mix + (1.0f - mix); + out[i * 2] *= c; + out[i * 2 + 1] *= c; + + m_currentSample++; + m_currentSample %= m_length; + } } TapeStopDSP::TapeStopDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } void TapeStopDSP::SetLength(double length) { - assert(m_sampleRate > 0); - double flength = length / 1000.0 * m_sampleRate; - m_length = (uint32)flength; - m_sampleBuffer.clear(); - m_sampleBuffer.reserve(2 * m_length + 100); -} -void TapeStopDSP::Process(float *out, uint32 numSamples) -{ - const uint32 startSample = GetStartSample(); - const uint32 currentSample = GetCurrentSample(); - - for (uint32 i = 0; i < numSamples; i++) - { - if (currentSample + i < startSample) - { - continue; - } - float sampleRate = 1.0f - (float)m_currentSample / (float)m_length; - if (sampleRate == 0.0f) - { - // Mute - out[i * 2] = 0.0f; - out[i * 2 + 1] = 0.0f; - continue; - } - // Store samples for later - m_sampleBuffer.Add(out[i * 2]); - m_sampleBuffer.Add(out[i * 2 + 1]); - - // The sample index into the buffer - uint32 i2 = (uint32)floor(m_sampleIdx); - out[i * 2] = m_sampleBuffer[i2 * 2] * mix + out[i * 2] * (1 - mix); - out[i * 2 + 1] = m_sampleBuffer[i2 * 2 + 1] * mix + out[i * 2 + 1] * (1 - mix); - - // Increase index - m_sampleIdx += sampleRate; - m_currentSample++; - } + assert(m_sampleRate > 0); + double flength = length / 1000.0 * m_sampleRate; + m_length = (uint32)flength; + m_sampleBuffer.clear(); + m_sampleBuffer.reserve(2 * m_length + 100); +} +void TapeStopDSP::Process(float* out, uint32 numSamples) +{ + const uint32 startSample = GetStartSample(); + const uint32 currentSample = GetCurrentSample(); + + for (uint32 i = 0; i < numSamples; i++) + { + if (currentSample + i < startSample) + { + continue; + } + float sampleRate = 1.0f - (float)m_currentSample / (float)m_length; + if (sampleRate == 0.0f) + { + // Mute + out[i * 2] = 0.0f; + out[i * 2 + 1] = 0.0f; + continue; + } + // Store samples for later + m_sampleBuffer.Add(out[i * 2]); + m_sampleBuffer.Add(out[i * 2 + 1]); + + // The sample index into the buffer + uint32 i2 = (uint32)floor(m_sampleIdx); + out[i * 2] = m_sampleBuffer[i2 * 2] * mix + out[i * 2] * (1 - mix); + out[i * 2 + 1] = m_sampleBuffer[i2 * 2 + 1] * mix + out[i * 2 + 1] * (1 - mix); + + // Increase index + m_sampleIdx += sampleRate; + m_currentSample++; + } } RetriggerDSP::RetriggerDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } void RetriggerDSP::SetLength(double length) { - double flength = length / 1000.0 * m_sampleRate; - m_length = (uint32)flength; - SetGating(m_gating); - if (!m_bufferReserved) - { - m_sampleBuffer.reserve(2 * m_length + 100); - m_bufferReserved = true; - } + double flength = length / 1000.0 * m_sampleRate; + m_length = (uint32)flength; + SetGating(m_gating); + if (!m_bufferReserved) + { + m_sampleBuffer.reserve(2 * m_length + 100); + m_bufferReserved = true; + } } void RetriggerDSP::SetResetDuration(uint32 resetDuration) { - float flength = (float)resetDuration / 1000.0f * (float)m_sampleRate; - m_resetDuration = (uint32)flength; + float flength = (float)resetDuration / 1000.0f * (float)m_sampleRate; + m_resetDuration = (uint32)flength; } void RetriggerDSP::SetGating(float gating) { - gating = Math::Clamp(gating, 0.f, 1.f); - m_gating = gating; - m_gateLength = (uint32)((float)m_length * gating); + gating = Math::Clamp(gating, 0.f, 1.f); + m_gating = gating; + m_gateLength = (uint32)((float)m_length * gating); } void RetriggerDSP::SetMaxLength(uint32 length) { - float flength = (float)length / 1000.0f * (float)m_sampleRate; - if (!m_bufferReserved) - { - m_sampleBuffer.reserve(2 * (uint32_t)flength + 100); - m_bufferReserved = true; - } -} -void RetriggerDSP::Process(float *out, uint32 numSamples) -{ - if (m_length == 0) - return; - - const uint32 startSample = GetStartSample(); - const uint32 nowSample = GetCurrentSample(); - const auto maxSample = m_audioBase->GetPCMCount(); - - float *pcmSource = m_audioBase->GetPCM(); - double rateMult = (double)m_audioBase->GetSampleRate() / m_sampleRate; - uint32 pcmStartSample = static_cast(lastTimingPoint * ((double)m_audioBase->GetSampleRate() / 1000.0)); - uint32 baseStartRepeat = static_cast(lastTimingPoint * ((double)m_audioBase->GetSampleRate() / 1000.0)); - - for (uint32 i = 0; i < numSamples; i++) - { - if (nowSample + i < startSample) - { - continue; - } - - int startOffset = 0; - if (m_resetDuration > 0) - { - startOffset = (nowSample + i - baseStartRepeat) / (int)(m_resetDuration * rateMult); - startOffset = static_cast(startOffset * m_resetDuration * rateMult); - } - else - { - startOffset = static_cast((startSample - baseStartRepeat)); - } - - int pcmSample = static_cast(pcmStartSample) + startOffset + static_cast(m_currentSample * rateMult); - float gating = 1.0f; - if (m_currentSample > m_gateLength) - gating = 0; - // Sample from buffer - assert(static_cast(pcmSample) < maxSample); - if (static_cast(pcmSample) < maxSample) //TODO: Improve whatever is necessary to make sure this never happens - { - out[i * 2] = gating * pcmSource[pcmSample * 2] * mix + out[i * 2] * (1 - mix); - out[i * 2 + 1] = gating * pcmSource[pcmSample * 2 + 1] * mix + out[i * 2 + 1] * (1 - mix); - } - - // Increase index - m_currentSample = (m_currentSample + 1) % m_length; - } + float flength = (float)length / 1000.0f * (float)m_sampleRate; + if (!m_bufferReserved) + { + m_sampleBuffer.reserve(2 * (uint32_t)flength + 100); + m_bufferReserved = true; + } +} +void RetriggerDSP::Process(float* out, uint32 numSamples) +{ + if (m_length == 0) + return; + + const uint32 startSample = GetStartSample(); + const uint32 nowSample = GetCurrentSample(); + const auto maxSample = m_audioBase->GetPCMCount(); + + float* pcmSource = m_audioBase->GetPCM(); + double rateMult = (double)m_audioBase->GetSampleRate() / m_sampleRate; + uint32 pcmStartSample = static_cast(lastTimingPoint * ((double)m_audioBase->GetSampleRate() / 1000.0)); + uint32 baseStartRepeat = static_cast(lastTimingPoint * ((double)m_audioBase->GetSampleRate() / 1000.0)); + + for (uint32 i = 0; i < numSamples; i++) + { + if (nowSample + i < startSample) + { + continue; + } + + int startOffset = 0; + if (m_resetDuration > 0) + { + startOffset = (nowSample + i - baseStartRepeat) / (int)(m_resetDuration * rateMult); + startOffset = static_cast(startOffset * m_resetDuration * rateMult); + } + else + { + startOffset = static_cast((startSample - baseStartRepeat)); + } + + int pcmSample = static_cast(pcmStartSample) + startOffset + static_cast(m_currentSample * rateMult); + float gating = 1.0f; + if (m_currentSample > m_gateLength) + gating = 0; + // Sample from buffer + assert(static_cast(pcmSample) < maxSample); + if (static_cast(pcmSample) < maxSample) // TODO: Improve whatever is necessary to make sure this never happens + { + out[i * 2] = gating * pcmSource[pcmSample * 2] * mix + out[i * 2] * (1 - mix); + out[i * 2 + 1] = gating * pcmSource[pcmSample * 2 + 1] * mix + out[i * 2 + 1] * (1 - mix); + } + + // Increase index + m_currentSample = (m_currentSample + 1) % m_length; + } } WobbleDSP::WobbleDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } void WobbleDSP::SetLength(double length) { - double flength = length / 1000.0 * m_sampleRate; - m_length = (uint32)flength; + double flength = length / 1000.0 * m_sampleRate; + m_length = (uint32)flength; } -void WobbleDSP::Process(float *out, uint32 numSamples) +void WobbleDSP::Process(float* out, uint32 numSamples) { - if (m_length == 0) - return; + if (m_length == 0) + return; - static Interpolation::CubicBezier easing(Interpolation::EaseInExpo); - const uint32 startSample = GetStartSample(); - const uint32 currentSample = GetCurrentSample(); + static Interpolation::CubicBezier easing(Interpolation::EaseInExpo); + const uint32 startSample = GetStartSample(); + const uint32 currentSample = GetCurrentSample(); - for (uint32 i = 0; i < numSamples; i++) - { - if (currentSample + i < startSample) - { - continue; - } - float f = abs(2.0f * ((float)m_currentSample / (float)m_length) - 1.0f); - f = easing.Sample(f); - float freq = fmin + (fmax - fmin) * f; + for (uint32 i = 0; i < numSamples; i++) + { + if (currentSample + i < startSample) + { + continue; + } + float f = abs(2.0f * ((float)m_currentSample / (float)m_length) - 1.0f); + f = easing.Sample(f); + float freq = fmin + (fmax - fmin) * f; - for (uint32 c = 0; c < 2; c++) { - m_filters[c].SetLowPass(q, freq, (float)m_sampleRate); - out[i * 2 + c] = m_filters[c].Update(out[i * 2 + c]) * mix + out[i * 2 + c] * (1.0f - mix); - } + for (uint32 c = 0; c < 2; c++) + { + m_filters[c].SetLowPass(q, freq, (float)m_sampleRate); + out[i * 2 + c] = m_filters[c].Update(out[i * 2 + c]) * mix + out[i * 2 + c] * (1.0f - mix); + } - m_currentSample++; - m_currentSample %= m_length; - } + m_currentSample++; + m_currentSample %= m_length; + } } PhaserDSP::PhaserDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } void PhaserDSP::SetLength(double length) { - double flength = length / 1000.0 * m_sampleRate; - m_length = (uint32)flength; + double flength = length / 1000.0 * m_sampleRate; + m_length = (uint32)flength; } void PhaserDSP::SetStage(uint32 stage) { - stage = Math::Clamp(stage, 0u, 12u); - m_stage = (stage / 2) * 2; -} -void PhaserDSP::Process(float *out, uint32 numSamples) -{ - if (m_length == 0) - return; - - const uint32 startSample = GetStartSample(); - const uint32 currentSample = GetCurrentSample(); - - // logarithmic center - float freqCenter = sqrt(fmin*fmax); - for (uint32 c = 0; c < 2; c++) { - m_hiShelf[c].SetHighShelf(1.5f, freqCenter, hiCutGain, (float)m_sampleRate); - } - - for (uint32 i = 0; i < numSamples; i++) - { - if (currentSample + i < startSample) - { - continue; - } - - float fLeft = abs(2.0f * ((float)m_currentSample / (float)m_length) - 1.0f); - float fRight = abs(2.0f * fmodf((float)m_currentSample / (float)m_length + stereoWidth, 1.0f) - 1.0f); - float f[2] = {fLeft, fRight}; - for (uint32 c = 0; c < 2; c++) { - // logarithmic interpolation - float freq = pow(fmin, f[c]) * pow(fmax, 1-f[c]); - float output = feedback * za[c] + out[i * 2 + c]; - for (uint32 j = 0; j < m_stage; j++) { - m_apf[j][c].SetAllPass(q, freq, (float)m_sampleRate); - output = m_apf[j][c].Update(output); - } - za[c] = output; - // effect strongest when mix = 0.5 - output = (mix/2) * output + (1-mix/2) * out[i * 2 + c]; - float shelved = m_hiShelf[c].Update(output); - out[i * 2 + c] = mix * shelved + (1-mix) * output; - } - m_currentSample++; - m_currentSample %= m_length; - } + stage = Math::Clamp(stage, 0u, 12u); + m_stage = (stage / 2) * 2; +} +void PhaserDSP::Process(float* out, uint32 numSamples) +{ + if (m_length == 0) + return; + + const uint32 startSample = GetStartSample(); + const uint32 currentSample = GetCurrentSample(); + + // logarithmic center + float freqCenter = sqrt(fmin * fmax); + for (uint32 c = 0; c < 2; c++) + { + m_hiShelf[c].SetHighShelf(1.5f, freqCenter, hiCutGain, (float)m_sampleRate); + } + + for (uint32 i = 0; i < numSamples; i++) + { + if (currentSample + i < startSample) + { + continue; + } + + float fLeft = abs(2.0f * ((float)m_currentSample / (float)m_length) - 1.0f); + float fRight = abs(2.0f * fmodf((float)m_currentSample / (float)m_length + stereoWidth, 1.0f) - 1.0f); + float f[2] = { fLeft, fRight }; + for (uint32 c = 0; c < 2; c++) + { + // logarithmic interpolation + float freq = pow(fmin, f[c]) * pow(fmax, 1 - f[c]); + float output = feedback * za[c] + out[i * 2 + c]; + for (uint32 j = 0; j < m_stage; j++) + { + m_apf[j][c].SetAllPass(q, freq, (float)m_sampleRate); + output = m_apf[j][c].Update(output); + } + za[c] = output; + // effect strongest when mix = 0.5 + output = (mix / 2) * output + (1 - mix / 2) * out[i * 2 + c]; + float shelved = m_hiShelf[c].Update(output); + out[i * 2 + c] = mix * shelved + (1 - mix) * output; + } + m_currentSample++; + m_currentSample %= m_length; + } } FlangerDSP::FlangerDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } void FlangerDSP::SetLength(double length) { - double flength = length / 1000.0 * m_sampleRate; - m_length = (uint32)flength; + double flength = length / 1000.0 * m_sampleRate; + m_length = (uint32)flength; } void FlangerDSP::SetDelayRange(uint32 offset, uint32 depth) { - // Assuming 44100hz is the base sample rate - const uint32 max = offset + depth; - const uint32 min = offset; + // Assuming 44100hz is the base sample rate + const uint32 max = offset + depth; + const uint32 min = offset; - const float mult = (float)m_sampleRate / 44100.f; - m_min = static_cast(min * mult); - m_max = static_cast(max * mult); - m_bufferLength = (m_max + 1) * 2; - m_sampleBuffer.resize(m_bufferLength); + const float mult = (float)m_sampleRate / 44100.f; + m_min = static_cast(min * mult); + m_max = static_cast(max * mult); + m_bufferLength = (m_max + 1) * 2; + m_sampleBuffer.resize(m_bufferLength); } void FlangerDSP::SetFeedback(float feedback) { - m_feedback = feedback; + m_feedback = feedback; } void FlangerDSP::SetStereoWidth(float stereoWidth) { - m_stereoWidth = stereoWidth; + m_stereoWidth = stereoWidth; } void FlangerDSP::SetVolume(float volume) { - m_volume = volume; -} -void FlangerDSP::Process(float *out, uint32 numSamples) -{ - if (m_bufferLength <= 0) - return; - float *data = m_sampleBuffer.data(); - if (data == nullptr) - return; - - const uint32 startSample = GetStartSample(); - const uint32 currentSample = GetCurrentSample(); - const uint32 depth = m_max - m_min; - - for (uint32 i = 0; i < numSamples; i++) - { - if (currentSample + i < startSample) - { - continue; - } - // Determine where we want to sample past samples - float f = fmodf(((float)m_time / ((float)m_length * 2)), 1.f); - f = fabsf(f * 2 - 1); - uint32 dLeft = (uint32)(m_max - (depth * f)); - uint32 dRight = dLeft + (uint32)(m_stereoWidth * depth); - // "fold" dRight to fit [m_min,m_max] range: matches KSM behavior - if (dRight > m_max) dRight = m_max - (dRight - m_max); - - // TODO: clean up? - int32 samplePosLeft = ((int)m_bufferOffset - (int)dLeft * 2) % (int)m_bufferLength; - if (samplePosLeft < 0) samplePosLeft = m_bufferLength + samplePosLeft; - int32 samplePosRight = ((int)m_bufferOffset - (int)dRight * 2 + 1) % (int)m_bufferLength; - if (samplePosRight < 0) samplePosRight = m_bufferLength + samplePosRight; - - // Inject new sample - data[m_bufferOffset + 0] = Math::Clamp( - (m_feedback * data[samplePosLeft] + out[i * 2]) * (mix * m_volume + (1.f - mix)), -1.f, - 1.f); - data[m_bufferOffset + 1] = Math::Clamp( - (m_feedback * data[samplePosRight] + out[i * 2 + 1]) * (mix * m_volume + (1.f - mix)), - -1.f, 1.f); - - // Apply delay - out[i * 2] = Math::Clamp( - (mix * data[samplePosLeft] + out[i * 2]) * (mix * m_volume + (1.f - mix)), -1.f, 1.f); - out[i * 2 + 1] = Math::Clamp( - (mix * data[samplePosRight] + out[i * 2 + 1]) * (mix * m_volume + (1.f - mix)), -1.f, - 1.f); - - m_bufferOffset += 2; - if (m_bufferOffset >= m_bufferLength) - m_bufferOffset = 0; - m_time++; - } + m_volume = volume; +} +void FlangerDSP::Process(float* out, uint32 numSamples) +{ + if (m_bufferLength <= 0) + return; + float* data = m_sampleBuffer.data(); + if (data == nullptr) + return; + + const uint32 startSample = GetStartSample(); + const uint32 currentSample = GetCurrentSample(); + const uint32 depth = m_max - m_min; + + for (uint32 i = 0; i < numSamples; i++) + { + if (currentSample + i < startSample) + { + continue; + } + // Determine where we want to sample past samples + float f = fmodf(((float)m_time / ((float)m_length * 2)), 1.f); + f = fabsf(f * 2 - 1); + uint32 dLeft = (uint32)(m_max - (depth * f)); + uint32 dRight = dLeft + (uint32)(m_stereoWidth * depth); + // "fold" dRight to fit [m_min,m_max] range: matches KSM behavior + if (dRight > m_max) + dRight = m_max - (dRight - m_max); + + // TODO: clean up? + int32 samplePosLeft = ((int)m_bufferOffset - (int)dLeft * 2) % (int)m_bufferLength; + if (samplePosLeft < 0) + samplePosLeft = m_bufferLength + samplePosLeft; + int32 samplePosRight = ((int)m_bufferOffset - (int)dRight * 2 + 1) % (int)m_bufferLength; + if (samplePosRight < 0) + samplePosRight = m_bufferLength + samplePosRight; + + // Inject new sample + data[m_bufferOffset + 0] = Math::Clamp( + (m_feedback * data[samplePosLeft] + out[i * 2]) * (mix * m_volume + (1.f - mix)), -1.f, + 1.f); + data[m_bufferOffset + 1] = Math::Clamp( + (m_feedback * data[samplePosRight] + out[i * 2 + 1]) * (mix * m_volume + (1.f - mix)), + -1.f, 1.f); + + // Apply delay + out[i * 2] = Math::Clamp( + (mix * data[samplePosLeft] + out[i * 2]) * (mix * m_volume + (1.f - mix)), -1.f, 1.f); + out[i * 2 + 1] = Math::Clamp( + (mix * data[samplePosRight] + out[i * 2 + 1]) * (mix * m_volume + (1.f - mix)), -1.f, + 1.f); + + m_bufferOffset += 2; + if (m_bufferOffset >= m_bufferLength) + m_bufferOffset = 0; + m_time++; + } } EchoDSP::EchoDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } void EchoDSP::SetLength(double length) { - double flength = length / 1000.0 * m_sampleRate; - m_sampleBuffer.clear(); - m_bufferLength = (uint32)(flength * 2); - m_sampleBuffer.resize(m_bufferLength); - memset(m_sampleBuffer.data(), 0, sizeof(float) * m_bufferLength); - m_numLoops = 0; + double flength = length / 1000.0 * m_sampleRate; + m_sampleBuffer.clear(); + m_bufferLength = (uint32)(flength * 2); + m_sampleBuffer.resize(m_bufferLength); + memset(m_sampleBuffer.data(), 0, sizeof(float) * m_bufferLength); + m_numLoops = 0; } -void EchoDSP::Process(float *out, uint32 numSamples) +void EchoDSP::Process(float* out, uint32 numSamples) { - float *data = m_sampleBuffer.data(); - if (!data) - return; + float* data = m_sampleBuffer.data(); + if (!data) + return; - const uint32 startSample = GetStartSample(); - const uint32 currentSample = GetCurrentSample(); + const uint32 startSample = GetStartSample(); + const uint32 currentSample = GetCurrentSample(); - for (uint32 i = 0; i < numSamples; i++) - { - if (currentSample + i < startSample) - { - continue; - } + for (uint32 i = 0; i < numSamples; i++) + { + if (currentSample + i < startSample) + { + continue; + } - float l0 = data[m_bufferOffset + 0]; - float l1 = data[m_bufferOffset + 1]; + float l0 = data[m_bufferOffset + 0]; + float l1 = data[m_bufferOffset + 1]; - if (m_numLoops > 0) - { - // Send echo to output - out[i * 2] = l0 * mix; - out[i * 2 + 1] = l1 * mix; - } + if (m_numLoops > 0) + { + // Send echo to output + out[i * 2] = l0 * mix; + out[i * 2 + 1] = l1 * mix; + } - // Inject new sample - data[m_bufferOffset + 0] = out[i * 2] * feedback; - data[m_bufferOffset + 1] = out[i * 2 + 1] * feedback; + // Inject new sample + data[m_bufferOffset + 0] = out[i * 2] * feedback; + data[m_bufferOffset + 1] = out[i * 2 + 1] * feedback; - m_bufferOffset += 2; - if (m_bufferOffset >= m_bufferLength) - { - m_bufferOffset = 0; - m_numLoops++; - } - } + m_bufferOffset += 2; + if (m_bufferOffset >= m_bufferLength) + { + m_bufferOffset = 0; + m_numLoops++; + } + } } SidechainDSP::SidechainDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); + SetSampleRate(sampleRate); } void SidechainDSP::SetLength(double length) { - double flength = length / 1000.0 * m_sampleRate; - m_length = (uint32)flength; - m_time = 0; + double flength = length / 1000.0 * m_sampleRate; + m_length = (uint32)flength; + m_time = 0; } void SidechainDSP::SetAttackTime(double length) { - double flength = length / 1000.0 * m_sampleRate; - m_attackTime = (uint32)flength; + double flength = length / 1000.0 * m_sampleRate; + m_attackTime = (uint32)flength; } void SidechainDSP::SetHoldTime(double length) { - double flength = length / 1000.0 * m_sampleRate; - m_holdTime = (uint32)flength; + double flength = length / 1000.0 * m_sampleRate; + m_holdTime = (uint32)flength; } void SidechainDSP::SetReleaseTime(double length) { - double flength = length / 1000.0 * m_sampleRate; - m_releaseTime = (uint32)flength; -} -void SidechainDSP::Process(float *out, uint32 numSamples) -{ - if (m_length == 0) - return; - - const uint32 startSample = GetStartSample(); - const uint32 currentSample = GetCurrentSample(); - for (uint32 i = 0; i < numSamples; i++) - { - if (currentSample + i < startSample) - { - continue; - } - float volume; - if (m_time < m_attackTime) - { - volume = 1.0f - ((float)m_time / (float)m_attackTime); - } - else if (m_time < m_attackTime + m_holdTime) - { - volume = 0.0f; - } - else if (m_time < m_attackTime + m_holdTime + m_releaseTime) - { - volume = ((float)(m_time - m_attackTime - m_holdTime) / (float)m_releaseTime); - } - else - { - volume = 1.0f; - } - // range from 1/ratio (volume=0) to 1 (volume=1) - float sampleGain = mix * ((1.0f/ratio) + (1.0f - 1.0f/ratio)*volume) + (1.0f - mix); - out[i * 2 + 0] *= sampleGain; - out[i * 2 + 1] *= sampleGain; - if (++m_time > m_length) - { - m_time = 0; - } - } + double flength = length / 1000.0 * m_sampleRate; + m_releaseTime = (uint32)flength; +} +void SidechainDSP::Process(float* out, uint32 numSamples) +{ + if (m_length == 0) + return; + + const uint32 startSample = GetStartSample(); + const uint32 currentSample = GetCurrentSample(); + for (uint32 i = 0; i < numSamples; i++) + { + if (currentSample + i < startSample) + { + continue; + } + float volume; + if (m_time < m_attackTime) + { + volume = 1.0f - ((float)m_time / (float)m_attackTime); + } + else if (m_time < m_attackTime + m_holdTime) + { + volume = 0.0f; + } + else if (m_time < m_attackTime + m_holdTime + m_releaseTime) + { + volume = ((float)(m_time - m_attackTime - m_holdTime) / (float)m_releaseTime); + } + else + { + volume = 1.0f; + } + // range from 1/ratio (volume=0) to 1 (volume=1) + float sampleGain = mix * ((1.0f / ratio) + (1.0f - 1.0f / ratio) * volume) + (1.0f - mix); + out[i * 2 + 0] *= sampleGain; + out[i * 2 + 1] *= sampleGain; + if (++m_time > m_length) + { + m_time = 0; + } + } } #include "SoundTouch.h" @@ -739,51 +746,51 @@ using namespace soundtouch; class PitchShiftDSP_Impl { public: - float pitch = 0.0f; - bool init = false; + float pitch = 0.0f; + bool init = false; private: - SoundTouch m_soundtouch; - Vector m_receiveBuffer; + SoundTouch m_soundtouch; + Vector m_receiveBuffer; public: - PitchShiftDSP_Impl() = default; - ~PitchShiftDSP_Impl() = default; - void Init(uint32 sampleRate) - { - m_soundtouch.setChannels(2); - m_soundtouch.setSampleRate(sampleRate); - m_soundtouch.setSetting(SETTING_USE_AA_FILTER, 0); - m_soundtouch.setSetting(SETTING_SEQUENCE_MS, 5); - //m_soundtouch.setSetting(SETTING_SEEKWINDOW_MS, 10); - //m_soundtouch.setSetting(SETTING_OVERLAP_MS, 10); - } - void Process(float *out, uint32 numSamples) - { - m_receiveBuffer.resize(numSamples * 2); - m_soundtouch.setPitchSemiTones(pitch); - m_soundtouch.putSamples(out, numSamples); - uint32 receivedSamples = m_soundtouch.receiveSamples(m_receiveBuffer.data(), numSamples); - if (receivedSamples > 0) - { - memcpy(out, m_receiveBuffer.data(), receivedSamples * sizeof(float) * 2); - } - } + PitchShiftDSP_Impl() = default; + ~PitchShiftDSP_Impl() = default; + void Init(uint32 sampleRate) + { + m_soundtouch.setChannels(2); + m_soundtouch.setSampleRate(sampleRate); + m_soundtouch.setSetting(SETTING_USE_AA_FILTER, 0); + m_soundtouch.setSetting(SETTING_SEQUENCE_MS, 5); + // m_soundtouch.setSetting(SETTING_SEEKWINDOW_MS, 10); + // m_soundtouch.setSetting(SETTING_OVERLAP_MS, 10); + } + void Process(float* out, uint32 numSamples) + { + m_receiveBuffer.resize(numSamples * 2); + m_soundtouch.setPitchSemiTones(pitch); + m_soundtouch.putSamples(out, numSamples); + uint32 receivedSamples = m_soundtouch.receiveSamples(m_receiveBuffer.data(), numSamples); + if (receivedSamples > 0) + { + memcpy(out, m_receiveBuffer.data(), receivedSamples * sizeof(float) * 2); + } + } }; PitchShiftDSP::PitchShiftDSP(uint32 sampleRate) : DSP() { - SetSampleRate(sampleRate); - m_impl = new PitchShiftDSP_Impl(); + SetSampleRate(sampleRate); + m_impl = new PitchShiftDSP_Impl(); } PitchShiftDSP::~PitchShiftDSP() { - delete m_impl; + delete m_impl; } -void PitchShiftDSP::Process(float *out, uint32 numSamples) +void PitchShiftDSP::Process(float* out, uint32 numSamples) { - m_impl->pitch = Math::Clamp(amount, -48.0f, 48.0f); - if (!m_impl->init) - m_impl->Init(m_sampleRate); - m_impl->Process(out, numSamples); + m_impl->pitch = Math::Clamp(amount, -48.0f, 48.0f); + if (!m_impl->init) + m_impl->Init(m_sampleRate); + m_impl->Process(out, numSamples); } diff --git a/Audio/src/Sample.cpp b/Audio/src/Sample.cpp index f1dfa1e0e..52025e27d 100644 --- a/Audio/src/Sample.cpp +++ b/Audio/src/Sample.cpp @@ -3,144 +3,144 @@ #include "Audio_Impl.hpp" #include "Audio.hpp" -#include "extras/dr_wav.h" // Enables WAV decoding. -#include "extras/dr_flac.h" // Enables FLAC decoding. -#include "extras/dr_mp3.h" // Enables MP3 decoding. +#include "extras/dr_wav.h" // Enables WAV decoding. +#include "extras/dr_flac.h" // Enables FLAC decoding. +#include "extras/dr_mp3.h" // Enables MP3 decoding. #undef STB_VORBIS_HEADER_ONLY -#include "extras/stb_vorbis.c" // Enables Vorbis decoding. +#include "extras/stb_vorbis.c" // Enables Vorbis decoding. #include "miniaudio.h" class Sample_Impl : public SampleRes { public: - Buffer m_data; - Audio *m_audio; - float *m_pcm = nullptr; + Buffer m_data; + Audio* m_audio; + float* m_pcm = nullptr; - mutex m_lock; + mutex m_lock; - uint64 m_playbackPointer = 0; - uint64 m_length = 0; - bool m_playing = false; - bool m_looping = false; + uint64 m_playbackPointer = 0; + uint64 m_length = 0; + bool m_playing = false; + bool m_looping = false; public: - ~Sample_Impl() - { - Deregister(); - if (m_pcm) - { - ma_free(m_pcm); - } - } - virtual void Play(bool looping) override - { - m_lock.lock(); - m_playing = true; - m_playbackPointer = 0; - m_looping = looping; - m_lock.unlock(); - } - virtual void Stop() override - { - m_lock.lock(); - m_playing = false; - m_looping = false; - m_lock.unlock(); - } - bool Init(const String &path) - { - - ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 2, g_audio->GetSampleRate()); - ma_result result; - result = ma_decode_file(*path, &config, &m_length, (void **)&m_pcm); - - if (result != MA_SUCCESS) - return false; - - return true; - } - virtual void Process(float *out, uint32 numSamples) override - { - if (!m_playing) - return; - - m_lock.lock(); - - for (uint32 i = 0; i < numSamples; i++) - { - if (m_playbackPointer >= m_length) - { - if (m_looping) - { - m_playbackPointer = 0; - } - else - { - // Playback ended - m_playing = false; - break; - } - } - - out[i * 2] = m_pcm[m_playbackPointer * 2]; - out[i * 2 + 1] = m_pcm[m_playbackPointer * 2 + 1]; - m_playbackPointer++; - } - m_lock.unlock(); - } - const Buffer &GetData() const override - { - return m_data; - } - uint32 GetBitsPerSample() const override - { - return 32; - } - uint32 GetNumChannels() const override - { - return 2; - } - int32 GetPosition() const override - { - return m_playbackPointer; - } - float *GetPCM() override - { - return nullptr; - } - uint64 GetPCMCount() const override - { - return 0; - } - uint32 GetSampleRate() const override - { - return g_audio->GetSampleRate(); - } - bool IsPlaying() const override - { - return m_playing; - } - void PreRenderDSPs(Vector &DSPs) override {} - uint64 GetSamplePos() const override - { - return m_playbackPointer; - } + ~Sample_Impl() + { + Deregister(); + if (m_pcm) + { + ma_free(m_pcm); + } + } + virtual void Play(bool looping) override + { + m_lock.lock(); + m_playing = true; + m_playbackPointer = 0; + m_looping = looping; + m_lock.unlock(); + } + virtual void Stop() override + { + m_lock.lock(); + m_playing = false; + m_looping = false; + m_lock.unlock(); + } + bool Init(const String& path) + { + + ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 2, g_audio->GetSampleRate()); + ma_result result; + result = ma_decode_file(*path, &config, &m_length, (void**)&m_pcm); + + if (result != MA_SUCCESS) + return false; + + return true; + } + virtual void Process(float* out, uint32 numSamples) override + { + if (!m_playing) + return; + + m_lock.lock(); + + for (uint32 i = 0; i < numSamples; i++) + { + if (m_playbackPointer >= m_length) + { + if (m_looping) + { + m_playbackPointer = 0; + } + else + { + // Playback ended + m_playing = false; + break; + } + } + + out[i * 2] = m_pcm[m_playbackPointer * 2]; + out[i * 2 + 1] = m_pcm[m_playbackPointer * 2 + 1]; + m_playbackPointer++; + } + m_lock.unlock(); + } + const Buffer& GetData() const override + { + return m_data; + } + uint32 GetBitsPerSample() const override + { + return 32; + } + uint32 GetNumChannels() const override + { + return 2; + } + int32 GetPosition() const override + { + return m_playbackPointer; + } + float* GetPCM() override + { + return nullptr; + } + uint64 GetPCMCount() const override + { + return 0; + } + uint32 GetSampleRate() const override + { + return g_audio->GetSampleRate(); + } + bool IsPlaying() const override + { + return m_playing; + } + void PreRenderDSPs(Vector& DSPs) override {} + uint64 GetSamplePos() const override + { + return m_playbackPointer; + } }; -Sample SampleRes::Create(Audio *audio, const String &path) +Sample SampleRes::Create(Audio* audio, const String& path) { - Sample_Impl *res = new Sample_Impl(); - res->m_audio = audio; + Sample_Impl* res = new Sample_Impl(); + res->m_audio = audio; - if (!res->Init(path)) - { - delete res; - return Sample(); - } + if (!res->Init(path)) + { + delete res; + return Sample(); + } - audio->GetImpl()->Register(res); + audio->GetImpl()->Register(res); - return Sample(res); -} \ No newline at end of file + return Sample(res); +} diff --git a/Audio/src/Unix/AudioOutput_SDL.cpp b/Audio/src/Unix/AudioOutput_SDL.cpp index 79a0f5147..3ab71487f 100644 --- a/Audio/src/Unix/AudioOutput_SDL.cpp +++ b/Audio/src/Unix/AudioOutput_SDL.cpp @@ -11,137 +11,137 @@ using std::this_thread::yield; /* SDL audio instance singleton*/ class SDLAudio { - SDLAudio() - { - SDL_SetMainReady(); - int r = SDL_InitSubSystem(SDL_INIT_AUDIO); - assert(r == 0); - } + SDLAudio() + { + SDL_SetMainReady(); + int r = SDL_InitSubSystem(SDL_INIT_AUDIO); + assert(r == 0); + } + public: - ~SDLAudio() - { - SDL_QuitSubSystem(SDL_INIT_AUDIO); - } - static SDLAudio& Main() - { - static SDLAudio sdl; - return sdl; - } + ~SDLAudio() + { + SDL_QuitSubSystem(SDL_INIT_AUDIO); + } + static SDLAudio& Main() + { + static SDLAudio sdl; + return sdl; + } }; class AudioOutput_Impl { public: - SDL_AudioSpec m_audioSpec; - SDL_AudioDeviceID m_deviceId = 0; - IMixer* m_mixer = nullptr; - volatile bool m_running = false; + SDL_AudioSpec m_audioSpec; + SDL_AudioDeviceID m_deviceId = 0; + IMixer* m_mixer = nullptr; + volatile bool m_running = false; public: - AudioOutput_Impl() - { + AudioOutput_Impl() + { int32 numAudioDrivers = SDL_GetNumAudioDrivers(); - for(int32 i = 0; i < numAudioDrivers; i++) - { + for (int32 i = 0; i < numAudioDrivers; i++) + { const char* drvName = SDL_GetAudioDriver(i); Logf("Audio driver [%d]: %s", Logger::Severity::Info, i, drvName); - } + } - SDLAudio::Main(); - } - ~AudioOutput_Impl() - { - CloseDevice(); - } - void CloseDevice() - { - if(m_deviceId != 0) - SDL_CloseAudioDevice(m_deviceId); - m_deviceId = 0; - } - bool OpenDevice(const char* dev) - { - CloseDevice(); + SDLAudio::Main(); + } + ~AudioOutput_Impl() + { + CloseDevice(); + } + void CloseDevice() + { + if (m_deviceId != 0) + SDL_CloseAudioDevice(m_deviceId); + m_deviceId = 0; + } + bool OpenDevice(const char* dev) + { + CloseDevice(); - SDL_AudioSpec desiredSpec; - memset(&desiredSpec, 0, sizeof(SDL_AudioSpec)); - memset(&m_audioSpec, 0, sizeof(SDL_AudioSpec)); - desiredSpec.freq = 44100; - desiredSpec.format = AUDIO_F32; - desiredSpec.channels = 2; /* 1 = mono, 2 = stereo */ - desiredSpec.samples = 1024; /* Good low-latency value for callback */ - desiredSpec.callback = (SDL_AudioCallback)&AudioOutput_Impl::FillBuffer; - desiredSpec.userdata = this; + SDL_AudioSpec desiredSpec; + memset(&desiredSpec, 0, sizeof(SDL_AudioSpec)); + memset(&m_audioSpec, 0, sizeof(SDL_AudioSpec)); + desiredSpec.freq = 44100; + desiredSpec.format = AUDIO_F32; + desiredSpec.channels = 2; /* 1 = mono, 2 = stereo */ + desiredSpec.samples = 1024; /* Good low-latency value for callback */ + desiredSpec.callback = (SDL_AudioCallback)&AudioOutput_Impl::FillBuffer; + desiredSpec.userdata = this; - const char* audioDriverName = SDL_GetCurrentAudioDriver(); - Logf("Using audio driver: %s", Logger::Severity::Info, audioDriverName); + const char* audioDriverName = SDL_GetCurrentAudioDriver(); + Logf("Using audio driver: %s", Logger::Severity::Info, audioDriverName); - int32 numAudioDevices = SDL_GetNumAudioDevices(0); - for(int32 i = 0; i < numAudioDevices; i++) - { + int32 numAudioDevices = SDL_GetNumAudioDevices(0); + for (int32 i = 0; i < numAudioDevices; i++) + { const char* devName = SDL_GetAudioDeviceName(i, 0); Logf("Audio device [%d]: %s", Logger::Severity::Info, i, devName); - } - + } - m_deviceId = SDL_OpenAudioDevice(dev, 0, &desiredSpec, &m_audioSpec, SDL_AUDIO_ALLOW_ANY_CHANGE); - if(m_deviceId == 0 || m_deviceId < 2) - { + m_deviceId = SDL_OpenAudioDevice(dev, 0, &desiredSpec, &m_audioSpec, SDL_AUDIO_ALLOW_ANY_CHANGE); + if (m_deviceId == 0 || m_deviceId < 2) + { const char* errMsg = SDL_GetError(); Logf("Failed to open SDL audio device: %s", Logger::Severity::Error, errMsg); - return false; + return false; } - SDL_PauseAudioDevice(m_deviceId, 0); - return true; - } - bool Init() - { - OpenDevice(nullptr); - return true; - } - static void SDLCALL FillBuffer(AudioOutput_Impl* self, float* data, int len) - { - uint32 bufferSamples = (uint32)(len / (4 * self->m_audioSpec.channels)); - if(self->m_mixer) - self->m_mixer->Mix(data, bufferSamples); - } + SDL_PauseAudioDevice(m_deviceId, 0); + return true; + } + bool Init() + { + OpenDevice(nullptr); + return true; + } + static void SDLCALL FillBuffer(AudioOutput_Impl* self, float* data, int len) + { + uint32 bufferSamples = (uint32)(len / (4 * self->m_audioSpec.channels)); + if (self->m_mixer) + self->m_mixer->Mix(data, bufferSamples); + } }; AudioOutput::AudioOutput() { - m_impl = new AudioOutput_Impl(); + m_impl = new AudioOutput_Impl(); } AudioOutput::~AudioOutput() { - delete m_impl; + delete m_impl; } bool AudioOutput::Init(bool exclusive) { - return m_impl->Init(); + return m_impl->Init(); } uint32_t AudioOutput::GetNumChannels() const { - return m_impl->m_audioSpec.channels; + return m_impl->m_audioSpec.channels; } uint32_t AudioOutput::GetSampleRate() const { - return m_impl->m_audioSpec.freq; + return m_impl->m_audioSpec.freq; } double AudioOutput::GetBufferLength() const { - return 0; + return 0; } void AudioOutput::Start(IMixer* mixer) { - m_impl->m_mixer = mixer; + m_impl->m_mixer = mixer; } void AudioOutput::Stop() { - m_impl->m_mixer = nullptr; + m_impl->m_mixer = nullptr; } bool AudioOutput::IsIntegerFormat() const { - return false; + return false; } #endif diff --git a/Audio/src/Windows/AudioOutput_WASAPI.cpp b/Audio/src/Windows/AudioOutput_WASAPI.cpp index 36716bc63..f837d5046 100644 --- a/Audio/src/Windows/AudioOutput_WASAPI.cpp +++ b/Audio/src/Windows/AudioOutput_WASAPI.cpp @@ -10,12 +10,15 @@ #include "Functiondiscoverykeys_devpkey.h" #define REFTIME_NS (100) -#define REFTIMES_PER_MICROSEC (1000/REFTIME_NS) +#define REFTIMES_PER_MICROSEC (1000 / REFTIME_NS) #define REFTIMES_PER_MILLISEC (REFTIMES_PER_MICROSEC * 1000) -#define REFTIMES_PER_SEC (REFTIMES_PER_MILLISEC * 1000) -#define SAFE_RELEASE(punk) \ - if ((punk) != NULL) \ - { (punk)->Release(); (punk) = nullptr; } +#define REFTIMES_PER_SEC (REFTIMES_PER_MILLISEC * 1000) +#define SAFE_RELEASE(punk) \ + if ((punk) != NULL) \ + { \ + (punk)->Release(); \ + (punk) = nullptr; \ + } static const uint32_t freq = 44100; static const uint32_t channels = 2; @@ -29,483 +32,523 @@ static const char* GetDisplayString(HRESULT code); class NotificationClient : public IMMNotificationClient { public: - class AudioOutput_Impl* output; - - virtual HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(_In_ LPCWSTR pwstrDeviceId, _In_ DWORD dwNewState); - virtual HRESULT STDMETHODCALLTYPE OnDeviceAdded(_In_ LPCWSTR pwstrDeviceId); - virtual HRESULT STDMETHODCALLTYPE OnDeviceRemoved(_In_ LPCWSTR pwstrDeviceId); - virtual HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(_In_ EDataFlow flow, _In_ ERole role, _In_ LPCWSTR pwstrDefaultDeviceId); - virtual HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(_In_ LPCWSTR pwstrDeviceId, _In_ const PROPERTYKEY key); - - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) - { - return E_NOINTERFACE; - } - virtual ULONG STDMETHODCALLTYPE AddRef(void) - { - return 0; - } - virtual ULONG STDMETHODCALLTYPE Release(void) - { - return 0; - } + class AudioOutput_Impl* output; + + virtual HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(_In_ LPCWSTR pwstrDeviceId, _In_ DWORD dwNewState); + virtual HRESULT STDMETHODCALLTYPE OnDeviceAdded(_In_ LPCWSTR pwstrDeviceId); + virtual HRESULT STDMETHODCALLTYPE OnDeviceRemoved(_In_ LPCWSTR pwstrDeviceId); + virtual HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(_In_ EDataFlow flow, _In_ ERole role, _In_ LPCWSTR pwstrDefaultDeviceId); + virtual HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(_In_ LPCWSTR pwstrDeviceId, _In_ const PROPERTYKEY key); + + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) + { + return E_NOINTERFACE; + } + virtual ULONG STDMETHODCALLTYPE AddRef(void) + { + return 0; + } + virtual ULONG STDMETHODCALLTYPE Release(void) + { + return 0; + } }; class AudioOutput_Impl { public: - struct IAudioClient* m_audioClient = nullptr; - struct IAudioRenderClient* m_audioRenderClient = nullptr; - struct IMMDevice* m_device = nullptr; - tWAVEFORMATEX m_format; - IMMDeviceEnumerator* m_deviceEnumerator = nullptr; - // The output wave buffer - uint32_t m_numBufferFrames; - - // Object that receives device change notifications - NotificationClient m_notificationClient; - - double m_bufferLength; - - // Dummy audio output - static const uint32 m_dummyChannelCount = 2; - static const uint32 m_dummyBufferLength = (uint32)((double)freq * 0.2); - float m_dummyBuffer[m_dummyBufferLength * m_dummyChannelCount]; - double m_dummyTimerPos; - Timer m_dummyTimer; - - // Set if the device should change soon - IMMDevice* m_pendingDevice = nullptr; - bool m_pendingDeviceChange = false; - - bool m_runAudioThread = false; - Thread m_audioThread; - IMixer* m_mixer = nullptr; - bool m_exclusive = false; + struct IAudioClient* m_audioClient = nullptr; + struct IAudioRenderClient* m_audioRenderClient = nullptr; + struct IMMDevice* m_device = nullptr; + tWAVEFORMATEX m_format; + IMMDeviceEnumerator* m_deviceEnumerator = nullptr; + // The output wave buffer + uint32_t m_numBufferFrames; + + // Object that receives device change notifications + NotificationClient m_notificationClient; + + double m_bufferLength; + + // Dummy audio output + static const uint32 m_dummyChannelCount = 2; + static const uint32 m_dummyBufferLength = (uint32)((double)freq * 0.2); + float m_dummyBuffer[m_dummyBufferLength * m_dummyChannelCount]; + double m_dummyTimerPos; + Timer m_dummyTimer; + + // Set if the device should change soon + IMMDevice* m_pendingDevice = nullptr; + bool m_pendingDeviceChange = false; + + bool m_runAudioThread = false; + Thread m_audioThread; + IMixer* m_mixer = nullptr; + bool m_exclusive = false; public: - AudioOutput_Impl() - { - m_notificationClient.output = this; - - } - ~AudioOutput_Impl() - { - // Stop thread - Stop(); - - CloseDevice(); - - SAFE_RELEASE(m_pendingDevice); - if(m_deviceEnumerator) - { - m_deviceEnumerator->UnregisterEndpointNotificationCallback(&m_notificationClient); - SAFE_RELEASE(m_deviceEnumerator); - } - } - - void Start() - { - if(m_runAudioThread) - return; - - m_runAudioThread = true; - m_audioThread = Thread(&AudioOutput_Impl::AudioThread, this); - } - void Stop() - { - if(!m_runAudioThread) - return; - - // Join audio thread - m_runAudioThread = false; - if(m_audioThread.joinable()) - m_audioThread.join(); - } - - bool Init(bool exclusive) - { - m_exclusive = exclusive; - - // Initialize the WASAPI device enumerator - HRESULT res; - const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); - const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); - CoInitialize(nullptr); - if(!m_deviceEnumerator) - { - res = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&m_deviceEnumerator); - if(res != S_OK) - throw _com_error(res); - - // Register change handler - m_deviceEnumerator->RegisterEndpointNotificationCallback(&m_notificationClient); - } - - // Select default device - IMMDevice* defaultDevice = nullptr; - m_deviceEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eMultimedia, &defaultDevice); - return OpenDevice(defaultDevice); - } - void CloseDevice() - { - if(m_audioClient) - m_audioClient->Stop(); - SAFE_RELEASE(m_device); - SAFE_RELEASE(m_audioClient); - SAFE_RELEASE(m_audioRenderClient); - } - IMMDevice* FindDevice(LPCWSTR devId) - { - assert(m_deviceEnumerator); - IMMDevice* newDevice = nullptr; - if(m_deviceEnumerator->GetDevice(devId, &newDevice) != S_OK) - { - SAFE_RELEASE(newDevice); - return nullptr; - } - return newDevice; - } - bool OpenDevice(LPCWSTR devId) - { - return OpenDevice(FindDevice(devId)); - } - bool OpenDevice(IMMDevice* device) - { - // Close old device first - CloseDevice(); - - // Open dummy device when no device specified - if(!device) - return OpenNullDevice(); - - HRESULT res = 0; - - // Obtain audio client - m_device = device; - res = m_device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&m_audioClient); - if(res != S_OK) - throw _com_error(res); - - WAVEFORMATEX* mixFormat = nullptr; - WAVEFORMATEX* closestFormat = nullptr; - res = m_audioClient->GetMixFormat(&mixFormat); - if (m_exclusive) - { - // Aquire format and initialize device for exclusive mode - REFERENCE_TIME defaultDevicePeriod, minDevicePeriod; - m_audioClient->GetDevicePeriod(&defaultDevicePeriod, &minDevicePeriod); - res = m_audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mixFormat, NULL); - if (res == AUDCLNT_E_UNSUPPORTED_FORMAT) - { - Log("Default format not supported in exclusive mode, attempting other formats", Logger::Severity::Error); - - int numFormats = 2; - WORD formats[2] = { WAVE_FORMAT_PCM, WAVE_FORMAT_IEEE_FLOAT }; - - int numRates = 5; - long sampleRates[5] = {192000L, 96000L, 88200L, 48000L, 44100L}; - - Vector allFormats; - for (size_t f = 0; f < numFormats; f++) - { - WORD bitDepth = 16; - if (formats[f] == WAVE_FORMAT_IEEE_FLOAT) - { - bitDepth = 32; - } - for (size_t r = 0; r < numRates; r++) - { - long avgBytesPerSec = (bitDepth / 8) * sampleRates[r] * 2; - WAVEFORMATEX newformat; - newformat.wFormatTag = formats[f]; - newformat.nChannels = 2; - newformat.nSamplesPerSec = sampleRates[r]; - newformat.nAvgBytesPerSec = avgBytesPerSec; - newformat.nBlockAlign = 4; - newformat.wBitsPerSample = bitDepth; - newformat.cbSize = 0; - allFormats.Add(newformat); - } - } - - int attemptingFormat = 0; - while (res != S_OK) - { - *mixFormat = allFormats[attemptingFormat]; - - Logf("Attempting exclusive mode format:\nSample Rate: %dhz,\nBit Depth: %dbit,\nFormat: %s\n-----", Logger::Severity::Info, - mixFormat->nSamplesPerSec, - mixFormat->wBitsPerSample, - mixFormat->wFormatTag == WAVE_FORMAT_PCM ? "PCM" : "IEEE FLOAT" - ); - - res = m_audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mixFormat, NULL); - attemptingFormat++; - if (attemptingFormat >= allFormats.size()) - { - break; - } - } - if (res == S_OK) - Log("Format found.", Logger::Severity::Info); - else - Log("No accepted format found.", Logger::Severity::Error); - } - // Init client - res = m_audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, 0, - bufferDuration, defaultDevicePeriod, mixFormat, nullptr); - } - else - { - res = m_audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, mixFormat, &closestFormat); - if (res != S_OK) - { - CoTaskMemFree(mixFormat); - mixFormat = closestFormat; - } - // Init client - res = m_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, - bufferDuration, 0, mixFormat, nullptr); - } - // Store selected format - m_format = *mixFormat; - CoTaskMemFree(mixFormat); - - // Check if initialization was succesfull - if(res != S_OK) - { - Logf("Failed to initialize audio client with the selected settings. (%d: %s)", Logger::Severity::Error, res, GetDisplayString(res)); - SAFE_RELEASE(device); - SAFE_RELEASE(m_audioClient); - return false; - } - - // Get the audio render client - res = m_audioClient->GetService(__uuidof(IAudioRenderClient), (void**)&m_audioRenderClient); - if(res != S_OK) - { - Logf("Failed to get audio render client service. (%d: %s)", Logger::Severity::Error, res, GetDisplayString(res)); - SAFE_RELEASE(device); - SAFE_RELEASE(m_audioClient); - return false; - } - - // Get the number of buffer frames - m_audioClient->GetBufferSize(&m_numBufferFrames); - - m_bufferLength = (double)m_numBufferFrames / (double)m_format.nSamplesPerSec; - - res = m_audioClient->Start(); - return true; - } - bool OpenNullDevice() - { - m_format.nSamplesPerSec = freq; - m_format.nChannels = 2; - m_dummyTimer.Restart(); - m_dummyTimerPos = 0; - return true; - } - bool NullBegin(float*& buffer, uint32_t& numSamples) - { - if(m_dummyTimer.Milliseconds() > 2) - { - m_dummyTimerPos += m_dummyTimer.SecondsAsDouble(); - m_dummyTimer.Restart(); - - uint32 availableSamples = (uint32)(m_dummyTimerPos * (double)m_format.nSamplesPerSec); - if(availableSamples > 0) - { - numSamples = Math::Min(m_dummyBufferLength, availableSamples); - - // Restart timer pos - m_dummyTimerPos = 0; - buffer = m_dummyBuffer; - return true; - } - } - return false; - } - - bool Begin(float*& buffer, uint32_t& numSamples) - { - if(m_pendingDeviceChange) - { - OpenDevice(m_pendingDevice); - m_pendingDeviceChange = false; - } - if(!m_device) - return NullBegin(buffer, numSamples); - - // See how much buffer space is available. - uint32_t numFramesPadding; - m_audioClient->GetCurrentPadding(&numFramesPadding); - numSamples = m_numBufferFrames - numFramesPadding; - - if(numSamples > 0) - { - // Grab all the available space in the shared buffer. - HRESULT hr = m_audioRenderClient->GetBuffer(numSamples, (BYTE**)&buffer); - if(hr != S_OK) - { - if(hr == AUDCLNT_E_DEVICE_INVALIDATED) - { - Logf("Audio device unplugged", Logger::Severity::Warning); - return false; - } - else - { - assert(false); - } - } - return true; - } - return false; - } - void End(uint32_t numSamples) - { - if(!m_device) - return; - - if(numSamples > 0) - { - m_audioRenderClient->ReleaseBuffer(numSamples, 0); - } - } - - // Main mixer thread - void AudioThread() - { - while(m_runAudioThread) - { - int32 sleepDuration = 1; - float* data; - uint32 numSamples; - if(Begin(data, numSamples)) - { - if(m_mixer) - m_mixer->Mix(data, numSamples); - End(numSamples); - } - std::this_thread::sleep_for(std::chrono::microseconds(100)); - } - } + AudioOutput_Impl() + { + m_notificationClient.output = this; + } + ~AudioOutput_Impl() + { + // Stop thread + Stop(); + + CloseDevice(); + + SAFE_RELEASE(m_pendingDevice); + if (m_deviceEnumerator) + { + m_deviceEnumerator->UnregisterEndpointNotificationCallback(&m_notificationClient); + SAFE_RELEASE(m_deviceEnumerator); + } + } + + void Start() + { + if (m_runAudioThread) + return; + + m_runAudioThread = true; + m_audioThread = Thread(&AudioOutput_Impl::AudioThread, this); + } + void Stop() + { + if (!m_runAudioThread) + return; + + // Join audio thread + m_runAudioThread = false; + if (m_audioThread.joinable()) + m_audioThread.join(); + } + + bool Init(bool exclusive) + { + m_exclusive = exclusive; + + // Initialize the WASAPI device enumerator + HRESULT res; + const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); + const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); + CoInitialize(nullptr); + if (!m_deviceEnumerator) + { + res = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&m_deviceEnumerator); + if (res != S_OK) + throw _com_error(res); + + // Register change handler + m_deviceEnumerator->RegisterEndpointNotificationCallback(&m_notificationClient); + } + + // Select default device + IMMDevice* defaultDevice = nullptr; + m_deviceEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eMultimedia, &defaultDevice); + return OpenDevice(defaultDevice); + } + void CloseDevice() + { + if (m_audioClient) + m_audioClient->Stop(); + SAFE_RELEASE(m_device); + SAFE_RELEASE(m_audioClient); + SAFE_RELEASE(m_audioRenderClient); + } + IMMDevice* FindDevice(LPCWSTR devId) + { + assert(m_deviceEnumerator); + IMMDevice* newDevice = nullptr; + if (m_deviceEnumerator->GetDevice(devId, &newDevice) != S_OK) + { + SAFE_RELEASE(newDevice); + return nullptr; + } + return newDevice; + } + bool OpenDevice(LPCWSTR devId) + { + return OpenDevice(FindDevice(devId)); + } + bool OpenDevice(IMMDevice* device) + { + // Close old device first + CloseDevice(); + + // Open dummy device when no device specified + if (!device) + return OpenNullDevice(); + + HRESULT res = 0; + + // Obtain audio client + m_device = device; + res = m_device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&m_audioClient); + if (res != S_OK) + throw _com_error(res); + + WAVEFORMATEX* mixFormat = nullptr; + WAVEFORMATEX* closestFormat = nullptr; + res = m_audioClient->GetMixFormat(&mixFormat); + if (m_exclusive) + { + // Aquire format and initialize device for exclusive mode + REFERENCE_TIME defaultDevicePeriod, minDevicePeriod; + m_audioClient->GetDevicePeriod(&defaultDevicePeriod, &minDevicePeriod); + res = m_audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mixFormat, NULL); + if (res == AUDCLNT_E_UNSUPPORTED_FORMAT) + { + Log("Default format not supported in exclusive mode, attempting other formats", Logger::Severity::Error); + + int numFormats = 2; + WORD formats[2] = { WAVE_FORMAT_PCM, WAVE_FORMAT_IEEE_FLOAT }; + + int numRates = 5; + long sampleRates[5] = { 192000L, 96000L, 88200L, 48000L, 44100L }; + + Vector allFormats; + for (size_t f = 0; f < numFormats; f++) + { + WORD bitDepth = 16; + if (formats[f] == WAVE_FORMAT_IEEE_FLOAT) + { + bitDepth = 32; + } + for (size_t r = 0; r < numRates; r++) + { + long avgBytesPerSec = (bitDepth / 8) * sampleRates[r] * 2; + WAVEFORMATEX newformat; + newformat.wFormatTag = formats[f]; + newformat.nChannels = 2; + newformat.nSamplesPerSec = sampleRates[r]; + newformat.nAvgBytesPerSec = avgBytesPerSec; + newformat.nBlockAlign = 4; + newformat.wBitsPerSample = bitDepth; + newformat.cbSize = 0; + allFormats.Add(newformat); + } + } + + int attemptingFormat = 0; + while (res != S_OK) + { + *mixFormat = allFormats[attemptingFormat]; + + Logf("Attempting exclusive mode format:\nSample Rate: %dhz,\nBit Depth: %dbit,\nFormat: %s\n-----", Logger::Severity::Info, + mixFormat->nSamplesPerSec, + mixFormat->wBitsPerSample, + mixFormat->wFormatTag == WAVE_FORMAT_PCM ? "PCM" : "IEEE FLOAT"); + + res = m_audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mixFormat, NULL); + attemptingFormat++; + if (attemptingFormat >= allFormats.size()) + { + break; + } + } + if (res == S_OK) + Log("Format found.", Logger::Severity::Info); + else + Log("No accepted format found.", Logger::Severity::Error); + } + // Init client + res = m_audioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, 0, + bufferDuration, defaultDevicePeriod, mixFormat, nullptr); + } + else + { + res = m_audioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, mixFormat, &closestFormat); + if (res != S_OK) + { + CoTaskMemFree(mixFormat); + mixFormat = closestFormat; + } + // Init client + res = m_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, + bufferDuration, 0, mixFormat, nullptr); + } + // Store selected format + m_format = *mixFormat; + CoTaskMemFree(mixFormat); + + // Check if initialization was succesfull + if (res != S_OK) + { + Logf("Failed to initialize audio client with the selected settings. (%d: %s)", Logger::Severity::Error, res, GetDisplayString(res)); + SAFE_RELEASE(device); + SAFE_RELEASE(m_audioClient); + return false; + } + + // Get the audio render client + res = m_audioClient->GetService(__uuidof(IAudioRenderClient), (void**)&m_audioRenderClient); + if (res != S_OK) + { + Logf("Failed to get audio render client service. (%d: %s)", Logger::Severity::Error, res, GetDisplayString(res)); + SAFE_RELEASE(device); + SAFE_RELEASE(m_audioClient); + return false; + } + + // Get the number of buffer frames + m_audioClient->GetBufferSize(&m_numBufferFrames); + + m_bufferLength = (double)m_numBufferFrames / (double)m_format.nSamplesPerSec; + + res = m_audioClient->Start(); + return true; + } + bool OpenNullDevice() + { + m_format.nSamplesPerSec = freq; + m_format.nChannels = 2; + m_dummyTimer.Restart(); + m_dummyTimerPos = 0; + return true; + } + bool NullBegin(float*& buffer, uint32_t& numSamples) + { + if (m_dummyTimer.Milliseconds() > 2) + { + m_dummyTimerPos += m_dummyTimer.SecondsAsDouble(); + m_dummyTimer.Restart(); + + uint32 availableSamples = (uint32)(m_dummyTimerPos * (double)m_format.nSamplesPerSec); + if (availableSamples > 0) + { + numSamples = Math::Min(m_dummyBufferLength, availableSamples); + + // Restart timer pos + m_dummyTimerPos = 0; + buffer = m_dummyBuffer; + return true; + } + } + return false; + } + + bool Begin(float*& buffer, uint32_t& numSamples) + { + if (m_pendingDeviceChange) + { + OpenDevice(m_pendingDevice); + m_pendingDeviceChange = false; + } + if (!m_device) + return NullBegin(buffer, numSamples); + + // See how much buffer space is available. + uint32_t numFramesPadding; + m_audioClient->GetCurrentPadding(&numFramesPadding); + numSamples = m_numBufferFrames - numFramesPadding; + + if (numSamples > 0) + { + // Grab all the available space in the shared buffer. + HRESULT hr = m_audioRenderClient->GetBuffer(numSamples, (BYTE**)&buffer); + if (hr != S_OK) + { + if (hr == AUDCLNT_E_DEVICE_INVALIDATED) + { + Logf("Audio device unplugged", Logger::Severity::Warning); + return false; + } + else + { + assert(false); + } + } + return true; + } + return false; + } + void End(uint32_t numSamples) + { + if (!m_device) + return; + + if (numSamples > 0) + { + m_audioRenderClient->ReleaseBuffer(numSamples, 0); + } + } + + // Main mixer thread + void AudioThread() + { + while (m_runAudioThread) + { + int32 sleepDuration = 1; + float* data; + uint32 numSamples; + if (Begin(data, numSamples)) + { + if (m_mixer) + m_mixer->Mix(data, numSamples); + End(numSamples); + } + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + } }; /* Audio change notifications */ HRESULT STDMETHODCALLTYPE NotificationClient::OnDeviceStateChanged(_In_ LPCWSTR pwstrDeviceId, _In_ DWORD dwNewState) { - return S_OK; + return S_OK; } HRESULT STDMETHODCALLTYPE NotificationClient::OnDeviceAdded(_In_ LPCWSTR pwstrDeviceId) { - return S_OK; + return S_OK; } HRESULT STDMETHODCALLTYPE NotificationClient::OnDeviceRemoved(_In_ LPCWSTR pwstrDeviceId) { - return S_OK; + return S_OK; } HRESULT STDMETHODCALLTYPE NotificationClient::OnDefaultDeviceChanged(_In_ EDataFlow flow, _In_ ERole role, _In_ LPCWSTR pwstrDefaultDeviceId) { - if(flow == EDataFlow::eRender && role == ERole::eMultimedia) - { - output->m_pendingDeviceChange = true; - output->m_pendingDevice = output->FindDevice(pwstrDefaultDeviceId); - } - return S_OK; + if (flow == EDataFlow::eRender && role == ERole::eMultimedia) + { + output->m_pendingDeviceChange = true; + output->m_pendingDevice = output->FindDevice(pwstrDefaultDeviceId); + } + return S_OK; } HRESULT STDMETHODCALLTYPE NotificationClient::OnPropertyValueChanged(_In_ LPCWSTR pwstrDeviceId, _In_ const PROPERTYKEY key) { - return S_OK; + return S_OK; } AudioOutput::AudioOutput() { - m_impl = new AudioOutput_Impl(); + m_impl = new AudioOutput_Impl(); } AudioOutput::~AudioOutput() { - delete m_impl; + delete m_impl; } bool AudioOutput::Init(bool exclusive) { - return m_impl->Init(exclusive); + return m_impl->Init(exclusive); } void AudioOutput::Start(IMixer* mixer) { - m_impl->m_mixer = mixer; - m_impl->Start(); + m_impl->m_mixer = mixer; + m_impl->Start(); } void AudioOutput::Stop() { - m_impl->Stop(); - m_impl->m_mixer = nullptr; + m_impl->Stop(); + m_impl->m_mixer = nullptr; } uint32_t AudioOutput::GetNumChannels() const { - return m_impl->m_format.nChannels; + return m_impl->m_format.nChannels; } uint32_t AudioOutput::GetSampleRate() const { - return m_impl->m_format.nSamplesPerSec; + return m_impl->m_format.nSamplesPerSec; } double AudioOutput::GetBufferLength() const { - return m_impl->m_bufferLength; + return m_impl->m_bufferLength; } bool AudioOutput::IsIntegerFormat() const { - ///TODO: check more cases? - return m_impl->m_format.wFormatTag == WAVE_FORMAT_PCM && m_impl->m_format.wBitsPerSample != 32; + /// TODO: check more cases? + return m_impl->m_format.wFormatTag == WAVE_FORMAT_PCM && m_impl->m_format.wBitsPerSample != 32; } static const char* GetDisplayString(HRESULT code) { - switch (code) - { - case S_OK: return "S_OK"; - case S_FALSE: return "S_FALSE"; - case AUDCLNT_E_NOT_INITIALIZED: return "AUDCLNT_E_NOT_INITIALIZED"; - case AUDCLNT_E_ALREADY_INITIALIZED: return "AUDCLNT_E_ALREADY_INITIALIZED"; - case AUDCLNT_E_WRONG_ENDPOINT_TYPE: return "AUDCLNT_E_WRONG_ENDPOINT_TYPE"; - case AUDCLNT_E_DEVICE_INVALIDATED: return "AUDCLNT_E_DEVICE_INVALIDATED"; - case AUDCLNT_E_NOT_STOPPED: return "AUDCLNT_E_NOT_STOPPED"; - case AUDCLNT_E_BUFFER_TOO_LARGE: return "AUDCLNT_E_BUFFER_TOO_LARGE"; - case AUDCLNT_E_OUT_OF_ORDER: return "AUDCLNT_E_OUT_OF_ORDER"; - case AUDCLNT_E_UNSUPPORTED_FORMAT: return "AUDCLNT_E_UNSUPPORTED_FORMAT"; - case AUDCLNT_E_INVALID_SIZE: return "AUDCLNT_E_INVALID_SIZE"; - case AUDCLNT_E_DEVICE_IN_USE: return "AUDCLNT_E_DEVICE_IN_USE"; - case AUDCLNT_E_BUFFER_OPERATION_PENDING: return "AUDCLNT_E_BUFFER_OPERATION_PENDING"; - case AUDCLNT_E_THREAD_NOT_REGISTERED: return "AUDCLNT_E_THREAD_NOT_REGISTERED"; - case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: return "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED"; - case AUDCLNT_E_ENDPOINT_CREATE_FAILED: return "AUDCLNT_E_ENDPOINT_CREATE_FAILED"; - case AUDCLNT_E_SERVICE_NOT_RUNNING: return "AUDCLNT_E_SERVICE_NOT_RUNNING"; - case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: return "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED"; - case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: return "AUDCLNT_E_EXCLUSIVE_MODE_ONLY"; - case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: return "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"; - case AUDCLNT_E_EVENTHANDLE_NOT_SET: return "AUDCLNT_E_EVENTHANDLE_NOT_SET"; - case AUDCLNT_E_INCORRECT_BUFFER_SIZE: return "AUDCLNT_E_INCORRECT_BUFFER_SIZE"; - case AUDCLNT_E_BUFFER_SIZE_ERROR: return "AUDCLNT_E_BUFFER_SIZE_ERROR"; - case AUDCLNT_E_CPUUSAGE_EXCEEDED: return "AUDCLNT_E_CPUUSAGE_EXCEEDED"; - case AUDCLNT_E_BUFFER_ERROR: return "AUDCLNT_E_BUFFER_ERROR"; - case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: return "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED"; - case AUDCLNT_E_INVALID_DEVICE_PERIOD: return "AUDCLNT_E_INVALID_DEVICE_PERIOD"; - case AUDCLNT_E_INVALID_STREAM_FLAG: return "AUDCLNT_E_INVALID_STREAM_FLAG"; - case AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE: return "AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE"; - case AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES: return "AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES"; - case AUDCLNT_E_OFFLOAD_MODE_ONLY: return "AUDCLNT_E_OFFLOAD_MODE_ONLY"; - case AUDCLNT_E_NONOFFLOAD_MODE_ONLY: return "AUDCLNT_E_NONOFFLOAD_MODE_ONLY"; - case AUDCLNT_E_RESOURCES_INVALIDATED: return "AUDCLNT_E_RESOURCES_INVALIDATED"; - case AUDCLNT_E_RAW_MODE_UNSUPPORTED: return "AUDCLNT_E_RAW_MODE_UNSUPPORTED"; - case AUDCLNT_E_ENGINE_PERIODICITY_LOCKED: return "AUDCLNT_E_ENGINE_PERIODICITY_LOCKED"; - case AUDCLNT_E_ENGINE_FORMAT_LOCKED: return "AUDCLNT_E_ENGINE_FORMAT_LOCKED"; - case AUDCLNT_E_HEADTRACKING_ENABLED: return "AUDCLNT_E_HEADTRACKING_ENABLED"; - case AUDCLNT_E_HEADTRACKING_UNSUPPORTED: return "AUDCLNT_E_HEADTRACKING_UNSUPPORTED"; - case AUDCLNT_S_BUFFER_EMPTY: return "AUDCLNT_S_BUFFER_EMPTY"; - case AUDCLNT_S_THREAD_ALREADY_REGISTERED: return "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; - case AUDCLNT_S_POSITION_STALLED: return "AUDCLNT_S_POSITION_STALLED"; - default: return "UNKNOWN"; - } + switch (code) + { + case S_OK: + return "S_OK"; + case S_FALSE: + return "S_FALSE"; + case AUDCLNT_E_NOT_INITIALIZED: + return "AUDCLNT_E_NOT_INITIALIZED"; + case AUDCLNT_E_ALREADY_INITIALIZED: + return "AUDCLNT_E_ALREADY_INITIALIZED"; + case AUDCLNT_E_WRONG_ENDPOINT_TYPE: + return "AUDCLNT_E_WRONG_ENDPOINT_TYPE"; + case AUDCLNT_E_DEVICE_INVALIDATED: + return "AUDCLNT_E_DEVICE_INVALIDATED"; + case AUDCLNT_E_NOT_STOPPED: + return "AUDCLNT_E_NOT_STOPPED"; + case AUDCLNT_E_BUFFER_TOO_LARGE: + return "AUDCLNT_E_BUFFER_TOO_LARGE"; + case AUDCLNT_E_OUT_OF_ORDER: + return "AUDCLNT_E_OUT_OF_ORDER"; + case AUDCLNT_E_UNSUPPORTED_FORMAT: + return "AUDCLNT_E_UNSUPPORTED_FORMAT"; + case AUDCLNT_E_INVALID_SIZE: + return "AUDCLNT_E_INVALID_SIZE"; + case AUDCLNT_E_DEVICE_IN_USE: + return "AUDCLNT_E_DEVICE_IN_USE"; + case AUDCLNT_E_BUFFER_OPERATION_PENDING: + return "AUDCLNT_E_BUFFER_OPERATION_PENDING"; + case AUDCLNT_E_THREAD_NOT_REGISTERED: + return "AUDCLNT_E_THREAD_NOT_REGISTERED"; + case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: + return "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED"; + case AUDCLNT_E_ENDPOINT_CREATE_FAILED: + return "AUDCLNT_E_ENDPOINT_CREATE_FAILED"; + case AUDCLNT_E_SERVICE_NOT_RUNNING: + return "AUDCLNT_E_SERVICE_NOT_RUNNING"; + case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: + return "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED"; + case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: + return "AUDCLNT_E_EXCLUSIVE_MODE_ONLY"; + case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: + return "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"; + case AUDCLNT_E_EVENTHANDLE_NOT_SET: + return "AUDCLNT_E_EVENTHANDLE_NOT_SET"; + case AUDCLNT_E_INCORRECT_BUFFER_SIZE: + return "AUDCLNT_E_INCORRECT_BUFFER_SIZE"; + case AUDCLNT_E_BUFFER_SIZE_ERROR: + return "AUDCLNT_E_BUFFER_SIZE_ERROR"; + case AUDCLNT_E_CPUUSAGE_EXCEEDED: + return "AUDCLNT_E_CPUUSAGE_EXCEEDED"; + case AUDCLNT_E_BUFFER_ERROR: + return "AUDCLNT_E_BUFFER_ERROR"; + case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: + return "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED"; + case AUDCLNT_E_INVALID_DEVICE_PERIOD: + return "AUDCLNT_E_INVALID_DEVICE_PERIOD"; + case AUDCLNT_E_INVALID_STREAM_FLAG: + return "AUDCLNT_E_INVALID_STREAM_FLAG"; + case AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE: + return "AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE"; + case AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES: + return "AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES"; + case AUDCLNT_E_OFFLOAD_MODE_ONLY: + return "AUDCLNT_E_OFFLOAD_MODE_ONLY"; + case AUDCLNT_E_NONOFFLOAD_MODE_ONLY: + return "AUDCLNT_E_NONOFFLOAD_MODE_ONLY"; + case AUDCLNT_E_RESOURCES_INVALIDATED: + return "AUDCLNT_E_RESOURCES_INVALIDATED"; + case AUDCLNT_E_RAW_MODE_UNSUPPORTED: + return "AUDCLNT_E_RAW_MODE_UNSUPPORTED"; + case AUDCLNT_E_ENGINE_PERIODICITY_LOCKED: + return "AUDCLNT_E_ENGINE_PERIODICITY_LOCKED"; + case AUDCLNT_E_ENGINE_FORMAT_LOCKED: + return "AUDCLNT_E_ENGINE_FORMAT_LOCKED"; + case AUDCLNT_E_HEADTRACKING_ENABLED: + return "AUDCLNT_E_HEADTRACKING_ENABLED"; + case AUDCLNT_E_HEADTRACKING_UNSUPPORTED: + return "AUDCLNT_E_HEADTRACKING_UNSUPPORTED"; + case AUDCLNT_S_BUFFER_EMPTY: + return "AUDCLNT_S_BUFFER_EMPTY"; + case AUDCLNT_S_THREAD_ALREADY_REGISTERED: + return "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; + case AUDCLNT_S_POSITION_STALLED: + return "AUDCLNT_S_POSITION_STALLED"; + default: + return "UNKNOWN"; + } } -#endif \ No newline at end of file +#endif diff --git a/Audio/stdafx.cpp b/Audio/stdafx.cpp index 1577c4e3b..fd4f341c7 100644 --- a/Audio/stdafx.cpp +++ b/Audio/stdafx.cpp @@ -1 +1 @@ -#include "stdafx.h" \ No newline at end of file +#include "stdafx.h" diff --git a/Beatmap/include/Beatmap/AudioEffects.hpp b/Beatmap/include/Beatmap/AudioEffects.hpp index 0a979093b..eacc352d0 100644 --- a/Beatmap/include/Beatmap/AudioEffects.hpp +++ b/Beatmap/include/Beatmap/AudioEffects.hpp @@ -1,5 +1,5 @@ /* - Contains a list of game specific audio effect types + Contains a list of game specific audio effect types */ #pragma once #include @@ -7,251 +7,251 @@ // The types of effects that can be used on the effect buttons and on lasers DefineEnum(EffectType, - None = 0, - Retrigger, - Flanger, - Phaser, - Gate, - TapeStop, - Bitcrush, - Wobble, - SideChain, - Echo, - Panning, - PitchShift, - LowPassFilter, - HighPassFilter, - PeakingFilter, - SwitchAudio, // Not a real effect - UserDefined0 = 0x40, // This ID or higher is user for user defined effects inside map objects - UserDefined1, // Keep this ID at least a few ID's away from the normal effect so more native effects can be added later - UserDefined2, - UserDefined3, - UserDefined4, - UserDefined5, - UserDefined6, - UserDefined7, - UserDefined8, - UserDefined9 // etc... - ) + None = 0, + Retrigger, + Flanger, + Phaser, + Gate, + TapeStop, + Bitcrush, + Wobble, + SideChain, + Echo, + Panning, + PitchShift, + LowPassFilter, + HighPassFilter, + PeakingFilter, + SwitchAudio, // Not a real effect + UserDefined0 = 0x40, // This ID or higher is user for user defined effects inside map objects + UserDefined1, // Keep this ID at least a few ID's away from the normal effect so more native effects can be added later + UserDefined2, + UserDefined3, + UserDefined4, + UserDefined5, + UserDefined6, + UserDefined7, + UserDefined8, + UserDefined9 // etc... +) - /* - Effect parameter that is used to define a certain time range/period/speed +/* +Effect parameter that is used to define a certain time range/period/speed */ - class EffectDuration +class EffectDuration { public: - EffectDuration() = default; - // Duration in milliseconds - EffectDuration(int32 duration); - // Duration relative to whole note duration - EffectDuration(float rate); + EffectDuration() = default; + // Duration in milliseconds + EffectDuration(int32 duration); + // Duration relative to whole note duration + EffectDuration(float rate); - static EffectDuration Lerp(const EffectDuration &lhs, const EffectDuration &rhs, float time); + static EffectDuration Lerp(const EffectDuration& lhs, const EffectDuration& rhs, float time); - // Convert to ms duration - // pass in the whole note duration - uint32 Absolute(double noteDuration); + // Convert to ms duration + // pass in the whole note duration + uint32 Absolute(double noteDuration); - // Either a float or integer value - union - { - float rate; - int32 duration; - }; + // Either a float or integer value + union + { + float rate; + int32 duration; + }; - // The type of timing that the value represents - enum Type : uint8 - { - Rate, // Relative (1/4, 1/2, 0.5, etc), all relative to whole notes - Time, // Absolute, in milliseconds - }; - Type type; + // The type of timing that the value represents + enum Type : uint8 + { + Rate, // Relative (1/4, 1/2, 0.5, etc), all relative to whole notes + Time, // Absolute, in milliseconds + }; + Type type; }; template T InterpolateEffectParamValue(T a, T b, float t) { - return T(a + (b - a) * t); + return T(a + (b - a) * t); } EffectDuration InterpolateEffectParamValue(EffectDuration a, EffectDuration b, float t); /* - Effect parameter that allows all the values which can be set for effects + Effect parameter that allows all the values which can be set for effects */ template class EffectParam { public: - EffectParam() = default; - EffectParam(T value) - { - values[0] = value; - values[1] = value; - isRange = false; - timeFunction = Interpolation::Linear; - } - EffectParam(T valueA, T valueB, Interpolation::TimeFunction timeFunction = Interpolation::Linear) - { - values[0] = valueA; - values[1] = valueB; - this->timeFunction = timeFunction; - isRange = true; - } + EffectParam() = default; + EffectParam(T value) + { + values[0] = value; + values[1] = value; + isRange = false; + timeFunction = Interpolation::Linear; + } + EffectParam(T valueA, T valueB, Interpolation::TimeFunction timeFunction = Interpolation::Linear) + { + values[0] = valueA; + values[1] = valueB; + this->timeFunction = timeFunction; + isRange = true; + } - // Sample based on laser input, or without parameters for just the actual value - T Sample(float t = 0.0f) const - { - t = Math::Clamp(timeFunction(t), 0.0f, 1.0f); - return isRange ? InterpolateEffectParamValue(values[0], values[1], t) : values[0]; - } + // Sample based on laser input, or without parameters for just the actual value + T Sample(float t = 0.0f) const + { + t = Math::Clamp(timeFunction(t), 0.0f, 1.0f); + return isRange ? InterpolateEffectParamValue(values[0], values[1], t) : values[0]; + } - Interpolation::TimeFunction timeFunction; + Interpolation::TimeFunction timeFunction; - // Either 1 or 2 values based on if this value should be interpolated by laser input or not - T values[2]; + // Either 1 or 2 values based on if this value should be interpolated by laser input or not + T values[2]; - // When set to true, this means the parameter is a range - bool isRange; + // When set to true, this means the parameter is a range + bool isRange; }; struct AudioEffect { - // Use this to get default effect settings - static const AudioEffect &GetDefault(EffectType type); - static int GetDefaultEffectPriority(EffectType type); + // Use this to get default effect settings + static const AudioEffect& GetDefault(EffectType type); + static int GetDefaultEffectPriority(EffectType type); - void SetDefaultEffectParams(int16 *params); + void SetDefaultEffectParams(int16* params); - // The effect type - EffectType type = EffectType::None; + // The effect type + EffectType type = EffectType::None; - // Timing division for time based effects - // Wobble: length of single cycle - // Phaser: length of single cycle - // Flanger: length of single cycle - // Tapestop: duration of stop - // Gate: length of a single - // Sidechain: duration before reset - // Echo: delay - EffectParam duration = EffectDuration(0.25f); // 1/4 + // Timing division for time based effects + // Wobble: length of single cycle + // Phaser: length of single cycle + // Flanger: length of single cycle + // Tapestop: duration of stop + // Gate: length of a single + // Sidechain: duration before reset + // Echo: delay + EffectParam duration = EffectDuration(0.25f); // 1/4 - // How much of the effect is mixed in with the source audio - EffectParam mix = 0.0f; + // How much of the effect is mixed in with the source audio + EffectParam mix = 0.0f; - union - { - struct - { - // Amount of gating on this effect (0-1) - EffectParam gate; - // Duration after which the retriggered sample area resets - // 0 for no reset - // TODO: This parameter allows this effect to be merged with gate - EffectParam reset; - } retrigger; - struct - { - // Amount of gating on this effect (0-1) - EffectParam gate; - } gate; - struct - { - // Number of samples that is offset from the source audio to create the flanging effect (Samples) - EffectParam offset; - // Depth of the effect (samples) - EffectParam depth; - // Feedback (0-1) - EffectParam feedback; - // Stereo width (0-1) - EffectParam stereoWidth; - // Volume of added source audio + delayed source audio (0-1) - EffectParam volume; - } flanger; - struct - { - // Number of stages - EffectParam stage; - // Minimum frequency (Hz) - EffectParam min; - // Maximum frequency (Hz) - EffectParam max; - // Q factor (0-1) - EffectParam q; - // Feedback (0-1) - EffectParam feedback; - // Stereo width (0-1) - EffectParam stereoWidth; - // High cut gain (dB) - EffectParam hiCutGain; - } phaser; - struct - { - // The duration in samples of how long a sample in the source audio gets reduced (creating square waves) (samples) - EffectParam reduction; - } bitcrusher; - struct - { - // Top frequency of the wobble (Hz) - EffectParam max; - // Bottom frequency of the wobble (Hz) - EffectParam min; - // Q factor for filter (>0) - EffectParam q; - } wobble; - struct - { - // Time to fully reduce volume - EffectParam attackTime; - // Time to keep volume at maximum reduced value - EffectParam holdTime; - // Time to restore volume - EffectParam releaseTime; - // Ratio to reduce volume by - EffectParam ratio; - } sidechain; - struct - { - // Ammount of echo (0-1) - EffectParam feedback; - } echo; - struct - { - // Panning position, 0 is center (-1-1) - EffectParam panning; - } panning; - struct - { - // Pitch shift amount, in semitones - EffectParam amount; - } pitchshift; - struct - { - // Q factor for filter (>0) - EffectParam q; - // Cuttoff frequency (Hz) - EffectParam freq; - } lpf; - struct - { - // Q factor for filter (>0) - EffectParam q; - // Cuttoff frequency (Hz) - EffectParam freq; - } hpf; - struct - { - // Peak amplification (>=0) - EffectParam gain; - // Q factor for filter (>0) - EffectParam q; - // Cuttoff frequency (Hz) - EffectParam freq; - } peaking; - struct - { - // Audio Index - EffectParam index; - } switchaudio; - }; + union + { + struct + { + // Amount of gating on this effect (0-1) + EffectParam gate; + // Duration after which the retriggered sample area resets + // 0 for no reset + // TODO: This parameter allows this effect to be merged with gate + EffectParam reset; + } retrigger; + struct + { + // Amount of gating on this effect (0-1) + EffectParam gate; + } gate; + struct + { + // Number of samples that is offset from the source audio to create the flanging effect (Samples) + EffectParam offset; + // Depth of the effect (samples) + EffectParam depth; + // Feedback (0-1) + EffectParam feedback; + // Stereo width (0-1) + EffectParam stereoWidth; + // Volume of added source audio + delayed source audio (0-1) + EffectParam volume; + } flanger; + struct + { + // Number of stages + EffectParam stage; + // Minimum frequency (Hz) + EffectParam min; + // Maximum frequency (Hz) + EffectParam max; + // Q factor (0-1) + EffectParam q; + // Feedback (0-1) + EffectParam feedback; + // Stereo width (0-1) + EffectParam stereoWidth; + // High cut gain (dB) + EffectParam hiCutGain; + } phaser; + struct + { + // The duration in samples of how long a sample in the source audio gets reduced (creating square waves) (samples) + EffectParam reduction; + } bitcrusher; + struct + { + // Top frequency of the wobble (Hz) + EffectParam max; + // Bottom frequency of the wobble (Hz) + EffectParam min; + // Q factor for filter (>0) + EffectParam q; + } wobble; + struct + { + // Time to fully reduce volume + EffectParam attackTime; + // Time to keep volume at maximum reduced value + EffectParam holdTime; + // Time to restore volume + EffectParam releaseTime; + // Ratio to reduce volume by + EffectParam ratio; + } sidechain; + struct + { + // Ammount of echo (0-1) + EffectParam feedback; + } echo; + struct + { + // Panning position, 0 is center (-1-1) + EffectParam panning; + } panning; + struct + { + // Pitch shift amount, in semitones + EffectParam amount; + } pitchshift; + struct + { + // Q factor for filter (>0) + EffectParam q; + // Cuttoff frequency (Hz) + EffectParam freq; + } lpf; + struct + { + // Q factor for filter (>0) + EffectParam q; + // Cuttoff frequency (Hz) + EffectParam freq; + } hpf; + struct + { + // Peak amplification (>=0) + EffectParam gain; + // Q factor for filter (>0) + EffectParam q; + // Cuttoff frequency (Hz) + EffectParam freq; + } peaking; + struct + { + // Audio Index + EffectParam index; + } switchaudio; + }; }; diff --git a/Beatmap/include/Beatmap/Beatmap.hpp b/Beatmap/include/Beatmap/Beatmap.hpp index 345a22f0f..b36a06e89 100644 --- a/Beatmap/include/Beatmap/Beatmap.hpp +++ b/Beatmap/include/Beatmap/Beatmap.hpp @@ -6,171 +6,171 @@ /* Global settings stored in a beatmap */ struct BeatmapSettings { - static bool StaticSerialize(BinaryStream& stream, BeatmapSettings*& settings); - - // Basic song meta data - String title; - String artist; - String effector; - String illustrator; - String tags; - // Reported BPM range by the map - String bpm; - // Offset in ms for the map to start - MapTime offset; - // Both audio tracks specified for the map / if any is set - String audioNoFX; - String audioFX; - // Path to the jacket image - String jacketPath; - // Path to the background and foreground shader files - String backgroundPath; - String foregroundPath; - - // Level, as indicated by map creator - uint8 level; - - // Difficulty, as indicated by map creator - uint8 difficulty; - - // Total, total gauge gained when played perfectly - uint16 total = 0; - - // Preview offset - MapTime previewOffset; - // Preview duration - MapTime previewDuration; - - // Initial audio settings - float slamVolume = 1.0f; - float laserEffectMix = 1.0f; - float musicVolume = 1.0f; - - // BPM Override for mmod calculation - float speedBpm = -1.0f; - - EffectType laserEffectType = EffectType::PeakingFilter; + static bool StaticSerialize(BinaryStream& stream, BeatmapSettings*& settings); + + // Basic song meta data + String title; + String artist; + String effector; + String illustrator; + String tags; + // Reported BPM range by the map + String bpm; + // Offset in ms for the map to start + MapTime offset; + // Both audio tracks specified for the map / if any is set + String audioNoFX; + String audioFX; + // Path to the jacket image + String jacketPath; + // Path to the background and foreground shader files + String backgroundPath; + String foregroundPath; + + // Level, as indicated by map creator + uint8 level; + + // Difficulty, as indicated by map creator + uint8 difficulty; + + // Total, total gauge gained when played perfectly + uint16 total = 0; + + // Preview offset + MapTime previewOffset; + // Preview duration + MapTime previewDuration; + + // Initial audio settings + float slamVolume = 1.0f; + float laserEffectMix = 1.0f; + float musicVolume = 1.0f; + + // BPM Override for mmod calculation + float speedBpm = -1.0f; + + EffectType laserEffectType = EffectType::PeakingFilter; }; /* - Generic beatmap format, Can either load it's own format or KShoot maps + Generic beatmap format, Can either load it's own format or KShoot maps */ class Beatmap : public Unique { public: - // Vector interacts badly with unique_ptr, so std::vector was used instead. - using Objects = std::vector>; - using ObjectsIterator = Objects::const_iterator; + // Vector interacts badly with unique_ptr, so std::vector was used instead. + using Objects = std::vector>; + using ObjectsIterator = Objects::const_iterator; - using TimingPoints = Vector; - using TimingPointsIterator = TimingPoints::const_iterator; + using TimingPoints = Vector; + using TimingPointsIterator = TimingPoints::const_iterator; - using LaneTogglePoints = Vector; - using LaneTogglePointsIterator = LaneTogglePoints::const_iterator; + using LaneTogglePoints = Vector; + using LaneTogglePointsIterator = LaneTogglePoints::const_iterator; public: - bool Load(BinaryStream& input, bool metadataOnly = false); + bool Load(BinaryStream& input, bool metadataOnly = false); - /// Returns the settings of the map, contains metadata + song/image paths. - const BeatmapSettings& GetMapSettings() const; + /// Returns the settings of the map, contains metadata + song/image paths. + const BeatmapSettings& GetMapSettings() const; - const Vector& GetLaneTogglePoints() const { return m_laneTogglePoints; } + const Vector& GetLaneTogglePoints() const { return m_laneTogglePoints; } - const Vector& GetSamplePaths() const { return m_samplePaths; } - const Vector& GetSwitchablePaths() const { return m_switchablePaths; } + const Vector& GetSamplePaths() const { return m_samplePaths; } + const Vector& GetSwitchablePaths() const { return m_switchablePaths; } - /// Retrieves audio effect settings for a given button id - AudioEffect GetEffect(EffectType type) const; - /// Retrieves audio effect settings for a given filter effect id - AudioEffect GetFilter(EffectType type) const; + /// Retrieves audio effect settings for a given button id + AudioEffect GetEffect(EffectType type) const; + /// Retrieves audio effect settings for a given filter effect id + AudioEffect GetFilter(EffectType type) const; - /// Get the timing of the first (non-event) object - MapTime GetFirstObjectTime(MapTime lowerBound) const; - /// Get the timing of the last (non-event) object - MapTime GetLastObjectTime() const; - /// Get the timing of the last object, including the event objects - MapTime GetLastObjectTimeIncludingEvents() const; + /// Get the timing of the first (non-event) object + MapTime GetFirstObjectTime(MapTime lowerBound) const; + /// Get the timing of the last (non-event) object + MapTime GetLastObjectTime() const; + /// Get the timing of the last object, including the event objects + MapTime GetLastObjectTimeIncludingEvents() const; - /// Measure -> Time - MapTime GetMapTimeFromMeasureInd(int measure) const; - /// Time -> Measure - int GetMeasureIndFromMapTime(MapTime time) const; + /// Measure -> Time + MapTime GetMapTimeFromMeasureInd(int measure) const; + /// Time -> Measure + int GetMeasureIndFromMapTime(MapTime time) const; - /// Computes the most frequently occuring BPM (to be used for MMod) - double GetModeBPM() const; - void GetBPMInfo(double& startBPM, double& minBPM, double& maxBPM, double& modeBPM) const; + /// Computes the most frequently occuring BPM (to be used for MMod) + double GetModeBPM() const; + void GetBPMInfo(double& startBPM, double& minBPM, double& maxBPM, double& modeBPM) const; - void Shuffle(int seed, bool random, bool mirror); - void ApplyShuffle(const std::array& swaps, bool flipLaser); + void Shuffle(int seed, bool random, bool mirror); + void ApplyShuffle(const std::array& swaps, bool flipLaser); - /// # of (4th-note) beats between the start and the end - float GetBeatCount(MapTime start, MapTime end, TimingPointsIterator hint) const; - float GetBeatCountWithScrollSpeedApplied(MapTime start, MapTime end, TimingPointsIterator hint) const; + /// # of (4th-note) beats between the start and the end + float GetBeatCount(MapTime start, MapTime end, TimingPointsIterator hint) const; + float GetBeatCountWithScrollSpeedApplied(MapTime start, MapTime end, TimingPointsIterator hint) const; - inline float GetBeatCount(MapTime start, MapTime end) const - { - return GetBeatCount(start, end, GetTimingPoint(start)); - } + inline float GetBeatCount(MapTime start, MapTime end) const + { + return GetBeatCount(start, end, GetTimingPoint(start)); + } - inline float GetBeatCountWithScrollSpeedApplied(MapTime start, MapTime end) const - { - return GetBeatCountWithScrollSpeedApplied(start, end, GetTimingPoint(start)); - } + inline float GetBeatCountWithScrollSpeedApplied(MapTime start, MapTime end) const + { + return GetBeatCountWithScrollSpeedApplied(start, end, GetTimingPoint(start)); + } - const Objects& GetObjectStates() const { return m_objectStates; } + const Objects& GetObjectStates() const { return m_objectStates; } - ObjectsIterator GetFirstObjectState() const { return m_objectStates.begin(); } - ObjectsIterator GetEndObjectState() const { return m_objectStates.end(); } + ObjectsIterator GetFirstObjectState() const { return m_objectStates.begin(); } + ObjectsIterator GetEndObjectState() const { return m_objectStates.end(); } - bool HasObjectState() const { return !m_objectStates.empty(); } + bool HasObjectState() const { return !m_objectStates.empty(); } - const TimingPoints& GetTimingPoints() const { return m_timingPoints; } + const TimingPoints& GetTimingPoints() const { return m_timingPoints; } - TimingPointsIterator GetFirstTimingPoint() const { return m_timingPoints.begin(); } - TimingPointsIterator GetEndTimingPoint() const { return m_timingPoints.end(); } + TimingPointsIterator GetFirstTimingPoint() const { return m_timingPoints.begin(); } + TimingPointsIterator GetEndTimingPoint() const { return m_timingPoints.end(); } - /// Returns the latest timing point for given mapTime - inline TimingPointsIterator GetTimingPoint(MapTime mapTime) const - { - return GetTimingPoint(mapTime, 0, m_timingPoints.size()); - } + /// Returns the latest timing point for given mapTime + inline TimingPointsIterator GetTimingPoint(MapTime mapTime) const + { + return GetTimingPoint(mapTime, 0, m_timingPoints.size()); + } - /// GetTimingPoint but a hint is given - TimingPointsIterator GetTimingPoint(MapTime mapTime, TimingPointsIterator hint, bool forwardOnly = false) const; + /// GetTimingPoint but a hint is given + TimingPointsIterator GetTimingPoint(MapTime mapTime, TimingPointsIterator hint, bool forwardOnly = false) const; - /// GetTimingPoint but begin and end ranges are specified - inline TimingPointsIterator GetTimingPoint(MapTime mapTime, TimingPointsIterator beginIt, TimingPointsIterator endIt) const - { - return GetTimingPoint(mapTime, static_cast(std::distance(m_timingPoints.begin(), beginIt)), static_cast(std::distance(m_timingPoints.begin(), endIt))); - } + /// GetTimingPoint but begin and end ranges are specified + inline TimingPointsIterator GetTimingPoint(MapTime mapTime, TimingPointsIterator beginIt, TimingPointsIterator endIt) const + { + return GetTimingPoint(mapTime, static_cast(std::distance(m_timingPoints.begin(), beginIt)), static_cast(std::distance(m_timingPoints.begin(), endIt))); + } - TimingPointsIterator GetTimingPoint(MapTime mapTime, size_t begin, size_t end) const; + TimingPointsIterator GetTimingPoint(MapTime mapTime, size_t begin, size_t end) const; - LaneTogglePointsIterator GetFirstLaneTogglePoint() const { return m_laneTogglePoints.begin(); } - LaneTogglePointsIterator GetEndLaneTogglePoint() const { return m_laneTogglePoints.end(); } + LaneTogglePointsIterator GetFirstLaneTogglePoint() const { return m_laneTogglePoints.begin(); } + LaneTogglePointsIterator GetEndLaneTogglePoint() const { return m_laneTogglePoints.end(); } - float GetGraphValueAt(EffectTimeline::GraphType type, MapTime mapTime) const; - bool CheckIfManualTiltInstant(MapTime bound, MapTime mapTime) const; + float GetGraphValueAt(EffectTimeline::GraphType type, MapTime mapTime) const; + bool CheckIfManualTiltInstant(MapTime bound, MapTime mapTime) const; - float GetCenterSplitValueAt(MapTime mapTime) const; - float GetScrollSpeedAt(MapTime mapTime) const; + float GetCenterSplitValueAt(MapTime mapTime) const; + float GetScrollSpeedAt(MapTime mapTime) const; private: - bool m_ProcessKShootMap(BinaryStream& input, bool metadataOnly); + bool m_ProcessKShootMap(BinaryStream& input, bool metadataOnly); - Map m_customAudioEffects; - Map m_customAudioFilters; + Map m_customAudioEffects; + Map m_customAudioFilters; - Objects m_objectStates; - TimingPoints m_timingPoints; + Objects m_objectStates; + TimingPoints m_timingPoints; - EffectTimeline m_effects; + EffectTimeline m_effects; - LineGraph m_centerSplit; - Vector m_laneTogglePoints; - Map> m_positionalOptions; + LineGraph m_centerSplit; + Vector m_laneTogglePoints; + Map> m_positionalOptions; - Vector m_samplePaths; - Vector m_switchablePaths; - BeatmapSettings m_settings; -}; \ No newline at end of file + Vector m_samplePaths; + Vector m_switchablePaths; + BeatmapSettings m_settings; +}; diff --git a/Beatmap/include/Beatmap/BeatmapObjects.hpp b/Beatmap/include/Beatmap/BeatmapObjects.hpp index 097db97c1..fd82405ff 100644 --- a/Beatmap/include/Beatmap/BeatmapObjects.hpp +++ b/Beatmap/include/Beatmap/BeatmapObjects.hpp @@ -1,9 +1,9 @@ /* - This file contains all the data types that are used for objects inside maps - The basic object class is ObjectState - this class contains 'type' member which indicates to which type it is castable + This file contains all the data types that are used for objects inside maps + The basic object class is ObjectState + this class contains 'type' member which indicates to which type it is castable - No vtable for smaller memory footprint + No vtable for smaller memory footprint */ #pragma once // For effect type enum @@ -21,59 +21,59 @@ using MapTime = int32; struct MapTimeRange { - constexpr MapTimeRange() noexcept : begin(0), end(0) {} - explicit constexpr MapTimeRange(MapTime begin) noexcept : begin(begin), end(0) {} - constexpr MapTimeRange(MapTime begin, MapTime end) noexcept : begin(begin), end(end) {} + constexpr MapTimeRange() noexcept : begin(0), end(0) {} + explicit constexpr MapTimeRange(MapTime begin) noexcept : begin(begin), end(0) {} + constexpr MapTimeRange(MapTime begin, MapTime end) noexcept : begin(begin), end(end) {} - constexpr MapTimeRange(const MapTimeRange&) noexcept = default; - constexpr MapTimeRange(MapTimeRange&&) noexcept = default; + constexpr MapTimeRange(const MapTimeRange&) noexcept = default; + constexpr MapTimeRange(MapTimeRange&&) noexcept = default; - constexpr MapTimeRange& operator= (const MapTimeRange&) noexcept = default; - constexpr MapTimeRange& operator= (MapTimeRange&&) noexcept = default; + constexpr MapTimeRange& operator= (const MapTimeRange&) noexcept = default; + constexpr MapTimeRange& operator= (MapTimeRange&&) noexcept = default; - constexpr bool HasEnd() const noexcept { return begin < end; } - constexpr MapTime Length() const noexcept { return HasEnd() ? end - begin : 0; } - constexpr MapTime Length(const MapTime defaultEnd) const noexcept { return HasEnd() ? end - begin : defaultEnd - begin; } - constexpr bool Includes(const MapTime t, const bool includingEnd = false) const noexcept { return begin <= t && (!HasEnd() || (includingEnd ? t <= end : t < end)); } - constexpr bool Includes(const MapTimeRange& other) const noexcept { return Includes(other.begin) && (other.HasEnd() ? Includes(other.end, true) : !HasEnd());} - constexpr bool Overlaps(const MapTimeRange& other) const noexcept { if (begin <= other.begin) return Includes(other.begin); else return other.Includes(begin); } + constexpr bool HasEnd() const noexcept { return begin < end; } + constexpr MapTime Length() const noexcept { return HasEnd() ? end - begin : 0; } + constexpr MapTime Length(const MapTime defaultEnd) const noexcept { return HasEnd() ? end - begin : defaultEnd - begin; } + constexpr bool Includes(const MapTime t, const bool includingEnd = false) const noexcept { return begin <= t && (!HasEnd() || (includingEnd ? t <= end : t < end)); } + constexpr bool Includes(const MapTimeRange& other) const noexcept { return Includes(other.begin) && (other.HasEnd() ? Includes(other.end, true) : !HasEnd()); } + constexpr bool Overlaps(const MapTimeRange& other) const noexcept { if (begin <= other.begin) return Includes(other.begin); else return other.Includes(begin); } - MapTime begin, end; + MapTime begin, end; }; // Type enum for map object enum class ObjectType : uint8 { - Invalid = 0, - Single, // Either normal or FX button - Hold, // Either normal or FX button but with a duration - Laser, // A laser segment - Event // Event object + Invalid = 0, + Single, // Either normal or FX button + Hold, // Either normal or FX button but with a duration + Laser, // A laser segment + Event // Event object }; // The key parameter for event objects enum class EventKey : uint8 { - SlamVolume, // Float - LaserEffectType, // Effect - LaserEffectMix, // Float - TrackRollBehaviour, // (uint8,Float) - ChartEnd, + SlamVolume, // Float + LaserEffectType, // Effect + LaserEffectMix, // Float + TrackRollBehaviour, // (uint8,Float) + ChartEnd, }; enum class TrackRollBehaviour : uint8 { - // Either one of the following four - Zero = 0, - Normal = 0x1, - Bigger = 0x2, - Biggest = 0x3, - Manual = 0x4, - // Flag for keep - Keep = 0x8, + // Either one of the following four + Zero = 0, + Normal = 0x1, + Bigger = 0x2, + Biggest = 0x3, + Manual = 0x4, + // Flag for keep + Keep = 0x8, }; -TrackRollBehaviour operator|(const TrackRollBehaviour &l, const TrackRollBehaviour &r); -TrackRollBehaviour operator&(const TrackRollBehaviour &l, const TrackRollBehaviour &r); +TrackRollBehaviour operator|(const TrackRollBehaviour& l, const TrackRollBehaviour& r); +TrackRollBehaviour operator&(const TrackRollBehaviour& l, const TrackRollBehaviour& r); // Sensitive data layout since union structure is used to access buttons/holds/... object states #pragma pack(push, 1) @@ -81,12 +81,12 @@ TrackRollBehaviour operator&(const TrackRollBehaviour &l, const TrackRollBehavio // Common data for all object types struct ObjectTypeData_Base { - ObjectTypeData_Base(ObjectType type) : type(type){}; + ObjectTypeData_Base(ObjectType type) : type(type) {}; - // Position in ms when this object appears - MapTime time; - // Type of this object, determines the size of this struct and which type its data is - ObjectType type; + // Position in ms when this object appears + MapTime time; + // Type of this object, determines the size of this struct and which type its data is + ObjectType type; }; struct MultiObjectState; @@ -95,196 +95,196 @@ struct MultiObjectState; template struct TObjectState : public ObjectTypeData_Base, T { - TObjectState() : ObjectTypeData_Base(T::staticType){}; - - // Implicit down-cast - operator TObjectState *() { return (TObjectState *)this; } - operator const TObjectState *() const { return (TObjectState *)this; } - // Implicit down-cast - operator MultiObjectState *() { return (MultiObjectState *)this; } - operator const MultiObjectState *() const { return (MultiObjectState *)this; } + TObjectState() : ObjectTypeData_Base(T::staticType) {}; + + // Implicit down-cast + operator TObjectState* () { return (TObjectState *)this; } + operator const TObjectState* () const { return (TObjectState *)this; } + // Implicit down-cast + operator MultiObjectState* () { return (MultiObjectState*)this; } + operator const MultiObjectState* () const { return (MultiObjectState*)this; } }; // Generic object, does not have an object member template <> struct TObjectState : public ObjectTypeData_Base { - TObjectState() : ObjectTypeData_Base(ObjectType::Invalid){}; + TObjectState() : ObjectTypeData_Base(ObjectType::Invalid) {}; - // Sort object states by their time and other properties - static void SortArray(std::vector>>& arr); + // Sort object states by their time and other properties + static void SortArray(std::vector>>& arr); - // Always allow casting from typeless object to Union State object - operator MultiObjectState *() { return (MultiObjectState *)this; } - operator const MultiObjectState *() const { return (MultiObjectState *)this; } + // Always allow casting from typeless object to Union State object + operator MultiObjectState* () { return (MultiObjectState*)this; } + operator const MultiObjectState* () const { return (MultiObjectState*)this; } }; // A Single button struct ObjectTypeData_Button { - // The index of the button - // 0-3 Normal buttons - // 4-5 FX buttons - uint8 index = 0xFF; + // The index of the button + // 0-3 Normal buttons + // 4-5 FX buttons + uint8 index = 0xFF; - // Does this button have a sound sample attached - bool hasSample = false; + // Does this button have a sound sample attached + bool hasSample = false; - // Index of the sound sample - uint8 sampleIndex = 0xFF; + // Index of the sound sample + uint8 sampleIndex = 0xFF; - // Playback volume of the sample - float sampleVolume = 1.0; + // Playback volume of the sample + float sampleVolume = 1.0; - static const ObjectType staticType = ObjectType::Single; + static const ObjectType staticType = ObjectType::Single; }; // A Hold button, extends a normal button with duration and effect type struct ObjectTypeData_Hold : public ObjectTypeData_Button { - TObjectState *GetRoot(); - // Used for hold notes, 0 is a normal note - MapTime duration = 0; - // The sound effect on the note - EffectType effectType = EffectType::None; - // The parameter for effects that have it - // the maximum number of parameters is 2 (only echo uses this) - int16 effectParams[2] = {0}; - - // Set for hold notes that are a continuation of the previous one, but with a different effect - TObjectState *next = nullptr; - TObjectState *prev = nullptr; - - static const ObjectType staticType = ObjectType::Hold; + TObjectState* GetRoot(); + // Used for hold notes, 0 is a normal note + MapTime duration = 0; + // The sound effect on the note + EffectType effectType = EffectType::None; + // The parameter for effects that have it + // the maximum number of parameters is 2 (only echo uses this) + int16 effectParams[2] = { 0 }; + + // Set for hold notes that are a continuation of the previous one, but with a different effect + TObjectState* next = nullptr; + TObjectState* prev = nullptr; + + static const ObjectType staticType = ObjectType::Hold; }; struct SpinStruct { - enum SpinType - { - None = 0x0, - Full = 0x1, - Quarter = 0x2, - // the side bounce thing - Bounce = 0x3, - }; - SpinType type = SpinType::None; - float direction = 0; - uint32 duration = 0; - // for the side bounce thing - uint32 amplitude = 0; - uint32 frequency = 0; - uint32 decay = 0; + enum SpinType + { + None = 0x0, + Full = 0x1, + Quarter = 0x2, + // the side bounce thing + Bounce = 0x3, + }; + SpinType type = SpinType::None; + float direction = 0; + uint32 duration = 0; + // for the side bounce thing + uint32 amplitude = 0; + uint32 frequency = 0; + uint32 decay = 0; }; // A laser segment struct ObjectTypeData_Laser { - // Retrieves the starting laser point - TObjectState* GetRoot(); - inline const TObjectState* GetRoot() const - { - return const_cast(this)->GetRoot(); - } - - // Ending point of laser - TObjectState* GetTail(); - float GetDirection() const; - float SamplePosition(MapTime time) const; - // Convert extended range to normal range - static float ConvertToNormalRange(float inputRange); - // Convert normal range to extended range - static float ConvertToExtendedRange(float inputRange); - - // Time to a direction change, returns -1 if there are no direction changes in this section - MapTime GetTimeToDirectionChange(MapTime currentTime, MapTime maxDelta); - // Duration of laser segment - MapTime duration = 0; - // 0 or 1 for left and right respectively - uint8 index = 0; - // Special options - uint8 flags = 0; - // Position of the laser on the track - float points[2]; - // Set the to the object state that connects to this laser, if any, otherwise null - TObjectState *next = nullptr; - TObjectState *prev = nullptr; - - SpinStruct spin; - - static const ObjectType staticType = ObjectType::Laser; - - // Indicates that this segment is instant and should generate a laser slam segment - static const uint8 flag_Instant = 0x1; - // Indicates that the range of this laser is extended from -0.5 to 1.5 - static const uint8 flag_Extended = 0x2; - - // Tick used for more accuracy in some calculations - uint32 tick; - - // Indicates that the slam has been processed (ensures slam rolls are applied once) - static const uint8 flag_slamProcessed = 0x4; + // Retrieves the starting laser point + TObjectState* GetRoot(); + inline const TObjectState* GetRoot() const + { + return const_cast(this)->GetRoot(); + } + + // Ending point of laser + TObjectState* GetTail(); + float GetDirection() const; + float SamplePosition(MapTime time) const; + // Convert extended range to normal range + static float ConvertToNormalRange(float inputRange); + // Convert normal range to extended range + static float ConvertToExtendedRange(float inputRange); + + // Time to a direction change, returns -1 if there are no direction changes in this section + MapTime GetTimeToDirectionChange(MapTime currentTime, MapTime maxDelta); + // Duration of laser segment + MapTime duration = 0; + // 0 or 1 for left and right respectively + uint8 index = 0; + // Special options + uint8 flags = 0; + // Position of the laser on the track + float points[2]; + // Set the to the object state that connects to this laser, if any, otherwise null + TObjectState* next = nullptr; + TObjectState* prev = nullptr; + + SpinStruct spin; + + static const ObjectType staticType = ObjectType::Laser; + + // Indicates that this segment is instant and should generate a laser slam segment + static const uint8 flag_Instant = 0x1; + // Indicates that the range of this laser is extended from -0.5 to 1.5 + static const uint8 flag_Extended = 0x2; + + // Tick used for more accuracy in some calculations + uint32 tick; + + // Indicates that the slam has been processed (ensures slam rolls are applied once) + static const uint8 flag_slamProcessed = 0x4; }; struct EventData { - EventData() = default; - template - EventData(const T &obj) - { - static_assert(sizeof(T) <= 4, "Invalid object size"); - memset(this, 0, 4); - memcpy(this, &obj, sizeof(T)); - } - union { - float floatVal; - uint32 uintVal; - int32 intVal; - uint8 byteVal; - EffectType effectVal; - TrackRollBehaviour rollVal; - }; - - // Address of operator that returns a pointer to 32-bit data - uint32 *operator&() - { - return (uint32 *)this; - } + EventData() = default; + template + EventData(const T& obj) + { + static_assert(sizeof(T) <= 4, "Invalid object size"); + memset(this, 0, 4); + memcpy(this, &obj, sizeof(T)); + } + union { + float floatVal; + uint32 uintVal; + int32 intVal; + uint8 byteVal; + EffectType effectVal; + TrackRollBehaviour rollVal; + }; + + // Address of operator that returns a pointer to 32-bit data + uint32* operator&() + { + return (uint32*)this; + } }; // An event segment, these set various settings at a given point, such as an effect volume, the roll behaviour of the track, etc. struct ObjectTypeData_Event { - // The key value for what value this event is setting - EventKey key; + // The key value for what value this event is setting + EventKey key; - // Always 32 bits of data, but the one used depends on the key - EventData data; + // Always 32 bits of data, but the one used depends on the key + EventData data; - // For sorting events that happen on the same tick - uint32 interTickIndex; + // For sorting events that happen on the same tick + uint32 interTickIndex; - static const ObjectType staticType = ObjectType::Event; + static const ObjectType staticType = ObjectType::Event; }; // Object state with union data member struct MultiObjectState { - static bool StaticSerialize(BinaryStream &stream, MultiObjectState *&out); - - // Position in ms when this object appears - MapTime time; - // Type of this object, determines the size of this struct and which type its data is - ObjectType type; - union { - ObjectTypeData_Button button; - ObjectTypeData_Hold hold; - ObjectTypeData_Laser laser; - ObjectTypeData_Event event; - }; - - // Implicit down-cast - operator TObjectState *() { return (TObjectState *)this; } - operator const TObjectState *() const { return (TObjectState *)this; } + static bool StaticSerialize(BinaryStream& stream, MultiObjectState*& out); + + // Position in ms when this object appears + MapTime time; + // Type of this object, determines the size of this struct and which type its data is + ObjectType type; + union { + ObjectTypeData_Button button; + ObjectTypeData_Hold hold; + ObjectTypeData_Laser laser; + ObjectTypeData_Event event; + }; + + // Implicit down-cast + operator TObjectState* () { return (TObjectState *)this; } + operator const TObjectState* () const { return (TObjectState *)this; } }; // Restore packing @@ -299,28 +299,28 @@ typedef TObjectState EventObjectState; // Map timing point struct TimingPoint { - static bool StaticSerialize(BinaryStream &stream, TimingPoint *&out); - - double GetWholeNoteLength() const { return beatDuration * 4; } - double GetBarDuration() const { return GetWholeNoteLength() * ((double)numerator / (double)denominator); } - double GetBPM() const { return 60000.0 / beatDuration; } - - /// Position in ms when this timing point appears - MapTime time = 0; - /// Beat duration of a 4th note in milliseconds (equals 60000.0 / BPM) - double beatDuration; - /// Upper part of the time signature (how many beats per bar) - uint8 numerator = 4; - /// Lower part of the time signature (the note value (4th, 3th, 8th notes, etc.) for a beat) - uint8 denominator = 4; - /// Multiplier for tickrates (x 2^tickrateOffset) - int8 tickrateOffset = 0; + static bool StaticSerialize(BinaryStream& stream, TimingPoint*& out); + + double GetWholeNoteLength() const { return beatDuration * 4; } + double GetBarDuration() const { return GetWholeNoteLength() * ((double)numerator / (double)denominator); } + double GetBPM() const { return 60000.0 / beatDuration; } + + /// Position in ms when this timing point appears + MapTime time = 0; + /// Beat duration of a 4th note in milliseconds (equals 60000.0 / BPM) + double beatDuration; + /// Upper part of the time signature (how many beats per bar) + uint8 numerator = 4; + /// Lower part of the time signature (the note value (4th, 3th, 8th notes, etc.) for a beat) + uint8 denominator = 4; + /// Multiplier for tickrates (x 2^tickrateOffset) + int8 tickrateOffset = 0; }; struct LaneHideTogglePoint { - /// Position in ms when to hide or show the lane - MapTime time; + /// Position in ms when to hide or show the lane + MapTime time; - /// How long the transition to/from hidden should take in 1/192nd notes - uint32 duration = 192; -}; \ No newline at end of file + /// How long the transition to/from hidden should take in 1/192nd notes + uint32 duration = 192; +}; diff --git a/Beatmap/include/Beatmap/BeatmapPlayback.hpp b/Beatmap/include/Beatmap/BeatmapPlayback.hpp index 158c1da79..7d90f4afa 100644 --- a/Beatmap/include/Beatmap/BeatmapPlayback.hpp +++ b/Beatmap/include/Beatmap/BeatmapPlayback.hpp @@ -2,180 +2,180 @@ #include "Beatmap.hpp" /* - Manages the iteration over beatmaps + Manages the iteration over beatmaps */ class BeatmapPlayback { public: - BeatmapPlayback() = default; - BeatmapPlayback(const Beatmap& beatmap); - - // Resets the playback of the map - // Must be called before any other function is called on this object - // returns false if the map contains no objects or timing or otherwise invalid - bool Reset(MapTime initTime = 0, MapTime start = 0); - - // Updates the time of the playback - // checks all items that have been triggered between last time and this time - // if it is a new timing point, this is used for the new BPM - void Update(MapTime newTime); - - MapTime hittableObjectEnter = 500; - MapTime hittableLaserEnter = 1000; - MapTime hittableObjectLeave = 500; - MapTime alertLaserThreshold = 1500; - MapTime audioOffset = 0; - bool cMod = false; - float cModSpeed = 400; - - // Removes any existing data and sets a special behaviour for calibration mode - void MakeCalibrationPlayback(); - - /// Get all objects that fall within the given visible range, - /// `numBeats` is the # of 4th notes - void GetObjectsInViewRange(float numBeats, Vector& objects); - void GetBarPositionsInViewRange(float numBeats, Vector& barPositions) const; - - const ObjectState* GetFirstButtonOrHoldAfterTime(MapTime t, int lane) const; - - // Duration for objects to keep being returned by GetObjectsInRange after they have passed the current time - MapTime keepObjectDuration = 1000; - - // Get the timing point at the current time - const TimingPoint& GetCurrentTimingPoint() const; - // Get the timing point at a given time - const TimingPoint* GetTimingPointAt(MapTime time) const; - - // The beatmap this player is using - const Beatmap& GetBeatmap() { return *m_beatmap; } - - // Counts the total amount of beats that have passed within - // Returns the number of passed beats - // Returns the starting index of the passed beats in 'startIndex' - // Additionally the time signature is multiplied by multiplier - // with a multiplier of 2 a 4/4 signature would tick twice as fast - uint32 CountBeats(MapTime start, MapTime range, int32& startIndex, uint32 multiplier = 1) const; - - // View coordinate conversions - inline float TimeToViewDistance(MapTime mapTime) const - { - return GetViewDistance(m_playbackTime, mapTime); - } - - inline float TimeToViewDistanceIgnoringScrollSpeed(MapTime mapTime) const - { - return GetViewDistanceIgnoringScrollSpeed(m_playbackTime, mapTime); - } - - inline float ToViewDistance(MapTime startTime, MapTime duration) const - { - return GetViewDistance(startTime, startTime + duration); - } - - inline float ToViewDistanceIgnoringScrollSpeed(MapTime startTime, MapTime duration) const - { - return GetViewDistanceIgnoringScrollSpeed(startTime, startTime + duration); - } - - /// Get # of (4th) beats between `startTime` and `endTime`, taking scroll speed changes into account (if CMOD is not used). - float GetViewDistance(MapTime startTime, MapTime endTime) const; - - /// Get # of (4th) beats between `startTime` and `endTime`, ignoring any scroll speed changes. - float GetViewDistanceIgnoringScrollSpeed(MapTime startTime, MapTime endTime) const; - - // Current map time in ms as last passed to Update - inline MapTime GetLastTime() const { return m_playbackTime; } - - // Value from 0 to 1 that indicates how far in a single bar the playback is - inline float GetBarTime() const { return m_barTime; } - inline float GetBeatTime() const { return m_beatTime; } - - // Gets the currently set value of a value set by events in the beatmap - const EventData& GetEventData(EventKey key); - - // Retrieve event data as any 32-bit type - template - const typename std::enable_if::value && sizeof(T) <= 4, T>::type& GetEventData(EventKey key) - { - return *(T*)&GetEventData(key); - } - - // Get interpolated top or bottom zoom as set by the map - float GetZoom(uint8 index) const; - float GetScrollSpeed() const; - - // Checks if current manual tilt value is instant - bool CheckIfManualTiltInstant(); - - /* Playback events */ - // Called when an object became within the 'hittableObjectTreshold' - Delegate OnObjectEntered; - // Called when a laser became within the 'alertLaserThreshold' - Delegate OnLaserAlertEntered; - // Called after an object has passed the duration it can be hit in - Delegate OnObjectLeaved; - // Called when an FX button with effect enters - Delegate OnFXBegin; - // Called when an FX button with effect leaves - Delegate OnFXEnd; - - // Called when a new timing point becomes active - Delegate OnTimingPointChanged; - Delegate OnLaneToggleChanged; - - Delegate OnEventChanged; - - /// Used for debugging - Vector GetStateString() const; + BeatmapPlayback() = default; + BeatmapPlayback(const Beatmap& beatmap); + + // Resets the playback of the map + // Must be called before any other function is called on this object + // returns false if the map contains no objects or timing or otherwise invalid + bool Reset(MapTime initTime = 0, MapTime start = 0); + + // Updates the time of the playback + // checks all items that have been triggered between last time and this time + // if it is a new timing point, this is used for the new BPM + void Update(MapTime newTime); + + MapTime hittableObjectEnter = 500; + MapTime hittableLaserEnter = 1000; + MapTime hittableObjectLeave = 500; + MapTime alertLaserThreshold = 1500; + MapTime audioOffset = 0; + bool cMod = false; + float cModSpeed = 400; + + // Removes any existing data and sets a special behaviour for calibration mode + void MakeCalibrationPlayback(); + + /// Get all objects that fall within the given visible range, + /// `numBeats` is the # of 4th notes + void GetObjectsInViewRange(float numBeats, Vector& objects); + void GetBarPositionsInViewRange(float numBeats, Vector& barPositions) const; + + const ObjectState* GetFirstButtonOrHoldAfterTime(MapTime t, int lane) const; + + // Duration for objects to keep being returned by GetObjectsInRange after they have passed the current time + MapTime keepObjectDuration = 1000; + + // Get the timing point at the current time + const TimingPoint& GetCurrentTimingPoint() const; + // Get the timing point at a given time + const TimingPoint* GetTimingPointAt(MapTime time) const; + + // The beatmap this player is using + const Beatmap& GetBeatmap() { return *m_beatmap; } + + // Counts the total amount of beats that have passed within + // Returns the number of passed beats + // Returns the starting index of the passed beats in 'startIndex' + // Additionally the time signature is multiplied by multiplier + // with a multiplier of 2 a 4/4 signature would tick twice as fast + uint32 CountBeats(MapTime start, MapTime range, int32& startIndex, uint32 multiplier = 1) const; + + // View coordinate conversions + inline float TimeToViewDistance(MapTime mapTime) const + { + return GetViewDistance(m_playbackTime, mapTime); + } + + inline float TimeToViewDistanceIgnoringScrollSpeed(MapTime mapTime) const + { + return GetViewDistanceIgnoringScrollSpeed(m_playbackTime, mapTime); + } + + inline float ToViewDistance(MapTime startTime, MapTime duration) const + { + return GetViewDistance(startTime, startTime + duration); + } + + inline float ToViewDistanceIgnoringScrollSpeed(MapTime startTime, MapTime duration) const + { + return GetViewDistanceIgnoringScrollSpeed(startTime, startTime + duration); + } + + /// Get # of (4th) beats between `startTime` and `endTime`, taking scroll speed changes into account (if CMOD is not used). + float GetViewDistance(MapTime startTime, MapTime endTime) const; + + /// Get # of (4th) beats between `startTime` and `endTime`, ignoring any scroll speed changes. + float GetViewDistanceIgnoringScrollSpeed(MapTime startTime, MapTime endTime) const; + + // Current map time in ms as last passed to Update + inline MapTime GetLastTime() const { return m_playbackTime; } + + // Value from 0 to 1 that indicates how far in a single bar the playback is + inline float GetBarTime() const { return m_barTime; } + inline float GetBeatTime() const { return m_beatTime; } + + // Gets the currently set value of a value set by events in the beatmap + const EventData& GetEventData(EventKey key); + + // Retrieve event data as any 32-bit type + template + const typename std::enable_if::value && sizeof(T) <= 4, T>::type& GetEventData(EventKey key) + { + return *(T*)&GetEventData(key); + } + + // Get interpolated top or bottom zoom as set by the map + float GetZoom(uint8 index) const; + float GetScrollSpeed() const; + + // Checks if current manual tilt value is instant + bool CheckIfManualTiltInstant(); + + /* Playback events */ + // Called when an object became within the 'hittableObjectTreshold' + Delegate OnObjectEntered; + // Called when a laser became within the 'alertLaserThreshold' + Delegate OnLaserAlertEntered; + // Called after an object has passed the duration it can be hit in + Delegate OnObjectLeaved; + // Called when an FX button with effect enters + Delegate OnFXBegin; + // Called when an FX button with effect leaves + Delegate OnFXEnd; + + // Called when a new timing point becomes active + Delegate OnTimingPointChanged; + Delegate OnLaneToggleChanged; + + Delegate OnEventChanged; + + /// Used for debugging + Vector GetStateString() const; private: - // Selects an object or timing point based on a given input state - // if allowReset is true the search starts from the start of the object list if current point lies beyond given input time - Beatmap::ObjectsIterator m_SelectHitObject(MapTime time, bool allowReset = false) const; - Beatmap::TimingPointsIterator m_SelectTimingPoint(MapTime time, bool allowReset = false) const; - Beatmap::LaneTogglePointsIterator m_SelectLaneTogglePoint(MapTime time, bool allowReset = false) const; + // Selects an object or timing point based on a given input state + // if allowReset is true the search starts from the start of the object list if current point lies beyond given input time + Beatmap::ObjectsIterator m_SelectHitObject(MapTime time, bool allowReset = false) const; + Beatmap::TimingPointsIterator m_SelectTimingPoint(MapTime time, bool allowReset = false) const; + Beatmap::LaneTogglePointsIterator m_SelectLaneTogglePoint(MapTime time, bool allowReset = false) const; - // End object iterator, this is not a valid iterator, but points to the element after the last element - bool IsEndObject(const Beatmap::ObjectsIterator& obj) const; - bool IsEndTiming(const Beatmap::TimingPointsIterator& obj) const; - bool IsEndLaneToggle(const Beatmap::LaneTogglePointsIterator& obj) const; + // End object iterator, this is not a valid iterator, but points to the element after the last element + bool IsEndObject(const Beatmap::ObjectsIterator& obj) const; + bool IsEndTiming(const Beatmap::TimingPointsIterator& obj) const; + bool IsEndLaneToggle(const Beatmap::LaneTogglePointsIterator& obj) const; - // Current map position of this playback object - MapTime m_playbackTime; + // Current map position of this playback object + MapTime m_playbackTime; - // Disregard objects outside of this range - MapTimeRange m_playRange; - bool m_initialEffectStateSent = false; + // Disregard objects outside of this range + MapTimeRange m_playRange; + bool m_initialEffectStateSent = false; - Beatmap::ObjectsIterator m_currObject; - Beatmap::ObjectsIterator m_currLaserObject; - Beatmap::ObjectsIterator m_currAlertObject; + Beatmap::ObjectsIterator m_currObject; + Beatmap::ObjectsIterator m_currLaserObject; + Beatmap::ObjectsIterator m_currAlertObject; - Beatmap::TimingPointsIterator m_currentTiming; - Beatmap::LaneTogglePointsIterator m_currentLaneTogglePoint; + Beatmap::TimingPointsIterator m_currentTiming; + Beatmap::LaneTogglePointsIterator m_currentLaneTogglePoint; - TrackRollBehaviour m_currentTrackRollBehaviour = TrackRollBehaviour::Normal; - MapTime m_lastTrackRollBehaviourChange = 0; + TrackRollBehaviour m_currentTrackRollBehaviour = TrackRollBehaviour::Normal; + MapTime m_lastTrackRollBehaviourChange = 0; - // Contains all the objects that are in the current valid timing area - Multimap m_objectsByTime; + // Contains all the objects that are in the current valid timing area + Multimap m_objectsByTime; - // Ordered by leaving time - Multimap m_objectsByLeaveTime; - - // Hold buttons with effects that are active - Set m_effectObjects; + // Ordered by leaving time + Multimap m_objectsByLeaveTime; - // Current state of events - Map m_eventMapping; + // Hold buttons with effects that are active + Set m_effectObjects; - float m_barTime; - float m_beatTime; + // Current state of events + Map m_eventMapping; - const Beatmap* m_beatmap = nullptr; + float m_barTime; + float m_beatTime; - // For the calibration mode - bool m_isCalibration = false; - Vector> m_calibrationObjects; - TimingPoint m_calibrationTiming; -}; \ No newline at end of file + const Beatmap* m_beatmap = nullptr; + + // For the calibration mode + bool m_isCalibration = false; + Vector> m_calibrationObjects; + TimingPoint m_calibrationTiming; +}; diff --git a/Beatmap/include/Beatmap/Database.hpp b/Beatmap/include/Beatmap/Database.hpp index 19f702259..98b2da3fe 100644 --- a/Beatmap/include/Beatmap/Database.hpp +++ b/Beatmap/include/Beatmap/Database.hpp @@ -1,53 +1,53 @@ #pragma once /* - Compiled operation on local database object + Compiled operation on local database object */ class DBStatement : public Unique { public: - DBStatement(DBStatement&& other); - ~DBStatement(); - bool Step(); - bool StepRow(); - void Rewind(); - void Finish(); - int32 IntColumn(int32 index = 0) const; - int64 Int64Column(int32 index = 0) const; - double DoubleColumn(int32 index = 0) const; - String StringColumn(int32 index = 0) const; - String StringColumnEmptyOnNull(int32 index = 0) const; - Buffer BlobColumn(int32 index = 0) const; - void BindInt(int32 index, const int32& value); - void BindInt64(int32 index, const int64& value); - void BindDouble(int32 index, const double& value); - void BindString(int32 index, const String& value); - void BindBlob(int32 index, const Buffer& value); - int32 ColumnCount() const; - operator bool(); + DBStatement(DBStatement&& other); + ~DBStatement(); + bool Step(); + bool StepRow(); + void Rewind(); + void Finish(); + int32 IntColumn(int32 index = 0) const; + int64 Int64Column(int32 index = 0) const; + double DoubleColumn(int32 index = 0) const; + String StringColumn(int32 index = 0) const; + String StringColumnEmptyOnNull(int32 index = 0) const; + Buffer BlobColumn(int32 index = 0) const; + void BindInt(int32 index, const int32& value); + void BindInt64(int32 index, const int64& value); + void BindDouble(int32 index, const double& value); + void BindString(int32 index, const String& value); + void BindBlob(int32 index, const Buffer& value); + int32 ColumnCount() const; + operator bool(); private: - DBStatement(const String& statement, class Database* db); - friend class Database; + DBStatement(const String& statement, class Database* db); + friend class Database; - Database& m_db; - struct sqlite3_stmt* m_stmt = nullptr; - int32 m_compileResult; - int32 m_queryResult; + Database& m_db; + struct sqlite3_stmt* m_stmt = nullptr; + int32 m_compileResult; + int32 m_queryResult; }; /* - Local database object + Local database object */ class Database : public Unique { public: - ~Database(); - void Close(); - bool Open(const String& path); - DBStatement Query(const String& queryString); - bool Exec(const String& queryString); - bool ExecDirect(const String& queryString); + ~Database(); + void Close(); + bool Open(const String& path); + DBStatement Query(const String& queryString); + bool Exec(const String& queryString); + bool ExecDirect(const String& queryString); - struct sqlite3* db = nullptr; + struct sqlite3* db = nullptr; }; diff --git a/Beatmap/include/Beatmap/EffectTimeline.hpp b/Beatmap/include/Beatmap/EffectTimeline.hpp index 588484f9d..cfc78681e 100644 --- a/Beatmap/include/Beatmap/EffectTimeline.hpp +++ b/Beatmap/include/Beatmap/EffectTimeline.hpp @@ -7,53 +7,53 @@ /// Contains camera and other miscellaneous effects for lane / notes class EffectTimeline { public: - enum class GraphType { - ZOOM_BOTTOM, - ZOOM_TOP, - SHIFT_X, - ROTATION_Z, - SCROLL_SPEED, - }; - - inline LineGraph& GetGraph(GraphType type) - { - switch (type) { - case GraphType::ZOOM_BOTTOM: return m_zoomBottom; - case GraphType::ZOOM_TOP: return m_zoomTop; - case GraphType::SHIFT_X: return m_shiftX; - case GraphType::ROTATION_Z: return m_rotationZ; - case GraphType::SCROLL_SPEED: return m_scrollSpeed; - - // Shouldn't happen at all. - default: assert(false); return m_shiftX; - } - } - - inline const LineGraph& GetGraph(GraphType type) const - { - switch (type) { - case GraphType::ZOOM_BOTTOM: return m_zoomBottom; - case GraphType::ZOOM_TOP: return m_zoomTop; - case GraphType::SHIFT_X: return m_shiftX; - case GraphType::ROTATION_Z: return m_rotationZ; - case GraphType::SCROLL_SPEED: return m_scrollSpeed; - - // Shouldn't happen at all. - default: assert(false); return m_shiftX; - } - } - - inline void InsertGraphValue(GraphType type, MapTime mapTime, double value) - { - GetGraph(type).Insert(mapTime, value); - } + enum class GraphType { + ZOOM_BOTTOM, + ZOOM_TOP, + SHIFT_X, + ROTATION_Z, + SCROLL_SPEED, + }; + + inline LineGraph& GetGraph(GraphType type) + { + switch (type) { + case GraphType::ZOOM_BOTTOM: return m_zoomBottom; + case GraphType::ZOOM_TOP: return m_zoomTop; + case GraphType::SHIFT_X: return m_shiftX; + case GraphType::ROTATION_Z: return m_rotationZ; + case GraphType::SCROLL_SPEED: return m_scrollSpeed; + + // Shouldn't happen at all. + default: assert(false); return m_shiftX; + } + } + + inline const LineGraph& GetGraph(GraphType type) const + { + switch (type) { + case GraphType::ZOOM_BOTTOM: return m_zoomBottom; + case GraphType::ZOOM_TOP: return m_zoomTop; + case GraphType::SHIFT_X: return m_shiftX; + case GraphType::ROTATION_Z: return m_rotationZ; + case GraphType::SCROLL_SPEED: return m_scrollSpeed; + + // Shouldn't happen at all. + default: assert(false); return m_shiftX; + } + } + + inline void InsertGraphValue(GraphType type, MapTime mapTime, double value) + { + GetGraph(type).Insert(mapTime, value); + } private: - LineGraph m_zoomBottom; - LineGraph m_zoomTop; - LineGraph m_shiftX; /// former zoom_side + LineGraph m_zoomBottom; + LineGraph m_zoomTop; + LineGraph m_shiftX; /// former zoom_side - LineGraph m_rotationZ; /// former manual tilt + LineGraph m_rotationZ; /// former manual tilt - LineGraph m_scrollSpeed = LineGraph{1.0}; -}; \ No newline at end of file + LineGraph m_scrollSpeed = LineGraph{ 1.0 }; +}; diff --git a/Beatmap/include/Beatmap/KShootMap.hpp b/Beatmap/include/Beatmap/KShootMap.hpp index f8fe63e38..3b9843ca8 100644 --- a/Beatmap/include/Beatmap/KShootMap.hpp +++ b/Beatmap/include/Beatmap/KShootMap.hpp @@ -4,90 +4,90 @@ using Utility::Sprintf; struct KShootTickSetting { - String first; - String second; + String first; + String second; }; /* - Any division inside a KShootBlock + Any division inside a KShootBlock */ class KShootTick { public: - String ToString() const; - void Clear(); + String ToString() const; + void Clear(); - Vector settings; + Vector settings; - // Original data for this tick - String buttons, fx, laser, add; + // Original data for this tick + String buttons, fx, laser, add; }; -/* - A single bar in the map file +/* + A single bar in the map file */ class KShootBlock { public: - Vector ticks; + Vector ticks; }; class KShootTime { public: - KShootTime();; - KShootTime(uint32_t block, uint32_t tick);; - operator bool() const; - uint32_t block; - uint32_t tick; + KShootTime();; + KShootTime(uint32_t block, uint32_t tick);; + operator bool() const; + uint32_t block; + uint32_t tick; }; struct KShootEffectDefinition { - String typeName; - Map parameters; + String typeName; + Map parameters; }; -/* - Map class for that splits up maps in the ksh format into Ticks and Blocks +/* + Map class for that splits up maps in the ksh format into Ticks and Blocks */ class KShootMap { public: - class TickIterator - { - public: - TickIterator(KShootMap& map, KShootTime start = KShootTime(0, 0)); - TickIterator& operator++(); - operator bool() const; - KShootTick& operator*(); - KShootTick* operator->(); - const KShootTime& GetTime() const; - const KShootBlock& GetCurrentBlock() const; - private: - KShootMap& m_map; - KShootBlock* m_currentBlock; - KShootTime m_time; - }; + class TickIterator + { + public: + TickIterator(KShootMap& map, KShootTime start = KShootTime(0, 0)); + TickIterator& operator++(); + operator bool() const; + KShootTick& operator*(); + KShootTick* operator->(); + const KShootTime& GetTime() const; + const KShootBlock& GetCurrentBlock() const; + private: + KShootMap& m_map; + KShootBlock* m_currentBlock; + KShootTime m_time; + }; public: - KShootMap(); - ~KShootMap(); - bool Init(BinaryStream& input, bool metadataOnly); - bool GetBlock(const KShootTime& time, KShootBlock*& tickOut); - bool GetTick(const KShootTime& time, KShootTick*& tickOut); - float TimeToFloat(const KShootTime& time) const; - float TranslateLaserChar(char c) const; + KShootMap(); + ~KShootMap(); + bool Init(BinaryStream& input, bool metadataOnly); + bool GetBlock(const KShootTime& time, KShootBlock*& tickOut); + bool GetTick(const KShootTime& time, KShootTick*& tickOut); + float TimeToFloat(const KShootTime& time) const; + float TranslateLaserChar(char c) const; - Map settings; - Vector blocks; - Map filterDefines; - Map fxDefines; + Map settings; + Vector blocks; + Map filterDefines; + Map fxDefines; private: - static const char* c_sep; + static const char* c_sep; }; -bool ParseKShootCourse(BinaryStream& input, Map& settings, Vector& charts); \ No newline at end of file +bool ParseKShootCourse(BinaryStream& input, Map& settings, Vector& charts); diff --git a/Beatmap/include/Beatmap/LineGraph.hpp b/Beatmap/include/Beatmap/LineGraph.hpp index 786975df7..b3e3b1320 100644 --- a/Beatmap/include/Beatmap/LineGraph.hpp +++ b/Beatmap/include/Beatmap/LineGraph.hpp @@ -12,7 +12,7 @@ class LineGraph { public: LineGraph(double defaultValue = 0.0) : m_default(defaultValue) {} - struct Point { + struct Point { explicit Point(double val) : value(val, val) {} explicit Point(double start, double end) : value(start, end) {} @@ -112,4 +112,4 @@ class LineGraph { } double ValueAt(MapTime mapTime) const; -}; \ No newline at end of file +}; diff --git a/Beatmap/include/Beatmap/MapDatabase.hpp b/Beatmap/include/Beatmap/MapDatabase.hpp index 7149933a2..595f44c5c 100644 --- a/Beatmap/include/Beatmap/MapDatabase.hpp +++ b/Beatmap/include/Beatmap/MapDatabase.hpp @@ -5,49 +5,49 @@ struct SimpleHitStat { - // 0 = miss, 1 = near, 2 = crit, 3 = idle - uint8 rating:3; - uint8 type:5; // We use this to save info about a tick - uint8 lane; - int32 time; - int32 delta; - // Hold state - // This is the amount of gotten ticks in a hold sequence - uint32 hold = 0; - // This is the amount of total ticks in this hold sequence - uint32 holdMax = 0; + // 0 = miss, 1 = near, 2 = crit, 3 = idle + uint8 rating : 3; + uint8 type : 5; // We use this to save info about a tick + uint8 lane; + int32 time; + int32 delta; + // Hold state + // This is the amount of gotten ticks in a hold sequence + uint32 hold = 0; + // This is the amount of total ticks in this hold sequence + uint32 holdMax = 0; }; // Need to ensure it is the same size as old replays use this static_assert(sizeof(SimpleHitStat) == 20); struct ScoreIndex { - int32 id; - int32 score; - int32 crit; - int32 almost; - int32 early; - int32 late; - int32 combo; - int32 miss; - float gauge; - GaugeType gaugeType; - uint32 gaugeOption; - AutoFlags autoFlags; - bool random; - bool mirror; - String replayPath; - String chartHash; - uint64 timestamp; - String userName; - String userId; - bool localScore; - - int32 hitWindowPerfect; - int32 hitWindowGood; - int32 hitWindowHold; - int32 hitWindowMiss; - int32 hitWindowSlam; + int32 id; + int32 score; + int32 crit; + int32 almost; + int32 early; + int32 late; + int32 combo; + int32 miss; + float gauge; + GaugeType gaugeType; + uint32 gaugeOption; + AutoFlags autoFlags; + bool random; + bool mirror; + String replayPath; + String chartHash; + uint64 timestamp; + String userName; + String userId; + bool localScore; + + int32 hitWindowPerfect; + int32 hitWindowGood; + int32 hitWindowHold; + int32 hitWindowMiss; + int32 hitWindowSlam; }; @@ -55,215 +55,215 @@ struct ScoreIndex // a single map may contain multiple difficulties struct DifficultyIndex { - // Id of this difficulty - int32 id; - // Id of the map that contains this difficulty - int32 mapId; - // Full path to the difficulty - String path; - // Last time the difficulty changed - uint64 lwt; - // Map metadata - BeatmapSettings settings; - // Map scores - Vector scores; - // Hash of the song file - String hash; + // Id of this difficulty + int32 id; + // Id of the map that contains this difficulty + int32 mapId; + // Full path to the difficulty + String path; + // Last time the difficulty changed + uint64 lwt; + // Map metadata + BeatmapSettings settings; + // Map scores + Vector scores; + // Hash of the song file + String hash; }; struct ChartIndex { - int32 id; - int32 folderId; - String path; - String title; - String artist; - String title_translit; - String artist_translit; - String jacket_path; - String effector; - String illustrator; - String diff_name; - String diff_shortname; - String bpm; - int32 diff_index; - int32 level; - String hash; - String preview_file; - int32 preview_offset; - int32 preview_length; - uint64 lwt; - int32 custom_offset = 0; - Vector scores; + int32 id; + int32 folderId; + String path; + String title; + String artist; + String title_translit; + String artist_translit; + String jacket_path; + String effector; + String illustrator; + String diff_name; + String diff_shortname; + String bpm; + int32 diff_index; + int32 level; + String hash; + String preview_file; + int32 preview_offset; + int32 preview_length; + uint64 lwt; + int32 custom_offset = 0; + Vector scores; }; // Map located in database // a map is represented by a single subfolder that contains map file struct FolderIndex { - // Id of this map - int32 id; - // Id of this map - int32 selectId; - // Full path to the map root folder - String path; - // List of charts contained within the folder - Vector charts; + // Id of this map + int32 id; + // Id of this map + int32 selectId; + // Full path to the map root folder + String path; + // List of charts contained within the folder + Vector charts; }; -struct ChallengeIndex +struct ChallengeIndex { - int32 id; - Vector charts; - int32 totalNumCharts; // Note: This is not the number found - nlohmann::json settings; - String title; - int32 clearMark; - int32 bestScore; - String reqText; - String path; - String hash; - int32 level; - uint64 lwt; - bool missingChart; - - // Access settings and check if they are actually loaded - const nlohmann::json& GetSettings() - { - if (settings.is_null() || settings.is_discarded()) - ReloadSettings(); - return settings; - } - void ReloadSettings() - { - settings = LoadJson(path); - if (!BasicValidate()) // Only keep if valid - settings = nlohmann::json(); - } - - // This will validate the overall objects/arrays but not option types - bool BasicValidate() const { return BasicValidate(settings, path); }; - void FindCharts(class MapDatabase*, const nlohmann::json& v); - static nlohmann::json LoadJson(const String& path); - static nlohmann::json LoadJson(const Buffer& buffer, const String& path); - static bool BasicValidate(const nlohmann::json& v, const String& path); - void GenerateDescription(); + int32 id; + Vector charts; + int32 totalNumCharts; // Note: This is not the number found + nlohmann::json settings; + String title; + int32 clearMark; + int32 bestScore; + String reqText; + String path; + String hash; + int32 level; + uint64 lwt; + bool missingChart; + + // Access settings and check if they are actually loaded + const nlohmann::json& GetSettings() + { + if (settings.is_null() || settings.is_discarded()) + ReloadSettings(); + return settings; + } + void ReloadSettings() + { + settings = LoadJson(path); + if (!BasicValidate()) // Only keep if valid + settings = nlohmann::json(); + } + + // This will validate the overall objects/arrays but not option types + bool BasicValidate() const { return BasicValidate(settings, path); }; + void FindCharts(class MapDatabase*, const nlohmann::json& v); + static nlohmann::json LoadJson(const String& path); + static nlohmann::json LoadJson(const Buffer& buffer, const String& path); + static bool BasicValidate(const nlohmann::json& v, const String& path); + void GenerateDescription(); private: - static String ChallengeDescriptionVal(const nlohmann::json&, const String&, const String&, bool, int, int); - static String ChallengeDescriptionVal(const nlohmann::json&, const String&, bool, int mult=1, int add=0); - static const Map> ChallengeDescriptionStrings; + static String ChallengeDescriptionVal(const nlohmann::json&, const String&, const String&, bool, int, int); + static String ChallengeDescriptionVal(const nlohmann::json&, const String&, bool, int mult = 1, int add = 0); + static const Map> ChallengeDescriptionStrings; }; struct PracticeSetupIndex { - int32 id = -1; - // ID of the chart - int32 chartId; - // Name of this setup (currently not used) - String setupTitle; - - // Setup options - int32 loopSuccess; - int32 loopFail; - int32 rangeBegin; - int32 rangeEnd; - int32 failCondType; - int32 failCondValue; - double playbackSpeed; - - int32 incSpeedOnSuccess; - double incSpeed; - int32 incStreak; - - int32 decSpeedOnFail; - double decSpeed; - double minPlaybackSpeed; - - int32 maxRewind; - int32 maxRewindMeasure; + int32 id = -1; + // ID of the chart + int32 chartId; + // Name of this setup (currently not used) + String setupTitle; + + // Setup options + int32 loopSuccess; + int32 loopFail; + int32 rangeBegin; + int32 rangeEnd; + int32 failCondType; + int32 failCondValue; + double playbackSpeed; + + int32 incSpeedOnSuccess; + double incSpeed; + int32 incStreak; + + int32 decSpeedOnFail; + double decSpeed; + double minPlaybackSpeed; + + int32 maxRewind; + int32 maxRewindMeasure; }; class MapDatabase : public Unique { public: - MapDatabase(); - // Postpone initialization to allow for hooks - MapDatabase(bool postponeInit /* = false */); - ~MapDatabase(); - - // Finish initialization if postponed - void FinishInit(); - - // Checks the background scanning and actualized the current map database - void Update(); - - bool IsSearching() const; - void StartSearching(); - void PauseSearching(); - void ResumeSearching(); - void StopSearching(); - void LoadDatabaseWithoutSearching(); - - // Finds maps using the search query provided - // search artist/title/tags for maps for any space separated terms - Map FindFolders(const String& search); - Map FindFoldersByPath(const String& search); - Map FindFoldersWithFilter(const String& search, const Vector> filter); - Map FindFoldersByHash(const String& hash); - Map FindFoldersByFolder(const String& folder); - Map FindFoldersByCollection(const String& collection); - Map FindChallenges(const String& search); - ChartIndex* FindFirstChartByPath(const String&); - ChartIndex* FindFirstChartByHash(const String&); - ChartIndex* FindFirstChartByNameAndLevel(const String&, int32 level); - FolderIndex* GetFolder(int32 idx); - Vector GetCollections(); - Vector GetCollectionsForMap(int32 mapid); - Vector GetOrAddPracticeSetups(int32 chartId, const PracticeSetupIndex& defaultOptions); - - // Get a random chart - ChartIndex* GetRandomChart(); - - const std::map& GetFolderMap(); - const std::map& GetChartMap(); - const std::map& GetChallengeMap(); - - - //Attempts to add to collection, if that fails attempt to remove from collection - void AddOrRemoveToCollection(const String& name, int32 mapid); - void AddSearchPath(const String& path); - void AddScore(ScoreIndex* score); - - void UpdatePracticeSetup(PracticeSetupIndex* practiceSetup); - void UpdateChallengeResult(ChallengeIndex*, uint32 clearMark, uint32 bestScore); - - void RemoveSearchPath(const String& path); - void UpdateChartOffset(const ChartIndex* chart); - - void SetChartUpdateBehavior(bool transferScores); - - Delegate OnSearchStatusUpdated; - // (mapId, mapIndex) - Delegate> OnFoldersRemoved; - // (mapId, mapIndex) - Delegate> OnFoldersAdded; - // (mapId, mapIndex) - Delegate> OnFoldersUpdated; - // Called when all maps are cleared - // (newMapList) - Delegate> OnFoldersCleared; - - Delegate> OnChallengesRemoved; - Delegate> OnChallengesAdded; - Delegate> OnChallengesUpdated; - Delegate> OnChallengesCleared; - - Delegate OnDatabaseUpdateStarted; - Delegate OnDatabaseUpdateProgress; - Delegate<> OnDatabaseUpdateDone; + MapDatabase(); + // Postpone initialization to allow for hooks + MapDatabase(bool postponeInit /* = false */); + ~MapDatabase(); + + // Finish initialization if postponed + void FinishInit(); + + // Checks the background scanning and actualized the current map database + void Update(); + + bool IsSearching() const; + void StartSearching(); + void PauseSearching(); + void ResumeSearching(); + void StopSearching(); + void LoadDatabaseWithoutSearching(); + + // Finds maps using the search query provided + // search artist/title/tags for maps for any space separated terms + Map FindFolders(const String& search); + Map FindFoldersByPath(const String& search); + Map FindFoldersWithFilter(const String& search, const Vector> filter); + Map FindFoldersByHash(const String& hash); + Map FindFoldersByFolder(const String& folder); + Map FindFoldersByCollection(const String& collection); + Map FindChallenges(const String& search); + ChartIndex* FindFirstChartByPath(const String&); + ChartIndex* FindFirstChartByHash(const String&); + ChartIndex* FindFirstChartByNameAndLevel(const String&, int32 level); + FolderIndex* GetFolder(int32 idx); + Vector GetCollections(); + Vector GetCollectionsForMap(int32 mapid); + Vector GetOrAddPracticeSetups(int32 chartId, const PracticeSetupIndex& defaultOptions); + + // Get a random chart + ChartIndex* GetRandomChart(); + + const std::map& GetFolderMap(); + const std::map& GetChartMap(); + const std::map& GetChallengeMap(); + + + //Attempts to add to collection, if that fails attempt to remove from collection + void AddOrRemoveToCollection(const String& name, int32 mapid); + void AddSearchPath(const String& path); + void AddScore(ScoreIndex* score); + + void UpdatePracticeSetup(PracticeSetupIndex* practiceSetup); + void UpdateChallengeResult(ChallengeIndex*, uint32 clearMark, uint32 bestScore); + + void RemoveSearchPath(const String& path); + void UpdateChartOffset(const ChartIndex* chart); + + void SetChartUpdateBehavior(bool transferScores); + + Delegate OnSearchStatusUpdated; + // (mapId, mapIndex) + Delegate> OnFoldersRemoved; + // (mapId, mapIndex) + Delegate> OnFoldersAdded; + // (mapId, mapIndex) + Delegate> OnFoldersUpdated; + // Called when all maps are cleared + // (newMapList) + Delegate> OnFoldersCleared; + + Delegate> OnChallengesRemoved; + Delegate> OnChallengesAdded; + Delegate> OnChallengesUpdated; + Delegate> OnChallengesCleared; + + Delegate OnDatabaseUpdateStarted; + Delegate OnDatabaseUpdateProgress; + Delegate<> OnDatabaseUpdateDone; private: - class MapDatabase_Impl* m_impl; - bool m_transferScores = false; + class MapDatabase_Impl* m_impl; + bool m_transferScores = false; }; diff --git a/Beatmap/include/Beatmap/PlaybackOptions.hpp b/Beatmap/include/Beatmap/PlaybackOptions.hpp index f8a2d52cd..4b660f575 100644 --- a/Beatmap/include/Beatmap/PlaybackOptions.hpp +++ b/Beatmap/include/Beatmap/PlaybackOptions.hpp @@ -2,34 +2,34 @@ #include enum class GaugeType : uint16 { - Normal = 0, - Hard, - Permissive, - Blastive, + Normal = 0, + Hard, + Permissive, + Blastive, }; enum class AutoFlags : uint8 { - None = 0, - AutoBT = 0x1, - AutoFX = 0x2, - AutoLaser = 0x4, + None = 0, + AutoBT = 0x1, + AutoFX = 0x2, + AutoLaser = 0x4, }; typedef struct PlaybackOptions { - static PlaybackOptions FromFlags(uint32 flags); - static uint32 ToLegacyFlags(const PlaybackOptions& options); + static PlaybackOptions FromFlags(uint32 flags); + static uint32 ToLegacyFlags(const PlaybackOptions& options); - GaugeType gaugeType = GaugeType::Normal; - float gaugeLevel = 0; + GaugeType gaugeType = GaugeType::Normal; + float gaugeLevel = 0; - // Per gauge type defined option, i.e. star rating - uint32 gaugeOption = 0; - bool mirror = false; - bool random = false; + // Per gauge type defined option, i.e. star rating + uint32 gaugeOption = 0; + bool mirror = false; + bool random = false; - // If true and gaugeType != normal then insert a normal type gauge as a fallback - bool backupGauge = false; + // If true and gaugeType != normal then insert a normal type gauge as a fallback + bool backupGauge = false; - AutoFlags autoFlags = AutoFlags::None; -} PlaybackOptions; \ No newline at end of file + AutoFlags autoFlags = AutoFlags::None; +} PlaybackOptions; diff --git a/Beatmap/include/Beatmap/TinySHA1.hpp b/Beatmap/include/Beatmap/TinySHA1.hpp index 70af046e6..95d485330 100644 --- a/Beatmap/include/Beatmap/TinySHA1.hpp +++ b/Beatmap/include/Beatmap/TinySHA1.hpp @@ -1,10 +1,10 @@ -/* +/* * * TinySHA1 - a header only implementation of the SHA1 algorithm in C++. Based * on the implementation in boost::uuid::details. - * + * * SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1 - * + * * Copyright (c) 2012-22 SAURAV MOHAPATRA * * Permission to use, copy, modify, and distribute this software for any @@ -27,170 +27,174 @@ #include namespace sha1 { - class SHA1 - { - public: - typedef uint32_t digest32_t[5]; - typedef uint8_t digest8_t[20]; - inline static uint32_t LeftRotate(uint32_t value, size_t count) { - return (value << count) ^ (value >> (32-count)); - } - SHA1(){ reset(); } - virtual ~SHA1() {} - SHA1(const SHA1& s) { *this = s; } - const SHA1& operator = (const SHA1& s) { - memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t)); - memcpy(m_block, s.m_block, 64); - m_blockByteIndex = s.m_blockByteIndex; - m_byteCount = s.m_byteCount; - return *this; - } - SHA1& reset() { - m_digest[0] = 0x67452301; - m_digest[1] = 0xEFCDAB89; - m_digest[2] = 0x98BADCFE; - m_digest[3] = 0x10325476; - m_digest[4] = 0xC3D2E1F0; - m_blockByteIndex = 0; - m_byteCount = 0; - return *this; - } - SHA1& processByte(uint8_t octet) { - this->m_block[this->m_blockByteIndex++] = octet; - ++this->m_byteCount; - if(m_blockByteIndex == 64) { - this->m_blockByteIndex = 0; - processBlock(); - } - return *this; - } - SHA1& processBlock(const void* const start, const void* const end) { - const uint8_t* begin = static_cast(start); - const uint8_t* finish = static_cast(end); - while(begin != finish) { - processByte(*begin); - begin++; - } - return *this; - } - SHA1& processBytes(const void* const data, size_t len) { - const uint8_t* block = static_cast(data); - processBlock(block, block + len); - return *this; - } - const uint32_t* getDigest(digest32_t digest) { - size_t bitCount = this->m_byteCount * 8; - processByte(0x80); - if (this->m_blockByteIndex > 56) { - while (m_blockByteIndex != 0) { - processByte(0); - } - while (m_blockByteIndex < 56) { - processByte(0); - } - } else { - while (m_blockByteIndex < 56) { - processByte(0); - } - } - processByte(0); - processByte(0); - processByte(0); - processByte(0); - processByte( static_cast((bitCount>>24) & 0xFF)); - processByte( static_cast((bitCount>>16) & 0xFF)); - processByte( static_cast((bitCount>>8 ) & 0xFF)); - processByte( static_cast((bitCount) & 0xFF)); - - memcpy(digest, m_digest, 5 * sizeof(uint32_t)); - return digest; - } - const uint8_t* getDigestBytes(digest8_t digest) { - digest32_t d32; - getDigest(d32); - size_t di = 0; - digest[di++] = ((d32[0] >> 24) & 0xFF); - digest[di++] = ((d32[0] >> 16) & 0xFF); - digest[di++] = ((d32[0] >> 8) & 0xFF); - digest[di++] = ((d32[0]) & 0xFF); - - digest[di++] = ((d32[1] >> 24) & 0xFF); - digest[di++] = ((d32[1] >> 16) & 0xFF); - digest[di++] = ((d32[1] >> 8) & 0xFF); - digest[di++] = ((d32[1]) & 0xFF); - - digest[di++] = ((d32[2] >> 24) & 0xFF); - digest[di++] = ((d32[2] >> 16) & 0xFF); - digest[di++] = ((d32[2] >> 8) & 0xFF); - digest[di++] = ((d32[2]) & 0xFF); - - digest[di++] = ((d32[3] >> 24) & 0xFF); - digest[di++] = ((d32[3] >> 16) & 0xFF); - digest[di++] = ((d32[3] >> 8) & 0xFF); - digest[di++] = ((d32[3]) & 0xFF); - - digest[di++] = ((d32[4] >> 24) & 0xFF); - digest[di++] = ((d32[4] >> 16) & 0xFF); - digest[di++] = ((d32[4] >> 8) & 0xFF); - digest[di++] = ((d32[4]) & 0xFF); - return digest; - } - - protected: - void processBlock() { - uint32_t w[80]; - for (size_t i = 0; i < 16; i++) { - w[i] = (m_block[i*4 + 0] << 24); - w[i] |= (m_block[i*4 + 1] << 16); - w[i] |= (m_block[i*4 + 2] << 8); - w[i] |= (m_block[i*4 + 3]); - } - for (size_t i = 16; i < 80; i++) { - w[i] = LeftRotate((w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]), 1); - } - - uint32_t a = m_digest[0]; - uint32_t b = m_digest[1]; - uint32_t c = m_digest[2]; - uint32_t d = m_digest[3]; - uint32_t e = m_digest[4]; - - for (std::size_t i=0; i<80; ++i) { - uint32_t f = 0; - uint32_t k = 0; - - if (i<20) { - f = (b & c) | (~b & d); - k = 0x5A827999; - } else if (i<40) { - f = b ^ c ^ d; - k = 0x6ED9EBA1; - } else if (i<60) { - f = (b & c) | (b & d) | (c & d); - k = 0x8F1BBCDC; - } else { - f = b ^ c ^ d; - k = 0xCA62C1D6; - } - uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i]; - e = d; - d = c; - c = LeftRotate(b, 30); - b = a; - a = temp; - } - - m_digest[0] += a; - m_digest[1] += b; - m_digest[2] += c; - m_digest[3] += d; - m_digest[4] += e; - } - private: - digest32_t m_digest; - uint8_t m_block[64]; - size_t m_blockByteIndex; - size_t m_byteCount; - }; + class SHA1 + { + public: + typedef uint32_t digest32_t[5]; + typedef uint8_t digest8_t[20]; + inline static uint32_t LeftRotate(uint32_t value, size_t count) { + return (value << count) ^ (value >> (32 - count)); + } + SHA1() { reset(); } + virtual ~SHA1() {} + SHA1(const SHA1& s) { *this = s; } + const SHA1& operator = (const SHA1& s) { + memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t)); + memcpy(m_block, s.m_block, 64); + m_blockByteIndex = s.m_blockByteIndex; + m_byteCount = s.m_byteCount; + return *this; + } + SHA1& reset() { + m_digest[0] = 0x67452301; + m_digest[1] = 0xEFCDAB89; + m_digest[2] = 0x98BADCFE; + m_digest[3] = 0x10325476; + m_digest[4] = 0xC3D2E1F0; + m_blockByteIndex = 0; + m_byteCount = 0; + return *this; + } + SHA1& processByte(uint8_t octet) { + this->m_block[this->m_blockByteIndex++] = octet; + ++this->m_byteCount; + if (m_blockByteIndex == 64) { + this->m_blockByteIndex = 0; + processBlock(); + } + return *this; + } + SHA1& processBlock(const void* const start, const void* const end) { + const uint8_t* begin = static_cast(start); + const uint8_t* finish = static_cast(end); + while (begin != finish) { + processByte(*begin); + begin++; + } + return *this; + } + SHA1& processBytes(const void* const data, size_t len) { + const uint8_t* block = static_cast(data); + processBlock(block, block + len); + return *this; + } + const uint32_t* getDigest(digest32_t digest) { + size_t bitCount = this->m_byteCount * 8; + processByte(0x80); + if (this->m_blockByteIndex > 56) { + while (m_blockByteIndex != 0) { + processByte(0); + } + while (m_blockByteIndex < 56) { + processByte(0); + } + } + else { + while (m_blockByteIndex < 56) { + processByte(0); + } + } + processByte(0); + processByte(0); + processByte(0); + processByte(0); + processByte(static_cast((bitCount >> 24) & 0xFF)); + processByte(static_cast((bitCount >> 16) & 0xFF)); + processByte(static_cast((bitCount >> 8) & 0xFF)); + processByte(static_cast((bitCount) & 0xFF)); + + memcpy(digest, m_digest, 5 * sizeof(uint32_t)); + return digest; + } + const uint8_t* getDigestBytes(digest8_t digest) { + digest32_t d32; + getDigest(d32); + size_t di = 0; + digest[di++] = ((d32[0] >> 24) & 0xFF); + digest[di++] = ((d32[0] >> 16) & 0xFF); + digest[di++] = ((d32[0] >> 8) & 0xFF); + digest[di++] = ((d32[0]) & 0xFF); + + digest[di++] = ((d32[1] >> 24) & 0xFF); + digest[di++] = ((d32[1] >> 16) & 0xFF); + digest[di++] = ((d32[1] >> 8) & 0xFF); + digest[di++] = ((d32[1]) & 0xFF); + + digest[di++] = ((d32[2] >> 24) & 0xFF); + digest[di++] = ((d32[2] >> 16) & 0xFF); + digest[di++] = ((d32[2] >> 8) & 0xFF); + digest[di++] = ((d32[2]) & 0xFF); + + digest[di++] = ((d32[3] >> 24) & 0xFF); + digest[di++] = ((d32[3] >> 16) & 0xFF); + digest[di++] = ((d32[3] >> 8) & 0xFF); + digest[di++] = ((d32[3]) & 0xFF); + + digest[di++] = ((d32[4] >> 24) & 0xFF); + digest[di++] = ((d32[4] >> 16) & 0xFF); + digest[di++] = ((d32[4] >> 8) & 0xFF); + digest[di++] = ((d32[4]) & 0xFF); + return digest; + } + + protected: + void processBlock() { + uint32_t w[80]; + for (size_t i = 0; i < 16; i++) { + w[i] = (m_block[i * 4 + 0] << 24); + w[i] |= (m_block[i * 4 + 1] << 16); + w[i] |= (m_block[i * 4 + 2] << 8); + w[i] |= (m_block[i * 4 + 3]); + } + for (size_t i = 16; i < 80; i++) { + w[i] = LeftRotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1); + } + + uint32_t a = m_digest[0]; + uint32_t b = m_digest[1]; + uint32_t c = m_digest[2]; + uint32_t d = m_digest[3]; + uint32_t e = m_digest[4]; + + for (std::size_t i = 0; i < 80; ++i) { + uint32_t f = 0; + uint32_t k = 0; + + if (i < 20) { + f = (b & c) | (~b & d); + k = 0x5A827999; + } + else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i]; + e = d; + d = c; + c = LeftRotate(b, 30); + b = a; + a = temp; + } + + m_digest[0] += a; + m_digest[1] += b; + m_digest[2] += c; + m_digest[3] += d; + m_digest[4] += e; + } + private: + digest32_t m_digest; + uint8_t m_block[64]; + size_t m_blockByteIndex; + size_t m_byteCount; + }; } #endif diff --git a/Beatmap/src/AudioEffects.cpp b/Beatmap/src/AudioEffects.cpp index c43b78abe..be5c590aa 100644 --- a/Beatmap/src/AudioEffects.cpp +++ b/Beatmap/src/AudioEffects.cpp @@ -3,189 +3,189 @@ EffectDuration::EffectDuration(int32 duration /*= 0*/) { - this->duration = duration; - type = Time; + this->duration = duration; + type = Time; } EffectDuration::EffectDuration(float rate) { - this->rate = rate; - type = Rate; + this->rate = rate; + type = Rate; } -EffectDuration EffectDuration::Lerp(const EffectDuration &lhs, const EffectDuration &rhs, float time) +EffectDuration EffectDuration::Lerp(const EffectDuration& lhs, const EffectDuration& rhs, float time) { - assert(rhs.type == lhs.type); - if (lhs.type == Type::Rate) - return lhs.rate + (lhs.rate - rhs.rate) * time; - else - return (int32)(lhs.duration + (float)(lhs.duration - rhs.duration) * time); + assert(rhs.type == lhs.type); + if (lhs.type == Type::Rate) + return lhs.rate + (lhs.rate - rhs.rate) * time; + else + return (int32)(lhs.duration + (float)(lhs.duration - rhs.duration) * time); } uint32 EffectDuration::Absolute(double noteDuration) { - if (type == Time) - return duration; - else - return (uint32)(rate * noteDuration); + if (type == Time) + return duration; + else + return (uint32)(rate * noteDuration); } EffectDuration InterpolateEffectParamValue(EffectDuration a, EffectDuration b, float t) { - return EffectDuration::Lerp(a, b, t); + return EffectDuration::Lerp(a, b, t); } static AudioEffect CreateDefault(EffectType type) { - AudioEffect ret; - ret.type = type; + AudioEffect ret; + ret.type = type; - typedef EffectParam FloatRange; - typedef EffectParam IntRange; - typedef EffectParam TimeRange; + typedef EffectParam FloatRange; + typedef EffectParam IntRange; + typedef EffectParam TimeRange; - // Default timing is 1/4 - ret.duration = TimeRange(0.25f); - ret.mix = FloatRange(1.0); - Interpolation::CubicBezier laserEasingCurve = Interpolation::EaseInExpo; - Interpolation::CubicBezier lpfEasingCurve = Interpolation::EaseOutCubic; + // Default timing is 1/4 + ret.duration = TimeRange(0.25f); + ret.mix = FloatRange(1.0); + Interpolation::CubicBezier laserEasingCurve = Interpolation::EaseInExpo; + Interpolation::CubicBezier lpfEasingCurve = Interpolation::EaseOutCubic; - // Set defaults based on effect type - switch (type) - { - // These are assumed to mostly be laser effect types (at least when used with the defaults) - case EffectType::PeakingFilter: - { - ret.peaking.freq = FloatRange(80.0f, 8000.0f, laserEasingCurve); - ret.peaking.q = FloatRange(1.f, 0.8f); - ret.peaking.gain = FloatRange(20.0f, 20.0f); - break; - } - case EffectType::LowPassFilter: - { - ret.lpf.freq = FloatRange(10000.0f, 700.0f, lpfEasingCurve); - ret.lpf.q = FloatRange(7.0f, 10.0f); - break; - } - case EffectType::HighPassFilter: - { - ret.hpf.freq = FloatRange(80.0f, 2000.0f, laserEasingCurve); - ret.hpf.q = FloatRange(10.0f, 5.0f); - break; - } - case EffectType::Bitcrush: - ret.bitcrusher.reduction = IntRange(0, 45); - break; - case EffectType::Gate: - ret.mix = FloatRange(0.9f); - ret.gate.gate = 0.5f; - break; - case EffectType::Retrigger: - ret.retrigger.gate = 0.7f; - ret.retrigger.reset = TimeRange(0.5f); - break; - case EffectType::Echo: - ret.echo.feedback = FloatRange(0.60f); - break; - case EffectType::Panning: - ret.panning.panning = FloatRange(0.0f); - break; - case EffectType::TapeStop: - break; - case EffectType::Phaser: - ret.duration = TimeRange(0.5f); - ret.mix = FloatRange(0.5f); - ret.phaser.stage = IntRange(6); - ret.phaser.min = FloatRange(1500.0f); - ret.phaser.max = FloatRange(20000.0f); - ret.phaser.q = FloatRange(0.707f); - ret.phaser.feedback = FloatRange(0.35f); - ret.phaser.stereoWidth = FloatRange(0.75f); - ret.phaser.hiCutGain = FloatRange(-8.0f); - break; - case EffectType::Wobble: - // wobble is 1/12 by default - ret.duration = TimeRange(1.0f / 12.0f); - ret.mix = FloatRange(0.8f); - ret.wobble.max = FloatRange(20000.0f); - ret.wobble.min = FloatRange(500.0f); - ret.wobble.q = FloatRange(1.414f); - break; - case EffectType::SideChain: - ret.duration = TimeRange(1.0f / 4.0f); - ret.sidechain.attackTime = TimeRange(10); - ret.sidechain.holdTime = TimeRange(50); - ret.sidechain.releaseTime = TimeRange(1.0f / 16.0f); - ret.sidechain.ratio = FloatRange(5.0f); - break; - case EffectType::Flanger: - ret.duration = TimeRange(2.0f); - ret.mix = FloatRange(0.8f); - ret.flanger.offset = IntRange(30); - ret.flanger.depth = IntRange(45); - ret.flanger.feedback = FloatRange(0.6f); - ret.flanger.stereoWidth = FloatRange(0.0f); - ret.flanger.volume = FloatRange(0.75f); - break; - case EffectType::SwitchAudio: - ret.switchaudio.index = -1; - break; - } + // Set defaults based on effect type + switch (type) + { + // These are assumed to mostly be laser effect types (at least when used with the defaults) + case EffectType::PeakingFilter: + { + ret.peaking.freq = FloatRange(80.0f, 8000.0f, laserEasingCurve); + ret.peaking.q = FloatRange(1.f, 0.8f); + ret.peaking.gain = FloatRange(20.0f, 20.0f); + break; + } + case EffectType::LowPassFilter: + { + ret.lpf.freq = FloatRange(10000.0f, 700.0f, lpfEasingCurve); + ret.lpf.q = FloatRange(7.0f, 10.0f); + break; + } + case EffectType::HighPassFilter: + { + ret.hpf.freq = FloatRange(80.0f, 2000.0f, laserEasingCurve); + ret.hpf.q = FloatRange(10.0f, 5.0f); + break; + } + case EffectType::Bitcrush: + ret.bitcrusher.reduction = IntRange(0, 45); + break; + case EffectType::Gate: + ret.mix = FloatRange(0.9f); + ret.gate.gate = 0.5f; + break; + case EffectType::Retrigger: + ret.retrigger.gate = 0.7f; + ret.retrigger.reset = TimeRange(0.5f); + break; + case EffectType::Echo: + ret.echo.feedback = FloatRange(0.60f); + break; + case EffectType::Panning: + ret.panning.panning = FloatRange(0.0f); + break; + case EffectType::TapeStop: + break; + case EffectType::Phaser: + ret.duration = TimeRange(0.5f); + ret.mix = FloatRange(0.5f); + ret.phaser.stage = IntRange(6); + ret.phaser.min = FloatRange(1500.0f); + ret.phaser.max = FloatRange(20000.0f); + ret.phaser.q = FloatRange(0.707f); + ret.phaser.feedback = FloatRange(0.35f); + ret.phaser.stereoWidth = FloatRange(0.75f); + ret.phaser.hiCutGain = FloatRange(-8.0f); + break; + case EffectType::Wobble: + // wobble is 1/12 by default + ret.duration = TimeRange(1.0f / 12.0f); + ret.mix = FloatRange(0.8f); + ret.wobble.max = FloatRange(20000.0f); + ret.wobble.min = FloatRange(500.0f); + ret.wobble.q = FloatRange(1.414f); + break; + case EffectType::SideChain: + ret.duration = TimeRange(1.0f / 4.0f); + ret.sidechain.attackTime = TimeRange(10); + ret.sidechain.holdTime = TimeRange(50); + ret.sidechain.releaseTime = TimeRange(1.0f / 16.0f); + ret.sidechain.ratio = FloatRange(5.0f); + break; + case EffectType::Flanger: + ret.duration = TimeRange(2.0f); + ret.mix = FloatRange(0.8f); + ret.flanger.offset = IntRange(30); + ret.flanger.depth = IntRange(45); + ret.flanger.feedback = FloatRange(0.6f); + ret.flanger.stereoWidth = FloatRange(0.0f); + ret.flanger.volume = FloatRange(0.75f); + break; + case EffectType::SwitchAudio: + ret.switchaudio.index = -1; + break; + } - return ret; + return ret; } class DefaultEffectSettings { public: - Vector effects; - DefaultEffectSettings() - { - // Preload all default effect settings - effects.resize((size_t)EffectType::_Length); - for (auto e : Enum_EffectType::GetMap()) - { - effects[(size_t)e.first] = CreateDefault(e.first); - } - } + Vector effects; + DefaultEffectSettings() + { + // Preload all default effect settings + effects.resize((size_t)EffectType::_Length); + for (auto e : Enum_EffectType::GetMap()) + { + effects[(size_t)e.first] = CreateDefault(e.first); + } + } }; -const AudioEffect &AudioEffect::GetDefault(EffectType type) +const AudioEffect& AudioEffect::GetDefault(EffectType type) { - static DefaultEffectSettings defaults; - assert((size_t)type < defaults.effects.size()); - return defaults.effects[(size_t)type]; + static DefaultEffectSettings defaults; + assert((size_t)type < defaults.effects.size()); + return defaults.effects[(size_t)type]; } int AudioEffect::GetDefaultEffectPriority(EffectType type) { - const Map priorityMap = { - {EffectType::Retrigger, 0}, - {EffectType::Echo, 1}, - {EffectType::Gate, 2}, - {EffectType::TapeStop, 3}, - {EffectType::SideChain, 4}, - {EffectType::Bitcrush, 5}, - }; + const Map priorityMap = { + {EffectType::Retrigger, 0}, + {EffectType::Echo, 1}, + {EffectType::Gate, 2}, + {EffectType::TapeStop, 3}, + {EffectType::SideChain, 4}, + {EffectType::Bitcrush, 5}, + }; - if (priorityMap.Contains(type)) - return priorityMap.at(type); + if (priorityMap.Contains(type)) + return priorityMap.at(type); - return 99; + return 99; } -void AudioEffect::SetDefaultEffectParams(int16 *params) +void AudioEffect::SetDefaultEffectParams(int16* params) { - // Set defaults based on effect type - switch (type) - { - case EffectType::Bitcrush: - params[0] = bitcrusher.reduction.Sample(1); - break; - case EffectType::Wobble: - break; - case EffectType::Flanger: - params[0] = flanger.depth.Sample(1); - params[1] = flanger.offset.Sample(1); - break; - default: - break; - } + // Set defaults based on effect type + switch (type) + { + case EffectType::Bitcrush: + params[0] = bitcrusher.reduction.Sample(1); + break; + case EffectType::Wobble: + break; + case EffectType::Flanger: + params[0] = flanger.depth.Sample(1); + params[1] = flanger.offset.Sample(1); + break; + default: + break; + } } diff --git a/Beatmap/src/Beatmap.cpp b/Beatmap/src/Beatmap.cpp index 18efbbc26..d83dfe551 100644 --- a/Beatmap/src/Beatmap.cpp +++ b/Beatmap/src/Beatmap.cpp @@ -9,592 +9,603 @@ static const uint32 c_mapVersion = 1; bool Beatmap::Load(BinaryStream& input, bool metadataOnly) { - ProfilerScope $("Load Beatmap"); + ProfilerScope $("Load Beatmap"); - return m_ProcessKShootMap(input, metadataOnly); + return m_ProcessKShootMap(input, metadataOnly); } const BeatmapSettings& Beatmap::GetMapSettings() const { - return m_settings; + return m_settings; } AudioEffect Beatmap::GetEffect(EffectType type) const { - if(type >= EffectType::UserDefined0) - { - const AudioEffect* fx = m_customAudioEffects.Find(type); - assert(fx); - return *fx; - } - return AudioEffect::GetDefault(type); + if (type >= EffectType::UserDefined0) + { + const AudioEffect* fx = m_customAudioEffects.Find(type); + assert(fx); + return *fx; + } + return AudioEffect::GetDefault(type); } AudioEffect Beatmap::GetFilter(EffectType type) const { - if(type >= EffectType::UserDefined0) - { - const AudioEffect* fx = m_customAudioFilters.Find(type); - assert(fx); - return *fx; - } - return AudioEffect::GetDefault(type); + if (type >= EffectType::UserDefined0) + { + const AudioEffect* fx = m_customAudioFilters.Find(type); + assert(fx); + return *fx; + } + return AudioEffect::GetDefault(type); } MapTime Beatmap::GetFirstObjectTime(MapTime lowerBound) const { - if (m_objectStates.empty()) - { - return lowerBound; - } - - for (const auto& obj : m_objectStates) - { - if (obj->type == ObjectType::Event) continue; - if (obj->time < lowerBound) continue; - - return obj->time; - } - - return lowerBound; + if (m_objectStates.empty()) + { + return lowerBound; + } + + for (const auto& obj : m_objectStates) + { + if (obj->type == ObjectType::Event) + continue; + if (obj->time < lowerBound) + continue; + + return obj->time; + } + + return lowerBound; } MapTime Beatmap::GetLastObjectTime() const { - if (m_objectStates.empty()) - { - return 0; - } - - for (auto it = m_objectStates.rbegin(); it != m_objectStates.rend(); ++it) - { - const auto& obj = *it; - switch (obj->type) - { - case ObjectType::Event: - continue; - case ObjectType::Hold: - return obj->time + ((const HoldObjectState*) obj.get())->duration; - case ObjectType::Laser: - return obj->time + ((const LaserObjectState*) obj.get())->duration; - default: - return obj->time; - } - } - - return 0; + if (m_objectStates.empty()) + { + return 0; + } + + for (auto it = m_objectStates.rbegin(); it != m_objectStates.rend(); ++it) + { + const auto& obj = *it; + switch (obj->type) + { + case ObjectType::Event: + continue; + case ObjectType::Hold: + return obj->time + ((const HoldObjectState*)obj.get())->duration; + case ObjectType::Laser: + return obj->time + ((const LaserObjectState*)obj.get())->duration; + default: + return obj->time; + } + } + + return 0; } MapTime Beatmap::GetLastObjectTimeIncludingEvents() const { - return m_objectStates.empty() ? 0 : m_objectStates.back()->time; + return m_objectStates.empty() ? 0 : m_objectStates.back()->time; } constexpr static double MEASURE_EPSILON = 0.005; -inline static int GetBarCount(const TimingPoint& a, const TimingPoint& b) +inline static int GetBarCount(const TimingPoint& a, const TimingPoint& b) { - const MapTime measureDuration = b.time - a.time; - const double barCount = measureDuration / a.GetBarDuration(); - int barCountInt = static_cast(barCount + 0.5); - - if (std::abs(barCount - static_cast(barCountInt)) >= MEASURE_EPSILON) - { - Logf("A timing point at %d contains non-integer # of bars: %g", Logger::Severity::Debug, a.time, barCount); - if (barCount > barCountInt) ++barCountInt; - } - - return barCountInt; + const MapTime measureDuration = b.time - a.time; + const double barCount = measureDuration / a.GetBarDuration(); + int barCountInt = static_cast(barCount + 0.5); + + if (std::abs(barCount - static_cast(barCountInt)) >= MEASURE_EPSILON) + { + Logf("A timing point at %d contains non-integer # of bars: %g", Logger::Severity::Debug, a.time, barCount); + if (barCount > barCountInt) + ++barCountInt; + } + + return barCountInt; } MapTime Beatmap::GetMapTimeFromMeasureInd(int measure) const { - if (measure < 0) return 0; - - int currMeasure = 0; - for (int i = 0; i < m_timingPoints.size(); ++i) - { - bool isInCurrentTimingPoint = false; - if (i == m_timingPoints.size() - 1 || measure <= currMeasure) - { - isInCurrentTimingPoint = true; - } - else - { - const int barCount = GetBarCount(m_timingPoints[i], m_timingPoints[i + 1]); - - if (measure < currMeasure + barCount) - isInCurrentTimingPoint = true; - else - currMeasure += barCount; - } - if (isInCurrentTimingPoint) - { - measure -= currMeasure; - return static_cast(m_timingPoints[i].time + m_timingPoints[i].GetBarDuration() * measure); - } - } - - assert(false); - return 0; + if (measure < 0) + return 0; + + int currMeasure = 0; + for (int i = 0; i < m_timingPoints.size(); ++i) + { + bool isInCurrentTimingPoint = false; + if (i == m_timingPoints.size() - 1 || measure <= currMeasure) + { + isInCurrentTimingPoint = true; + } + else + { + const int barCount = GetBarCount(m_timingPoints[i], m_timingPoints[i + 1]); + + if (measure < currMeasure + barCount) + isInCurrentTimingPoint = true; + else + currMeasure += barCount; + } + if (isInCurrentTimingPoint) + { + measure -= currMeasure; + return static_cast(m_timingPoints[i].time + m_timingPoints[i].GetBarDuration() * measure); + } + } + + assert(false); + return 0; } int Beatmap::GetMeasureIndFromMapTime(MapTime time) const { - if (time <= 0) return 0; - - int currMeasureCount = 0; - for (int i = 0; i < m_timingPoints.size(); ++i) - { - if (i < m_timingPoints.size() - 1 && m_timingPoints[i + 1].time <= time) - { - currMeasureCount += GetBarCount(m_timingPoints[i], m_timingPoints[i + 1]); - continue; - } - - return currMeasureCount + static_cast(MEASURE_EPSILON + (time - m_timingPoints[i].time) / m_timingPoints[i].GetBarDuration()); - } - - assert(false); - return 0; + if (time <= 0) + return 0; + + int currMeasureCount = 0; + for (int i = 0; i < m_timingPoints.size(); ++i) + { + if (i < m_timingPoints.size() - 1 && m_timingPoints[i + 1].time <= time) + { + currMeasureCount += GetBarCount(m_timingPoints[i], m_timingPoints[i + 1]); + continue; + } + + return currMeasureCount + static_cast(MEASURE_EPSILON + (time - m_timingPoints[i].time) / m_timingPoints[i].GetBarDuration()); + } + + assert(false); + return 0; } double Beatmap::GetModeBPM() const { - Map bpmDurations; - - MapTime lastMT = m_settings.offset; - double largestMT = -1; - double useBPM = -1; - double lastBPM = -1; - - for (const TimingPoint& tp : m_timingPoints) - { - const double thisBPM = tp.GetBPM(); - const MapTime timeSinceLastTP = tp.time - lastMT; - - const double duration = bpmDurations[lastBPM] += timeSinceLastTP; - if (duration > largestMT) - { - useBPM = lastBPM; - largestMT = duration; - } - lastMT = tp.time; - lastBPM = thisBPM; - } - - bpmDurations[lastBPM] += GetLastObjectTime() - lastMT; - - if (bpmDurations[lastBPM] > largestMT) - { - useBPM = lastBPM; - } - - return useBPM; + Map bpmDurations; + + MapTime lastMT = m_settings.offset; + double largestMT = -1; + double useBPM = -1; + double lastBPM = -1; + + for (const TimingPoint& tp : m_timingPoints) + { + const double thisBPM = tp.GetBPM(); + const MapTime timeSinceLastTP = tp.time - lastMT; + + const double duration = bpmDurations[lastBPM] += timeSinceLastTP; + if (duration > largestMT) + { + useBPM = lastBPM; + largestMT = duration; + } + lastMT = tp.time; + lastBPM = thisBPM; + } + + bpmDurations[lastBPM] += GetLastObjectTime() - lastMT; + + if (bpmDurations[lastBPM] > largestMT) + { + useBPM = lastBPM; + } + + return useBPM; } void Beatmap::GetBPMInfo(double& startBPM, double& minBPM, double& maxBPM, double& modeBPM) const { - startBPM = -1; - minBPM = -1; - maxBPM = -1; - modeBPM = -1; - - Map bpmDurations; - - MapTime lastMT = m_settings.offset; - - double largestMT = -1; - double lastBPM = -1; - - for (const TimingPoint& tp : m_timingPoints) - { - const double thisBPM = tp.GetBPM(); - const MapTime timeSinceLastTP = tp.time - lastMT; - - if (startBPM == -1) startBPM = thisBPM; - if (minBPM == -1 || minBPM > thisBPM) minBPM = thisBPM; - if (maxBPM == -1 || maxBPM < thisBPM) maxBPM = thisBPM; - - const double duration = bpmDurations[lastBPM] += timeSinceLastTP; - if (duration > largestMT) - { - modeBPM = lastBPM; - largestMT = duration; - } - lastMT = tp.time; - lastBPM = thisBPM; - } - - bpmDurations[lastBPM] += GetLastObjectTime() - lastMT; - - if (bpmDurations[lastBPM] > largestMT) - { - modeBPM = lastBPM; - } + startBPM = -1; + minBPM = -1; + maxBPM = -1; + modeBPM = -1; + + Map bpmDurations; + + MapTime lastMT = m_settings.offset; + + double largestMT = -1; + double lastBPM = -1; + + for (const TimingPoint& tp : m_timingPoints) + { + const double thisBPM = tp.GetBPM(); + const MapTime timeSinceLastTP = tp.time - lastMT; + + if (startBPM == -1) + startBPM = thisBPM; + if (minBPM == -1 || minBPM > thisBPM) + minBPM = thisBPM; + if (maxBPM == -1 || maxBPM < thisBPM) + maxBPM = thisBPM; + + const double duration = bpmDurations[lastBPM] += timeSinceLastTP; + if (duration > largestMT) + { + modeBPM = lastBPM; + largestMT = duration; + } + lastMT = tp.time; + lastBPM = thisBPM; + } + + bpmDurations[lastBPM] += GetLastObjectTime() - lastMT; + + if (bpmDurations[lastBPM] > largestMT) + { + modeBPM = lastBPM; + } } void Beatmap::Shuffle(int seed, bool random, bool mirror) { - if (!random && !mirror) return; - - if (!random) - { - assert(mirror); - ApplyShuffle({3, 2, 1, 0, 5, 4}, true); - - return; - } - - std::default_random_engine engine(seed); - std::array swaps = {0, 1, 2, 3, 4, 5}; - - std::shuffle(swaps.begin(), swaps.begin() + 4, engine); - std::shuffle(swaps.begin() + 4, swaps.end(), engine); - - bool unchanged = true; - for (int i = 0; i < 4; ++i) - { - if (swaps[i] != (mirror ? 3 - i : i)) - { - unchanged = true; - break; - } - } - - if (unchanged) - { - if (mirror) - { - swaps[4] = 4; - swaps[5] = 5; - } - else - { - swaps[4] = 5; - swaps[5] = 4; - } - } - - ApplyShuffle(swaps, mirror); + if (!random && !mirror) + return; + + if (!random) + { + assert(mirror); + ApplyShuffle({ 3, 2, 1, 0, 5, 4 }, true); + + return; + } + + std::default_random_engine engine(seed); + std::array swaps = { 0, 1, 2, 3, 4, 5 }; + + std::shuffle(swaps.begin(), swaps.begin() + 4, engine); + std::shuffle(swaps.begin() + 4, swaps.end(), engine); + + bool unchanged = true; + for (int i = 0; i < 4; ++i) + { + if (swaps[i] != (mirror ? 3 - i : i)) + { + unchanged = true; + break; + } + } + + if (unchanged) + { + if (mirror) + { + swaps[4] = 4; + swaps[5] = 5; + } + else + { + swaps[4] = 5; + swaps[5] = 4; + } + } + + ApplyShuffle(swaps, mirror); } void Beatmap::ApplyShuffle(const std::array& swaps, bool flipLaser) { - for (auto& object : m_objectStates) - { - if (object->type == ObjectType::Single || object->type == ObjectType::Hold) - { - ButtonObjectState* bos = (ButtonObjectState*)object.get(); - bos->index = swaps[bos->index]; - } - else if (object->type == ObjectType::Laser) - { - LaserObjectState* los = (LaserObjectState*)object.get(); - - if (flipLaser) - { - los->index = (los->index + 1) % 2; - for (size_t i = 0; i < 2; i++) - { - los->points[i] = fabsf(los->points[i] - 1.0f); - } - } - } - } + for (auto& object : m_objectStates) + { + if (object->type == ObjectType::Single || object->type == ObjectType::Hold) + { + ButtonObjectState* bos = (ButtonObjectState*)object.get(); + bos->index = swaps[bos->index]; + } + else if (object->type == ObjectType::Laser) + { + LaserObjectState* los = (LaserObjectState*)object.get(); + + if (flipLaser) + { + los->index = (los->index + 1) % 2; + for (size_t i = 0; i < 2; i++) + { + los->points[i] = fabsf(los->points[i] - 1.0f); + } + } + } + } } float Beatmap::GetBeatCount(MapTime start, MapTime end, TimingPointsIterator hint) const { - int sign = 1; + int sign = 1; - if (m_timingPoints.empty() || start == end) - { - return 0.0f; - } + if (m_timingPoints.empty() || start == end) + { + return 0.0f; + } - if (end < start) - { - std::swap(start, end); - sign = -1; - } + if (end < start) + { + std::swap(start, end); + sign = -1; + } - TimingPointsIterator tp = GetTimingPoint(start, hint); - assert(tp != m_timingPoints.end()); + TimingPointsIterator tp = GetTimingPoint(start, hint); + assert(tp != m_timingPoints.end()); - TimingPointsIterator tp_next = std::next(tp); + TimingPointsIterator tp_next = std::next(tp); - float result = 0.0f; - MapTime refTime = start; + float result = 0.0f; + MapTime refTime = start; - while (tp_next != m_timingPoints.end() && tp_next->time < end) - { - result += (tp_next->time - refTime) / tp->beatDuration; + while (tp_next != m_timingPoints.end() && tp_next->time < end) + { + result += (tp_next->time - refTime) / tp->beatDuration; - tp = tp_next; - tp_next = std::next(tp); - refTime = tp->time; - } + tp = tp_next; + tp_next = std::next(tp); + refTime = tp->time; + } - result += static_cast((end - refTime) / tp->beatDuration); + result += static_cast((end - refTime) / tp->beatDuration); - return sign * result; + return sign * result; } float Beatmap::GetBeatCountWithScrollSpeedApplied(MapTime start, MapTime end, TimingPointsIterator hint) const { - int sign = 1; + int sign = 1; - if (m_timingPoints.empty() || start == end) - { - return 0.0f; - } + if (m_timingPoints.empty() || start == end) + { + return 0.0f; + } - if (end < start) - { - std::swap(start, end); - sign = -1; - } + if (end < start) + { + std::swap(start, end); + sign = -1; + } - TimingPointsIterator tp = GetTimingPoint(start, hint); - assert(tp != m_timingPoints.end()); + TimingPointsIterator tp = GetTimingPoint(start, hint); + assert(tp != m_timingPoints.end()); - TimingPointsIterator tp_next = std::next(tp); + TimingPointsIterator tp_next = std::next(tp); - float result = 0.0f; - MapTime refTime = start; + float result = 0.0f; + MapTime refTime = start; - const LineGraph& scrollSpeedGraph = m_effects.GetGraph(EffectTimeline::GraphType::SCROLL_SPEED); + const LineGraph& scrollSpeedGraph = m_effects.GetGraph(EffectTimeline::GraphType::SCROLL_SPEED); - while (tp_next != m_timingPoints.end() && tp_next->time < end) - { - result += static_cast(scrollSpeedGraph.Integrate(refTime, tp_next->time) / tp->beatDuration); + while (tp_next != m_timingPoints.end() && tp_next->time < end) + { + result += static_cast(scrollSpeedGraph.Integrate(refTime, tp_next->time) / tp->beatDuration); - tp = tp_next; - tp_next = std::next(tp); - refTime = tp->time; - } + tp = tp_next; + tp_next = std::next(tp); + refTime = tp->time; + } - result += static_cast(scrollSpeedGraph.Integrate(refTime, end) / tp->beatDuration); + result += static_cast(scrollSpeedGraph.Integrate(refTime, end) / tp->beatDuration); - return sign * result; + return sign * result; } Beatmap::TimingPointsIterator Beatmap::GetTimingPoint(MapTime mapTime, TimingPointsIterator hint, bool forwardOnly) const { - if (m_timingPoints.empty()) - { - return m_timingPoints.end(); - } - - // Check for common cases - if (hint != m_timingPoints.end()) - { - if (hint->time <= mapTime) - { - if (std::next(hint) == m_timingPoints.end()) - { - return hint; - } - if (mapTime < std::next(hint)->time) - { - return hint; - } - else - { - ++hint; - } - } - else if (forwardOnly) - { - return hint; - } - else if (hint == m_timingPoints.begin()) - { - return hint; - } - else if (std::prev(hint)->time <= mapTime) - { - return std::prev(hint); - } - else - { - --hint; - } - } - - size_t hintInd = static_cast(std::distance(m_timingPoints.begin(), hint)); - - if (hint == m_timingPoints.end() || mapTime < hint->time) - { - // mapTime before hint - size_t diff = 1; - size_t prevDiff = 0; - while (diff <= hintInd) - { - if (m_timingPoints[hintInd - diff].time <= mapTime) - { - break; - } - - prevDiff = diff; - diff *= 2; - } - - if (diff > hintInd) - { - diff = hintInd; - } - return GetTimingPoint(mapTime, hintInd - diff, hintInd - prevDiff); - } - else if (hint->time == mapTime) - { - return hint; - } - else - { - // mapTime after hint - size_t diff = 1; - size_t prevDiff = 0; - while (hintInd + diff < m_timingPoints.size()) - { - if (mapTime <= m_timingPoints[hintInd + diff].time) - { - break; - } - - prevDiff = diff; - diff *= 2; - } - - if (hintInd + diff >= m_timingPoints.size()) - { - diff = m_timingPoints.size() - 1 - hintInd; - } - - return GetTimingPoint(mapTime, hintInd + prevDiff, hintInd + diff + 1); - } + if (m_timingPoints.empty()) + { + return m_timingPoints.end(); + } + + // Check for common cases + if (hint != m_timingPoints.end()) + { + if (hint->time <= mapTime) + { + if (std::next(hint) == m_timingPoints.end()) + { + return hint; + } + if (mapTime < std::next(hint)->time) + { + return hint; + } + else + { + ++hint; + } + } + else if (forwardOnly) + { + return hint; + } + else if (hint == m_timingPoints.begin()) + { + return hint; + } + else if (std::prev(hint)->time <= mapTime) + { + return std::prev(hint); + } + else + { + --hint; + } + } + + size_t hintInd = static_cast(std::distance(m_timingPoints.begin(), hint)); + + if (hint == m_timingPoints.end() || mapTime < hint->time) + { + // mapTime before hint + size_t diff = 1; + size_t prevDiff = 0; + while (diff <= hintInd) + { + if (m_timingPoints[hintInd - diff].time <= mapTime) + { + break; + } + + prevDiff = diff; + diff *= 2; + } + + if (diff > hintInd) + { + diff = hintInd; + } + return GetTimingPoint(mapTime, hintInd - diff, hintInd - prevDiff); + } + else if (hint->time == mapTime) + { + return hint; + } + else + { + // mapTime after hint + size_t diff = 1; + size_t prevDiff = 0; + while (hintInd + diff < m_timingPoints.size()) + { + if (mapTime <= m_timingPoints[hintInd + diff].time) + { + break; + } + + prevDiff = diff; + diff *= 2; + } + + if (hintInd + diff >= m_timingPoints.size()) + { + diff = m_timingPoints.size() - 1 - hintInd; + } + + return GetTimingPoint(mapTime, hintInd + prevDiff, hintInd + diff + 1); + } } Beatmap::TimingPointsIterator Beatmap::GetTimingPoint(MapTime mapTime, size_t begin, size_t end) const { - if (end <= begin) - { - return m_timingPoints.begin(); - } - - if (mapTime < m_timingPoints[begin].time) - { - return m_timingPoints.begin() + begin; - } - - if (end < m_timingPoints.size() && mapTime >= m_timingPoints[end].time) - { - return m_timingPoints.begin() + end; - } - - while (begin + 2 < end) - { - const size_t mid = (begin + end) / 2; - if (m_timingPoints[mid].time < mapTime) - { - begin = mid; - } - else if (m_timingPoints[mid].time > mapTime) - { - end = mid; - } - else - { - return m_timingPoints.begin() + mid; - } - } - - if (begin + 1 < end && m_timingPoints[begin + 1].time <= mapTime) - { - return m_timingPoints.begin() + (begin + 1); - } - else - { - return m_timingPoints.begin() + begin; - } + if (end <= begin) + { + return m_timingPoints.begin(); + } + + if (mapTime < m_timingPoints[begin].time) + { + return m_timingPoints.begin() + begin; + } + + if (end < m_timingPoints.size() && mapTime >= m_timingPoints[end].time) + { + return m_timingPoints.begin() + end; + } + + while (begin + 2 < end) + { + const size_t mid = (begin + end) / 2; + if (m_timingPoints[mid].time < mapTime) + { + begin = mid; + } + else if (m_timingPoints[mid].time > mapTime) + { + end = mid; + } + else + { + return m_timingPoints.begin() + mid; + } + } + + if (begin + 1 < end && m_timingPoints[begin + 1].time <= mapTime) + { + return m_timingPoints.begin() + (begin + 1); + } + else + { + return m_timingPoints.begin() + begin; + } } float Beatmap::GetGraphValueAt(EffectTimeline::GraphType type, MapTime mapTime) const { - return static_cast(m_effects.GetGraph(type).ValueAt(mapTime)); + return static_cast(m_effects.GetGraph(type).ValueAt(mapTime)); } bool Beatmap::CheckIfManualTiltInstant(MapTime bound, MapTime mapTime) const { - auto checkManualTiltInstant = [&](const EffectTimeline& timeline) { - const LineGraph& graph = timeline.GetGraph(EffectTimeline::GraphType::ROTATION_Z); - if (graph.empty()) return false; - - const LineGraph::PointsIterator point = graph.upper_bound(bound); - if (point == graph.end()) - { - return false; - } - - if (!point->second.IsSlam()) - { - return false; - } - - if (point->first > mapTime) - { - return false; - } - - return true; - }; - - return checkManualTiltInstant(m_effects); + auto checkManualTiltInstant = [&](const EffectTimeline& timeline) + { + const LineGraph& graph = timeline.GetGraph(EffectTimeline::GraphType::ROTATION_Z); + if (graph.empty()) + return false; + + const LineGraph::PointsIterator point = graph.upper_bound(bound); + if (point == graph.end()) + { + return false; + } + + if (!point->second.IsSlam()) + { + return false; + } + + if (point->first > mapTime) + { + return false; + } + + return true; + }; + + return checkManualTiltInstant(m_effects); } float Beatmap::GetCenterSplitValueAt(MapTime mapTime) const { - return static_cast(m_centerSplit.ValueAt(mapTime)); + return static_cast(m_centerSplit.ValueAt(mapTime)); } float Beatmap::GetScrollSpeedAt(MapTime mapTime) const { - return static_cast(m_effects.GetGraph(EffectTimeline::GraphType::SCROLL_SPEED).ValueAt(mapTime)); + return static_cast(m_effects.GetGraph(EffectTimeline::GraphType::SCROLL_SPEED).ValueAt(mapTime)); } BinaryStream& operator<<(BinaryStream& stream, BeatmapSettings& settings) { - stream << settings.title; - stream << settings.artist; - stream << settings.effector; - stream << settings.illustrator; - stream << settings.tags; + stream << settings.title; + stream << settings.artist; + stream << settings.effector; + stream << settings.illustrator; + stream << settings.tags; - stream << settings.bpm; - stream << settings.offset; + stream << settings.bpm; + stream << settings.offset; - stream << settings.audioNoFX; - stream << settings.audioFX; + stream << settings.audioNoFX; + stream << settings.audioFX; - stream << settings.jacketPath; + stream << settings.jacketPath; - stream << settings.level; - stream << settings.difficulty; + stream << settings.level; + stream << settings.difficulty; - stream << settings.previewOffset; - stream << settings.previewDuration; + stream << settings.previewOffset; + stream << settings.previewDuration; - stream << settings.slamVolume; - stream << settings.laserEffectMix; - stream << (uint8&)settings.laserEffectType; - return stream; + stream << settings.slamVolume; + stream << settings.laserEffectMix; + stream << (uint8&)settings.laserEffectType; + return stream; } bool BeatmapSettings::StaticSerialize(BinaryStream& stream, BeatmapSettings*& settings) { - if(stream.IsReading()) - settings = new BeatmapSettings(); - stream << *settings; - return true; -} \ No newline at end of file + if (stream.IsReading()) + settings = new BeatmapSettings(); + stream << *settings; + return true; +} diff --git a/Beatmap/src/BeatmapFromKSH.cpp b/Beatmap/src/BeatmapFromKSH.cpp index 0748ae97d..1b34ba275 100755 --- a/Beatmap/src/BeatmapFromKSH.cpp +++ b/Beatmap/src/BeatmapFromKSH.cpp @@ -1,1406 +1,1421 @@ -#include "stdafx.h" -#include "Beatmap.hpp" -#include "KShootMap.hpp" - -// Temporary object to keep track if a button is a hold button -struct TempButtonState -{ - TempButtonState(uint32 startTick) - : startTick(startTick) - { - } - uint32 startTick; - uint32 numTicks = 0; - EffectType effectType = EffectType::None; - uint16 effectParams[2] = {0}; - // If using the smalles grid to indicate hold note duration - bool fineSnap = false; - // Set for hold continuations, this is where there is a hold right after an existing one but with different effects - HoldObjectState *lastHoldObject = nullptr; - - uint8 sampleIndex = 0xFF; - bool usingSample = false; - float sampleVolume = 1.0f; -}; -struct TempLaserState -{ - TempLaserState(uint32 startTick, uint32 absoluteStartTick, uint32 effectType) - : startTick(startTick), effectType(effectType), absoluteStartTick(absoluteStartTick) - { - } - uint32 startTick; - uint32 absoluteStartTick; - uint32 numTicks = 0; - uint32 effectType = 0; - bool spinIsBounce = false; - char spinType = 0; - uint32 spinDuration = 0; - uint32 spinBounceAmplitude = 0; - uint32 spinBounceFrequency = 0; - uint32 spinBounceDecay = 0; - uint8 effectParams = 0; - float startPosition; // Entry position - // Previous segment - LaserObjectState *last = nullptr; -}; - -class EffectTypeMap -{ - // Custom effect types (1.60) - uint16 m_customEffectTypeID = (uint16)EffectType::UserDefined0; - -public: - EffectTypeMap() - { - // Add common effect types - effectTypes["None"] = EffectType::None; - effectTypes["Retrigger"] = EffectType::Retrigger; - effectTypes["Flanger"] = EffectType::Flanger; - effectTypes["Phaser"] = EffectType::Phaser; - effectTypes["Gate"] = EffectType::Gate; - effectTypes["TapeStop"] = EffectType::TapeStop; - effectTypes["BitCrusher"] = EffectType::Bitcrush; - effectTypes["Wobble"] = EffectType::Wobble; - effectTypes["SideChain"] = EffectType::SideChain; - effectTypes["Echo"] = EffectType::Echo; - effectTypes["Panning"] = EffectType::Panning; - effectTypes["PitchShift"] = EffectType::PitchShift; - effectTypes["LPF"] = EffectType::LowPassFilter; - effectTypes["HPF"] = EffectType::HighPassFilter; - effectTypes["PEAK"] = EffectType::PeakingFilter; - effectTypes["SwitchAudio"] = EffectType::SwitchAudio; - } - - // Only checks if a mapping exists and returns this, or None - const EffectType *FindEffectType(const String &name) const - { - return effectTypes.Find(name); - } - - // Adds or returns the enum value mapping to this effect - EffectType FindOrAddEffectType(const String &name) - { - EffectType *id = effectTypes.Find(name); - if (!id) - return effectTypes.Add(name, (EffectType)m_customEffectTypeID++); - return *id; - }; - - Map effectTypes; -}; - -template -void AssignAudioEffectParameter(EffectParam ¶m, const String ¶mName, Map &floatParams, Map &intParams) -{ - float *fval = floatParams.Find(paramName); - if (fval) - { - param = *fval; - return; - } - int32 *ival = intParams.Find(paramName); - if (ival) - { - param = *ival; - return; - } -} - -struct MultiParam -{ - enum Type - { - Float, - Samples, - Milliseconds, - Int, - }; - Type type; - union { - float fval; - int32 ival; - }; -}; -struct MultiParamRange -{ - MultiParamRange() = default; - MultiParamRange(const MultiParam &a) - { - params[0] = a; - } - MultiParamRange(const MultiParam &a, const MultiParam &b) - { - params[0] = a; - params[1] = b; - isRange = true; - } - EffectParam ToFloatParam() - { - auto r = params[0].type == MultiParam::Float ? EffectParam(params[0].fval, params[1].fval) : EffectParam((float)params[0].ival, (float)params[1].ival); - r.isRange = isRange; - return r; - } - EffectParam ToDurationParam() - { - EffectParam r; - if (params[0].type == MultiParam::Milliseconds) - { - r = EffectParam(params[0].ival, params[1].ival); - } - else if (params[0].type == MultiParam::Float) - { - r = EffectParam(params[0].fval, params[1].fval); - } - else - { - r = EffectParam((float)params[0].ival, (float)params[1].ival); - } - r.isRange = isRange; - return r; - } - EffectParam ToSamplesParam() - { - EffectParam r; - if (params[0].type == MultiParam::Int || params[0].type == MultiParam::Samples) - r = EffectParam(params[0].ival, params[1].ival); - r.isRange = isRange; - return r; - } - MultiParam params[2]; - bool isRange = false; -}; -static MultiParam ParseParam(const String &in) -{ - MultiParam ret; - if (in.find('/') != -1) - { - ret.type = MultiParam::Float; - String a, b; - in.Split("/", &a, &b); - ret.fval = (float)(atof(*a) / atof(*b)); - } - else if (in.find("samples") != -1) - { - ret.type = MultiParam::Samples; - sscanf(*in, "%i", &ret.ival); - } - else if (in.find("ms") != -1) - { - ret.type = MultiParam::Milliseconds; - float milliseconds = 0; - sscanf(*in, "%f", &milliseconds); - ret.ival = (int)(milliseconds); - } - else if (in.find("s") != -1) - { - ret.type = MultiParam::Milliseconds; - float seconds = 0; - sscanf(*in, "%f", &seconds); - ret.ival = (int)(seconds * 1000.0); - } - else if (in.find("%") != -1) - { - ret.type = MultiParam::Float; - int percentage = 0; - sscanf(*in, "%i", &percentage); - ret.fval = percentage / 100.0f; - } - else if (in.find('.') != -1) - { - ret.type = MultiParam::Float; - sscanf(*in, "%f", &ret.fval); - } - else - { - ret.type = MultiParam::Int; - sscanf(*in, "%i", &ret.ival); - } - return ret; -} -AudioEffect ParseCustomEffect(const KShootEffectDefinition &def, Vector &switchablePaths) -{ - static EffectTypeMap defaultEffects; - AudioEffect effect; - bool typeSet = false; - - Map params; - for (const auto& s : def.parameters) - { - // This one is easy - if (s.first == "type") - { - // Get the default effect for this name - const EffectType *type = defaultEffects.FindEffectType(s.second); - if (!type) - { - Logf("Unknown base effect type for custom effect type: %s", Logger::Severity::Warning, s.second); - continue; - } - effect = AudioEffect::GetDefault(*type); - typeSet = true; - } - else - { - // Special case for SwitchAudio effect - if (s.first == "fileName") - { - MultiParam switchableIndex; - switchableIndex.type = MultiParam::Int; - - auto it = std::find(switchablePaths.begin(), switchablePaths.end(), s.second); - if (it == switchablePaths.end()) - { - switchableIndex.ival = static_cast(switchablePaths.size()); - switchablePaths.Add(s.second); - } - else - { - switchableIndex.ival = static_cast(std::distance(switchablePaths.begin(), it)); - } - - params.Add("index", switchableIndex); - continue; - } - - size_t splitArrow = s.second.find('>', 1); - String param; - if (splitArrow != -1) - { - param = s.second.substr(splitArrow + 1); - } - else - { - param = s.second; - } - size_t split = param.find('-', 1); - if (split != -1) - { - String a, b; - a = param.substr(0, split); - b = param.substr(split + 1); - - MultiParamRange pr = {ParseParam(a), ParseParam(b)}; - if (pr.params[0].type != pr.params[1].type) - { - Logf("Non matching parameters types \"[%s, %s]\" for key: %s", Logger::Severity::Warning, s.first, param, s.first); - continue; - } - params.Add(s.first, pr); - } - else - { - params.Add(s.first, ParseParam(param)); - } - } - } - - if (!typeSet) - { - Logf("Type not set for custom effect type: %s", Logger::Severity::Warning, def.typeName); - return effect; - } - - auto AssignFloatIfSet = [&](EffectParam &target, const String &name) { - auto *param = params.Find(name); - if (param) - { - target = param->ToFloatParam(); - } - }; - auto AssignDurationIfSet = [&](EffectParam &target, const String &name) { - auto *param = params.Find(name); - if (param) - { - target = param->ToDurationParam(); - } - }; - auto AssignSamplesIfSet = [&](EffectParam &target, const String &name) { - auto *param = params.Find(name); - if (param && param->params[0].type == MultiParam::Samples) - { - target = param->ToSamplesParam(); - } - }; - auto AssignIntIfSet = [&](EffectParam &target, const String &name) { - auto *param = params.Find(name); - if (param) - { - target = param->ToSamplesParam(); - } - }; - - AssignFloatIfSet(effect.mix, "mix"); - - // Set individual parameters per effect based on if they are specified or not - // if they are not set the defaults will be kept (as aquired above) - switch (effect.type) - { - case EffectType::PitchShift: - AssignFloatIfSet(effect.pitchshift.amount, "pitch"); - break; - case EffectType::Bitcrush: - AssignSamplesIfSet(effect.bitcrusher.reduction, "amount"); - break; - case EffectType::Echo: - AssignDurationIfSet(effect.duration, "waveLength"); - AssignFloatIfSet(effect.echo.feedback, "feedbackLevel"); - break; - case EffectType::Phaser: - AssignDurationIfSet(effect.duration, "period"); - AssignIntIfSet(effect.phaser.stage, "stage"); - AssignFloatIfSet(effect.phaser.min, "loFreq"); - AssignFloatIfSet(effect.phaser.max, "hiFreq"); - AssignFloatIfSet(effect.phaser.q, "Q"); - AssignFloatIfSet(effect.phaser.feedback, "feedback"); - AssignFloatIfSet(effect.phaser.stereoWidth, "stereoWidth"); - AssignFloatIfSet(effect.phaser.hiCutGain, "hiCutGain"); - break; - case EffectType::Flanger: - AssignDurationIfSet(effect.duration, "period"); - AssignIntIfSet(effect.flanger.depth, "depth"); - AssignIntIfSet(effect.flanger.offset, "delay"); - AssignFloatIfSet(effect.flanger.feedback, "feedback"); - AssignFloatIfSet(effect.flanger.stereoWidth, "stereoWidth"); - AssignFloatIfSet(effect.flanger.volume, "volume"); - break; - case EffectType::Gate: - AssignDurationIfSet(effect.duration, "waveLength"); - AssignFloatIfSet(effect.gate.gate, "rate"); - break; - case EffectType::Retrigger: - AssignDurationIfSet(effect.duration, "waveLength"); - AssignFloatIfSet(effect.retrigger.gate, "rate"); - AssignDurationIfSet(effect.retrigger.reset, "updatePeriod"); - break; - case EffectType::Wobble: - AssignDurationIfSet(effect.duration, "waveLength"); - AssignFloatIfSet(effect.wobble.min, "loFreq"); - AssignFloatIfSet(effect.wobble.max, "hiFreq"); - AssignFloatIfSet(effect.wobble.q, "Q"); - break; - case EffectType::SideChain: - AssignDurationIfSet(effect.duration, "period"); - AssignDurationIfSet(effect.sidechain.attackTime, "attackTime"); - AssignDurationIfSet(effect.sidechain.holdTime, "holdTime"); - AssignDurationIfSet(effect.sidechain.releaseTime, "releaseTime"); - AssignFloatIfSet(effect.sidechain.ratio, "ratio"); - break; - case EffectType::TapeStop: - AssignDurationIfSet(effect.duration, "speed"); - break; - case EffectType::SwitchAudio: - AssignIntIfSet(effect.switchaudio.index, "index"); - break; - } - - return effect; -}; - -bool Beatmap::m_ProcessKShootMap(BinaryStream &input, bool metadataOnly) -{ - KShootMap kshootMap; - if (!kshootMap.Init(input, metadataOnly)) - return false; - - EffectTypeMap effectTypeMap; - EffectTypeMap filterTypeMap; - Map defaultEffectParams; - - // Set defaults - { - defaultEffectParams[EffectType::Bitcrush] = 4; - defaultEffectParams[EffectType::Gate] = 8; - defaultEffectParams[EffectType::Retrigger] = 8; - defaultEffectParams[EffectType::Phaser] = 2000; - defaultEffectParams[EffectType::Flanger] = 2000; - defaultEffectParams[EffectType::Wobble] = 12; - defaultEffectParams[EffectType::SideChain] = 8; - defaultEffectParams[EffectType::TapeStop] = 50; - } - - // Add all the custom effect types - for (auto it = kshootMap.fxDefines.begin(); it != kshootMap.fxDefines.end(); it++) - { - EffectType type = effectTypeMap.FindOrAddEffectType(it->first); - if (m_customAudioEffects.Contains(type)) - continue; - m_customAudioEffects.Add(type, ParseCustomEffect(it->second, m_switchablePaths)); - } - for (auto it = kshootMap.filterDefines.begin(); it != kshootMap.filterDefines.end(); it++) - { - EffectType type = filterTypeMap.FindOrAddEffectType(it->first); - if (m_customAudioFilters.Contains(type)) - continue; - m_customAudioFilters.Add(type, ParseCustomEffect(it->second, m_switchablePaths)); - } - - auto ParseFilterType = [&](const String &str) { - EffectType type = EffectType::None; - if (str == "hpf1") - { - type = EffectType::HighPassFilter; - } - else if (str == "lpf1") - { - type = EffectType::LowPassFilter; - } - else if (str == "fx;bitc" || str == "bitc") - { - type = EffectType::Bitcrush; - } - else if (str == "peak") - { - type = EffectType::PeakingFilter; - } - else - { - const EffectType *foundType = filterTypeMap.FindEffectType(str); - if (foundType) - type = *foundType; - else - Logf("[KSH]Unknown filter type: %s", Logger::Severity::Warning, str); - } - return type; - }; - - // Process map settings - m_settings.previewOffset = 0; - m_settings.previewDuration = 0; - for (auto &s : kshootMap.settings) - { - if (s.first == "title") - m_settings.title = s.second; - else if (s.first == "artist") - m_settings.artist = s.second; - else if (s.first == "effect") - m_settings.effector = s.second; - else if (s.first == "illustrator") - m_settings.illustrator = s.second; - else if (s.first == "t") - m_settings.bpm = s.second; - else if (s.first == "jacket") - m_settings.jacketPath = s.second; - else if (s.first == "bg") - m_settings.backgroundPath = s.second; - else if (s.first == "layer") - m_settings.foregroundPath = s.second; - else if (s.first == "m") - { - if (s.second.find(';') != -1) - { - String audioFX, audioNoFX; - s.second.Split(";", &audioNoFX, &audioFX); - size_t splitMore = audioFX.find(';'); - if (splitMore != -1) - audioFX = audioFX.substr(0, splitMore); - m_settings.audioFX = audioFX; - m_settings.audioNoFX = audioNoFX; - } - else - { - m_settings.audioNoFX = s.second; - } - } - else if (s.first == "o") - { - m_settings.offset = atol(*s.second); - } - // TODO: Move initial laser effect settings to an event instead - else if (s.first == "filtertype") - { - m_settings.laserEffectType = ParseFilterType(s.second); - } - else if (s.first == "pfiltergain") - { - m_settings.laserEffectMix = (float)atol(*s.second) / 100.0f; - } - else if (s.first == "chokkakuvol") - { - m_settings.slamVolume = (float)atol(*s.second) / 100.0f; - } - // end TODO - else if (s.first == "level") - { - m_settings.level = atoi(*s.second); - } - else if (s.first == "difficulty") - { - m_settings.difficulty = 0; - if (s.second == "challenge") - { - m_settings.difficulty = 1; - } - else if (s.second == "extended") - { - m_settings.difficulty = 2; - } - else if (s.second == "infinite") - { - m_settings.difficulty = 3; - } - } - else if (s.first == "po") - { - m_settings.previewOffset = atoi(*s.second); - } - else if (s.first == "plength") - { - m_settings.previewDuration = atoi(*s.second); - } - else if (s.first == "total") - { - m_settings.total = atoi(*s.second); - } - else if (s.first == "mvol") - { - m_settings.musicVolume = (float)atoi(*s.second) / 100.0f; - } - else if (s.first == "to") - { - m_settings.speedBpm = atof(*s.second); - } - } - - const static int tickResolution = 240; - - const TimingPoint* currTimingPoint = nullptr; - - /// For more accurate tracking of ticks for each timing point - size_t refTimingPointInd = 0; - double refTimingPointTime = 0.0; - - Vector timingPointTicks = {0}; - - auto TickToMapTime = [&](uint32 tick) { - if (tick < timingPointTicks[refTimingPointInd]) - { - refTimingPointInd = 0; - refTimingPointTime = static_cast(m_timingPoints[refTimingPointInd].time); - } - while (refTimingPointInd + 1 < timingPointTicks.size() && timingPointTicks[refTimingPointInd + 1] <= tick) - { - const MapTime timeDiff = timingPointTicks[refTimingPointInd+1] - timingPointTicks[refTimingPointInd]; - refTimingPointTime += Math::MSFromTicks((double) timeDiff, m_timingPoints[refTimingPointInd].GetBPM(), static_cast(tickResolution)); - ++refTimingPointInd; - } - - const uint32 tickDiff = tick - timingPointTicks[refTimingPointInd]; - - double mapTime = refTimingPointTime; - mapTime += Math::MSFromTicks((double) tickDiff, m_timingPoints[refTimingPointInd].GetBPM(), static_cast(tickResolution)); - return Math::RoundToInt(mapTime); - }; - - { - TimingPoint firstTimingPoint; - firstTimingPoint.numerator = 4; - firstTimingPoint.denominator = 4; - - firstTimingPoint.time = atol(*kshootMap.settings["o"]); - refTimingPointTime = static_cast(firstTimingPoint.time); - - const double bpm = atof(*kshootMap.settings["t"]); - firstTimingPoint.beatDuration = 60000.0 / bpm; - - currTimingPoint = &(m_timingPoints.Add(std::move(firstTimingPoint))); - } - - // Add First Lane Toggle Point - { - LaneHideTogglePoint startLaneTogglePoint; - startLaneTogglePoint.time = 0; - startLaneTogglePoint.duration = 1; - m_laneTogglePoints.Add(std::move(startLaneTogglePoint)); - } - - // Stop here if we're only going for metadata - if (metadataOnly) - { - return true; - } - - // Button hold states - TempButtonState *buttonStates[6] = {nullptr}; - // Laser segment states - TempLaserState *laserStates[2] = {nullptr}; - - EffectType currentButtonEffectTypes[2] = {EffectType::None}; - // 2 per button - int16 currentButtonEffectParams[4] = {-1}; - const uint32 maxEffectParamsPerButtons = 2; - float laserRanges[2] = {1.0f, 1.0f}; - MapTime lastLaserPointTime[2] = {0, 0}; - - // Stops will be applied after the scroll speed graph is constructed. - // Tuple of (stopBegin, stopEnd, isOverlappingStop) - Vector> stops; - - MapTime lastMapTime = 0; - uint32 currentTick = 0; - - bool isManualTilt = false; - for (KShootMap::TickIterator it(kshootMap); it; ++it) - { - const KShootBlock &block = it.GetCurrentBlock(); - KShootTime time = it.GetTime(); - const KShootTick &tick = *it; - float fxSampleVolume[2] = {1.0, 1.0}; - bool useFxSample[2] = {false, false}; - uint8 fxSampleIndex[2] = {0, 0}; - MapTime mapTime = TickToMapTime(currentTick); - bool lastTick = &block == &kshootMap.blocks.back() && - &tick == &block.ticks.back(); - - // flag set when a new effect parameter is set and a new hold notes should be created - bool splitupHoldNotes[2] = {false, false}; - - uint32 tickSettingIndex = 0; - // Process settings - for (auto &p : tick.settings) - { - // Functions that adds a new timing point at current location if it's not yet there - auto AddTimingPoint = [&](double newDuration, uint32 newNum, uint32 newDenom, int8 tickrateOffset) { - if (currTimingPoint->time != mapTime) - { - TimingPoint newTimingPoint = TimingPoint(*currTimingPoint); - newTimingPoint.time = mapTime; - - currTimingPoint = &(m_timingPoints.Add(std::move(newTimingPoint))); - timingPointTicks.Add(currentTick); - } - - TimingPoint& lastTimingPoint = *m_timingPoints.rbegin(); - - lastTimingPoint.numerator = newNum; - lastTimingPoint.denominator = newDenom; - lastTimingPoint.beatDuration = newDuration; - lastTimingPoint.tickrateOffset = tickrateOffset; - }; - - // Parser the effect and parameters of an FX button (1.60) - auto ParseFXAndParameters = [&](String in, int16 *paramsOut) { - // Clear parameters - memset(paramsOut, -1, sizeof(uint16) * maxEffectParamsPerButtons); - - String effectName = in; - size_t paramSplit = in.find_first_of(';'); - if (paramSplit != -1) - effectName = effectName.substr(0, paramSplit); - effectName.Trim(); - - // Clear effect instead? - if (effectName.empty()) - return EffectType::None; - - const EffectType *type = effectTypeMap.FindEffectType(effectName); - if (type == nullptr) - { - Logf("Invalid custom effect name in ksh map: %s", Logger::Severity::Warning, effectName); - return EffectType::None; - } - - if (paramSplit != -1) - { - String paramA, paramB; - String effectParams = p.second.substr(paramSplit + 1); - if (effectParams.Split(";", ¶mA, ¶mB)) - { - paramsOut[0] = atoi(*paramA); - paramsOut[1] = atoi(*paramB); - } - else - paramsOut[0] = atoi(*effectParams); - } - else //set default params - { - if (*type < EffectType::UserDefined0) { - switch (*type) - { - case EffectType::Flanger: - paramsOut[0] = 45; - paramsOut[1] = 15; - break; - - default: - break; - } - } - else { - m_customAudioEffects.at(*type).SetDefaultEffectParams(paramsOut); - } - } - - return *type; - }; - - if (p.first == "beat") - { - String n, d; - if (!p.second.Split("/", &n, &d)) - assert(false); - - uint32 num = atol(*n); - uint32 denom = atol(*d); - - AddTimingPoint(currTimingPoint->beatDuration, num, denom, currTimingPoint->tickrateOffset); - } - else if (p.first == "t") - { - const double bpm = atof(*p.second); - AddTimingPoint(60000.0 / bpm, currTimingPoint->numerator, currTimingPoint->denominator, currTimingPoint->tickrateOffset); - } - else if (p.first == "tickrate_offset") - { - int8 value = atoi(*p.second); - AddTimingPoint(currTimingPoint->beatDuration, currTimingPoint->numerator, currTimingPoint->denominator, value); - } - else if (p.first == "laserrange_l") - { - laserRanges[0] = 2.0f; - } - else if (p.first == "laserrange_r") - { - laserRanges[1] = 2.0f; - } - else if (p.first == "fx-l") // KSH 1.6 - { - currentButtonEffectTypes[0] = ParseFXAndParameters(p.second, currentButtonEffectParams); - splitupHoldNotes[0] = true; - } - else if (p.first == "fx-r") // KSH 1.6 - { - currentButtonEffectTypes[1] = ParseFXAndParameters(p.second, currentButtonEffectParams + maxEffectParamsPerButtons); - splitupHoldNotes[1] = true; - } - else if (p.first == "fx-l_param1") - { - currentButtonEffectParams[0] = atoi(*p.second); - splitupHoldNotes[0] = true; - } - else if (p.first == "fx-r_param1") - { - currentButtonEffectParams[maxEffectParamsPerButtons] = atoi(*p.second); - splitupHoldNotes[1] = true; - } - else if (p.first == "filtertype") - { - // Inser filter type change event - EventObjectState* evt = new EventObjectState(); - evt->interTickIndex = tickSettingIndex; - evt->time = mapTime; - evt->key = EventKey::LaserEffectType; - evt->data.effectVal = ParseFilterType(p.second); - m_objectStates.emplace_back(std::unique_ptr(*evt)); - } - else if (p.first == "pfiltergain") - { - // Inser filter type change event - float gain = (float)atol(*p.second) / 100.0f; - EventObjectState* evt = new EventObjectState(); - evt->interTickIndex = tickSettingIndex; - evt->time = mapTime; - evt->key = EventKey::LaserEffectMix; - evt->data.floatVal = gain; - m_objectStates.emplace_back(std::unique_ptr(*evt)); - } - else if (p.first == "chokkakuvol") - { - float vol = (float)atol(*p.second) / 100.0f; - EventObjectState* evt = new EventObjectState(); - evt->interTickIndex = tickSettingIndex; - evt->time = mapTime; - evt->key = EventKey::LaserEffectMix; - evt->data.floatVal = vol; - m_objectStates.emplace_back(std::unique_ptr(*evt)); - } - else if (p.first == "zoom_bottom") - { - const double value = atol(p.second.data()) / 100.0; - m_effects.InsertGraphValue(EffectTimeline::GraphType::ZOOM_BOTTOM, mapTime, value); - } - else if (p.first == "zoom_top") - { - const double value = atol(p.second.data()) / 100.0; - m_effects.InsertGraphValue(EffectTimeline::GraphType::ZOOM_TOP, mapTime, value); - } - else if (p.first == "zoom_side") - { - const double value = atol(p.second.data()) / 100.0; - m_effects.InsertGraphValue(EffectTimeline::GraphType::SHIFT_X, mapTime, value); - } - /* OLD USC MANUAL ROLL, KEPT JUST IN CASE - else if (p.first == "roll") - { - ZoomControlPoint* point = new ZoomControlPoint(); - point->time = mapTime; - point->index = 3; - point->zoom = (float)atol(*p.second) / 360.0f; - m_zoomControlPoints.Add(point); - CHECK_FIRST; - } - */ - else if (p.first == "lane_toggle") - { - LaneHideTogglePoint point; - point.time = mapTime; - point.duration = atol(*p.second); - m_laneTogglePoints.Add(std::move(point)); - } - else if (p.first == "center_split") - { - const double value = atol(*p.second) / 100.0; - m_centerSplit.Insert(mapTime, value); - } - else if (p.first == "tilt") - { - EventObjectState *evt = new EventObjectState(); - evt->time = mapTime; - evt->interTickIndex = tickSettingIndex; - evt->key = EventKey::TrackRollBehaviour; - evt->data.rollVal = TrackRollBehaviour::Zero; - - String v = p.second; - size_t f = v.find("keep_"); - if (f != -1) - { - evt->data.rollVal = TrackRollBehaviour::Keep; - v = v.substr(f + 5); - } - - if (v == "normal") - evt->data.rollVal = evt->data.rollVal | TrackRollBehaviour::Normal; - else if (v == "bigger") - evt->data.rollVal = evt->data.rollVal | TrackRollBehaviour::Bigger; - else if (v == "biggest") - evt->data.rollVal = evt->data.rollVal | TrackRollBehaviour::Biggest; - else if (v == "zero") - evt->data.rollVal = evt->data.rollVal | TrackRollBehaviour::Zero; - else - { - evt->data.rollVal = TrackRollBehaviour::Manual; - - const double rotation = atof(p.second.data()) * -(10.0 / 360.0); - m_effects.InsertGraphValue(EffectTimeline::GraphType::ROTATION_Z, mapTime, rotation); - - isManualTilt = true; - goto after_manual_check; - } - - if (isManualTilt) - { - m_effects.GetGraph(EffectTimeline::GraphType::ROTATION_Z).Extend(mapTime); - } - - after_manual_check: - m_objectStates.emplace_back(std::unique_ptr(*evt)); - } - else if (p.first == "fx-r_se") - { - String filename, vol; - int fxi = 1; - useFxSample[fxi] = true; - if (p.second.Split(";", &filename, &vol)) - { - fxSampleVolume[fxi] = (float)atoi(*vol) / 100.0f; - } - else - { - filename = p.second; - } - - auto it = std::find(m_samplePaths.begin(), m_samplePaths.end(), filename); - if (it == m_samplePaths.end()) - { - fxSampleIndex[fxi] = static_cast(m_samplePaths.size()); - m_samplePaths.Add(filename); - } - else - { - fxSampleIndex[fxi] = static_cast(std::distance(m_samplePaths.begin(), it)); - } - } - else if (p.first == "fx-l_se") - { - String filename, vol; - int fxi = 0; - useFxSample[fxi] = true; - if (p.second.Split(";", &filename, &vol)) - { - fxSampleVolume[fxi] = (float)atoi(*vol) / 100.0f; - } - else - { - filename = p.second; - } - - auto it = std::find(m_samplePaths.begin(), m_samplePaths.end(), filename); - if (it == m_samplePaths.end()) - { - fxSampleIndex[fxi] = static_cast(m_samplePaths.size()); - m_samplePaths.Add(filename); - } - else - { - fxSampleIndex[fxi] = std::distance(m_samplePaths.begin(), it); - } - } - else if (p.first == "stop") - { - // Stops will be applied after the scroll speed graph is constructed. - const MapTime stopDuration = Math::RoundToInt((atol(*p.second) / 192.0f) * (currTimingPoint->beatDuration) * 4); - bool isOverlappingStop = false; - - if (!stops.empty() && mapTime < std::get<1>(*stops.rbegin())) - { - isOverlappingStop = true; - std::get<2>(*stops.rbegin()) = true; - } - - stops.Add(std::make_tuple(mapTime, mapTime + stopDuration, isOverlappingStop)); - } - else if (p.first == "scroll_speed") - { - LineGraph& scrollSpeedGraph = m_effects.GetGraph(EffectTimeline::GraphType::SCROLL_SPEED); - scrollSpeedGraph.Insert(mapTime, atol(p.second.data()) / 100.0); - } - else - { - Logf("[KSH]Unkown map parameter at %d:%d: %s", Logger::Severity::Warning, it.GetTime().block, it.GetTime().tick, p.first); - } - tickSettingIndex++; - } - - // Set button states - for (uint32 i = 0; i < 6; i++) - { - char c = i < 4 ? tick.buttons[i] : tick.fx[i - 4]; - TempButtonState *&state = buttonStates[i]; - HoldObjectState *lastHoldObject = nullptr; - - auto IsHoldState = [&]() { - return state && state->numTicks > 0 && state->fineSnap; - }; - auto CreateButton = [&]() { - if (IsHoldState()) - { - HoldObjectState *obj = lastHoldObject = new HoldObjectState(); - obj->time = TickToMapTime(state->startTick); - obj->index = i; - obj->duration = TickToMapTime(currentTick) - obj->time; - obj->effectType = state->effectType; - if (state->lastHoldObject) - state->lastHoldObject->next = obj; - obj->prev = state->lastHoldObject; - memcpy(obj->effectParams, state->effectParams, sizeof(state->effectParams)); - m_objectStates.emplace_back(std::unique_ptr(*obj));; - } - else - { - ButtonObjectState *obj = new ButtonObjectState(); - - obj->time = TickToMapTime(state->startTick); - obj->index = i; - obj->hasSample = state->usingSample; - obj->sampleIndex = state->sampleIndex; - obj->sampleVolume = state->sampleVolume; - m_objectStates.emplace_back(std::unique_ptr(*obj)); - } - - // Reset - delete state; - state = nullptr; - }; - - // Split up multiple hold notes - if (i > 3 && IsHoldState() && splitupHoldNotes[i - 4]) - { - CreateButton(); - } - - if (c == '0') - { - // Terminate hold button - if (state) - { - CreateButton(); - } - - if (i >= 4) - { - // Unset effect parameters - currentButtonEffectParams[i - 4] = -1; - } - } - else if (!state) - { - // Create new hold state - state = new TempButtonState(currentTick); - uint32 div = (uint32)block.ticks.size(); - - if (lastHoldObject) - state->lastHoldObject = lastHoldObject; - - if (i < 4) - { - // Normal '1' notes are always individual - state->fineSnap = c != '1'; - } - else - { - // FX object '2' is always individual - state->fineSnap = c != '2'; - - // Set effect - if (c == 'B') - { - state->effectType = EffectType::Bitcrush; - if (currentButtonEffectParams[i - 4] != -1) - state->effectParams[0] = currentButtonEffectParams[i - 4]; - else - state->effectParams[0] = 5; - } - else if (c >= 'G' && c <= 'L') // Gate 4/8/16/32/12/24 - { - state->effectType = EffectType::Gate; - int16 paramMap[] = { - 4, 8, 16, 32, 12, 24}; - state->effectParams[0] = paramMap[c - 'G']; - } - else if (c >= 'S' && c <= 'W') // Retrigger 8/16/32/12/24 - { - state->effectType = EffectType::Retrigger; - int16 paramMap[] = { - 8, 16, 32, 12, 24}; - state->effectParams[0] = paramMap[c - 'S']; - } - else if (c == 'Q') - { - state->effectType = EffectType::Phaser; - } - else if (c == 'F') - { - state->effectType = EffectType::Flanger; - state->effectParams[0] = 5000; - } - else if (c == 'X') - { - state->effectType = EffectType::Wobble; - state->effectParams[0] = 12; - } - else if (c == 'D') - { - state->effectType = EffectType::SideChain; - } - else if (c == 'A') - { - state->effectType = EffectType::TapeStop; - if (currentButtonEffectParams[i - 4] != -1) - memcpy(state->effectParams, currentButtonEffectParams + (i - 4) * maxEffectParamsPerButtons, - sizeof(state->effectParams)); - else - state->effectParams[0] = 50; - } - else if (c == '2') - { - state->sampleIndex = fxSampleIndex[i - 4]; - state->usingSample = useFxSample[i - 4]; - state->sampleVolume = fxSampleVolume[i - 4]; - } - else - { - // Use settings method of setting effects+params (1.60) - state->effectType = currentButtonEffectTypes[i - 4]; - if (currentButtonEffectParams[(i - 4) * maxEffectParamsPerButtons] != -1) - memcpy(state->effectParams, currentButtonEffectParams + (i - 4) * maxEffectParamsPerButtons, - sizeof(state->effectParams)); - else - { - state->effectParams[0] = defaultEffectParams[state->effectType]; - state->effectParams[1] = 0; - } - } - } - } - else - { - // For buttons not using the 1/32 grid - if (!state->fineSnap) - { - CreateButton(); - - // Create new hold state - state = new TempButtonState(currentTick); - uint32 div = (uint32)block.ticks.size(); - - if (i < 4) - { - // Normal '1' notes are always individual - state->fineSnap = c != '1'; - } - else - { - // Hold are always on a high enough snap to make suere they are seperate when needed - if (c == '2') - { - state->fineSnap = false; - state->sampleIndex = fxSampleIndex[i - 4]; - state->usingSample = useFxSample[i - 4]; - state->sampleVolume = fxSampleVolume[i - 4]; - } - else - { - state->fineSnap = true; - state->effectType = currentButtonEffectTypes[i - 4]; - memcpy(state->effectParams, currentButtonEffectParams + (i - 4) * maxEffectParamsPerButtons, - sizeof(state->effectParams)); - } - } - } - else - { - // Update current hold state - state->numTicks++; - } - } - - // Terminate last item - if (lastTick && state) - CreateButton(); - } - - // Set laser states - for (uint32 i = 0; i < 2; i++) - { - TempLaserState *&state = laserStates[i]; - char c = tick.laser[i]; - - // Function that creates a new segment out of the current state - auto CreateLaserSegment = [&](float endPos) { - // Process existing segment - //assert(state->numTicks > 0); - - LaserObjectState *obj = new LaserObjectState(); - - obj->time = TickToMapTime(state->startTick); - obj->tick = state->startTick; - obj->duration = TickToMapTime(currentTick) - obj->time; - obj->index = i; - obj->points[0] = state->startPosition; - obj->points[1] = endPos; - uint32 tickDuration = currentTick - state->absoluteStartTick; - - if (laserRanges[i] > 1.0f) - { - obj->flags |= LaserObjectState::flag_Extended; - } - uint32 laserSlamThreshold = tickResolution / 8; - bool lastSlam = (state->last && (state->last->flags & LaserObjectState::flag_Instant) != 0); // Deal with super fast repeat slams - - if (tickDuration <= laserSlamThreshold && (obj->points[1] != obj->points[0])) - { - obj->flags |= LaserObjectState::flag_Instant; - obj->time = TickToMapTime(state->absoluteStartTick); - obj->tick = state->absoluteStartTick; - if (state->spinType != 0) - { - obj->spin.duration = state->spinDuration; - obj->spin.amplitude = state->spinBounceAmplitude; - obj->spin.frequency = state->spinBounceFrequency; - obj->spin.decay = state->spinBounceDecay; - - if (state->spinIsBounce) - obj->spin.type = SpinStruct::SpinType::Bounce; - else - { - switch (state->spinType) - { - case '(': - case ')': - obj->spin.type = SpinStruct::SpinType::Full; - break; - case '<': - case '>': - obj->spin.type = SpinStruct::SpinType::Quarter; - break; - default: - break; - } - } - - switch (state->spinType) - { - case '<': - case '(': - obj->spin.direction = -1.0f; - break; - case ')': - case '>': - obj->spin.direction = 1.0f; - break; - default: - break; - } - } - } - - // Link segments together - if (state->last) - { - // Always fixup duration so they are connected by duration as well - obj->prev = state->last; - MapTime actualPrevDuration = obj->time - obj->prev->time; - if (obj->prev->duration != actualPrevDuration) - { - obj->prev->duration = actualPrevDuration; - } - obj->prev->next = obj; - } - - if ((obj->flags & LaserObjectState::flag_Instant) != 0 && lastSlam) //add short straight segment between the slams - { - auto midobj = new LaserObjectState(); - midobj->flags = obj->prev->flags & ~LaserObjectState::flag_Instant; - midobj->points[0] = obj->points[0]; - midobj->points[1] = obj->points[0]; - midobj->time = obj->prev->time; - midobj->duration = lastLaserPointTime[i] - midobj->time; - midobj->index = obj->index; - - obj->time = lastLaserPointTime[i]; - - midobj->prev = obj->prev; - obj->prev = midobj; - midobj->next = obj; - midobj->prev->next = midobj; - - m_objectStates.emplace_back(std::unique_ptr(*midobj)); - } - - // Add to list of objects - - assert(obj->GetRoot() != nullptr); - - m_objectStates.emplace_back(std::unique_ptr(*obj)); - - return obj; - }; - - if (c == '-') - { - // Terminate laser - if (state) - { - // Reset state - delete state; - state = nullptr; - - // Reset range extension - laserRanges[i] = 1.0f; - } - } - else if (c == ':') - { - // Update current laser state - if (state) - { - state->numTicks++; - } - } - else - { - float pos = kshootMap.TranslateLaserChar(c); - LaserObjectState *last = nullptr; - if (state) - { - last = CreateLaserSegment(pos); - - // Reset state - delete state; - state = nullptr; - } - - uint32 startTick = currentTick; - if (last && (last->flags & LaserObjectState::flag_Instant) != 0) - { - // Move offset to be the same as last segment, as in ksh maps there is a 1 tick delay after laser slams - startTick = last->tick; - } - state = new TempLaserState(startTick, currentTick, 0); - state->last = last; // Link together - state->startPosition = pos; - - //@[Type][Speed] = spin - //Types - //) or ( = full spin - //> or < = quarter spin - //Speed is number of 192nd notes - if (!tick.add.empty() && (tick.add[0] == '@' || tick.add[0] == 'S')) - { - state->spinIsBounce = tick.add[0] == 'S'; - state->spinType = tick.add[1]; - - String add = tick.add.substr(2); - if (state->spinIsBounce) - { - String duration, amplitude, frequency, decay; - - add.Split(";", &duration, &litude); - amplitude.Split(";", &litude, &frequency); - frequency.Split(";", &frequency, &decay); - - state->spinDuration = std::stoi(duration); - state->spinBounceAmplitude = std::stoi(amplitude); - state->spinBounceFrequency = std::stoi(frequency); - state->spinBounceDecay = std::stoi(decay); - } - else - { - state->spinDuration = std::stoi(add); - if (state->spinType == '(' || state->spinType == ')') - state->spinDuration = state->spinDuration; - } - } - - lastLaserPointTime[i] = mapTime; - } - } - - lastMapTime = mapTime; - currentTick += static_cast((tickResolution * 4 * currTimingPoint->numerator / currTimingPoint->denominator) / block.ticks.size()); - } - - // Apply stops - for (const auto& stop : stops) - { - const MapTime stopBegin = std::get<0>(stop); - const MapTime stopEnd = std::get<1>(stop); - const bool isOverlapping = std::get<2>(stop); - - LineGraph& scrollSpeedGraph = m_effects.GetGraph(EffectTimeline::GraphType::SCROLL_SPEED); - - // In older versions of USC there was a bug where overlapping stop regions made notes scrolling backwards. - // In other words, stops weren't actually setting the scroll speed to 0, but instead decreased the speed by 1. - // This bug was utilized as gimmicks for several charts, so for backwards compatibility this behavior is reimplemented when stops are overlapping. - // For individual stops, scroll speed will actually set to 0 to make those behave nicely with manual scroll speed modifiers. - - if (isOverlapping) - { - scrollSpeedGraph.RangeAdd(stopBegin, stopEnd, -1.0); - } - else - { - scrollSpeedGraph.RangeSet(stopBegin, stopEnd, 0.0); - } - } - - // Add chart end event - EventObjectState *evt = new EventObjectState(); - evt->time = lastMapTime + 2000; - evt->key = EventKey::ChartEnd; - m_objectStates.emplace_back(std::unique_ptr(*evt)); - - // Re-sort collection to fix some inconsistencies caused by corrections after laser slams - ObjectState::SortArray(m_objectStates); - - return true; -} +#include "stdafx.h" +#include "Beatmap.hpp" +#include "KShootMap.hpp" + +// Temporary object to keep track if a button is a hold button +struct TempButtonState +{ + TempButtonState(uint32 startTick) + : startTick(startTick) + { + } + uint32 startTick; + uint32 numTicks = 0; + EffectType effectType = EffectType::None; + uint16 effectParams[2] = { 0 }; + // If using the smalles grid to indicate hold note duration + bool fineSnap = false; + // Set for hold continuations, this is where there is a hold right after an existing one but with different effects + HoldObjectState* lastHoldObject = nullptr; + + uint8 sampleIndex = 0xFF; + bool usingSample = false; + float sampleVolume = 1.0f; +}; +struct TempLaserState +{ + TempLaserState(uint32 startTick, uint32 absoluteStartTick, uint32 effectType) + : startTick(startTick), effectType(effectType), absoluteStartTick(absoluteStartTick) + { + } + uint32 startTick; + uint32 absoluteStartTick; + uint32 numTicks = 0; + uint32 effectType = 0; + bool spinIsBounce = false; + char spinType = 0; + uint32 spinDuration = 0; + uint32 spinBounceAmplitude = 0; + uint32 spinBounceFrequency = 0; + uint32 spinBounceDecay = 0; + uint8 effectParams = 0; + float startPosition; // Entry position + // Previous segment + LaserObjectState* last = nullptr; +}; + +class EffectTypeMap +{ + // Custom effect types (1.60) + uint16 m_customEffectTypeID = (uint16)EffectType::UserDefined0; + +public: + EffectTypeMap() + { + // Add common effect types + effectTypes["None"] = EffectType::None; + effectTypes["Retrigger"] = EffectType::Retrigger; + effectTypes["Flanger"] = EffectType::Flanger; + effectTypes["Phaser"] = EffectType::Phaser; + effectTypes["Gate"] = EffectType::Gate; + effectTypes["TapeStop"] = EffectType::TapeStop; + effectTypes["BitCrusher"] = EffectType::Bitcrush; + effectTypes["Wobble"] = EffectType::Wobble; + effectTypes["SideChain"] = EffectType::SideChain; + effectTypes["Echo"] = EffectType::Echo; + effectTypes["Panning"] = EffectType::Panning; + effectTypes["PitchShift"] = EffectType::PitchShift; + effectTypes["LPF"] = EffectType::LowPassFilter; + effectTypes["HPF"] = EffectType::HighPassFilter; + effectTypes["PEAK"] = EffectType::PeakingFilter; + effectTypes["SwitchAudio"] = EffectType::SwitchAudio; + } + + // Only checks if a mapping exists and returns this, or None + const EffectType* FindEffectType(const String& name) const + { + return effectTypes.Find(name); + } + + // Adds or returns the enum value mapping to this effect + EffectType FindOrAddEffectType(const String& name) + { + EffectType* id = effectTypes.Find(name); + if (!id) + return effectTypes.Add(name, (EffectType)m_customEffectTypeID++); + return *id; + }; + + Map effectTypes; +}; + +template +void AssignAudioEffectParameter(EffectParam& param, const String& paramName, Map& floatParams, Map& intParams) +{ + float* fval = floatParams.Find(paramName); + if (fval) + { + param = *fval; + return; + } + int32* ival = intParams.Find(paramName); + if (ival) + { + param = *ival; + return; + } +} + +struct MultiParam +{ + enum Type + { + Float, + Samples, + Milliseconds, + Int, + }; + Type type; + union + { + float fval; + int32 ival; + }; +}; +struct MultiParamRange +{ + MultiParamRange() = default; + MultiParamRange(const MultiParam& a) + { + params[0] = a; + } + MultiParamRange(const MultiParam& a, const MultiParam& b) + { + params[0] = a; + params[1] = b; + isRange = true; + } + EffectParam ToFloatParam() + { + auto r = params[0].type == MultiParam::Float ? EffectParam(params[0].fval, params[1].fval) : EffectParam((float)params[0].ival, (float)params[1].ival); + r.isRange = isRange; + return r; + } + EffectParam ToDurationParam() + { + EffectParam r; + if (params[0].type == MultiParam::Milliseconds) + { + r = EffectParam(params[0].ival, params[1].ival); + } + else if (params[0].type == MultiParam::Float) + { + r = EffectParam(params[0].fval, params[1].fval); + } + else + { + r = EffectParam((float)params[0].ival, (float)params[1].ival); + } + r.isRange = isRange; + return r; + } + EffectParam ToSamplesParam() + { + EffectParam r; + if (params[0].type == MultiParam::Int || params[0].type == MultiParam::Samples) + r = EffectParam(params[0].ival, params[1].ival); + r.isRange = isRange; + return r; + } + MultiParam params[2]; + bool isRange = false; +}; +static MultiParam ParseParam(const String& in) +{ + MultiParam ret; + if (in.find('/') != -1) + { + ret.type = MultiParam::Float; + String a, b; + in.Split("/", &a, &b); + ret.fval = (float)(atof(*a) / atof(*b)); + } + else if (in.find("samples") != -1) + { + ret.type = MultiParam::Samples; + sscanf(*in, "%i", &ret.ival); + } + else if (in.find("ms") != -1) + { + ret.type = MultiParam::Milliseconds; + float milliseconds = 0; + sscanf(*in, "%f", &milliseconds); + ret.ival = (int)(milliseconds); + } + else if (in.find("s") != -1) + { + ret.type = MultiParam::Milliseconds; + float seconds = 0; + sscanf(*in, "%f", &seconds); + ret.ival = (int)(seconds * 1000.0); + } + else if (in.find("%") != -1) + { + ret.type = MultiParam::Float; + int percentage = 0; + sscanf(*in, "%i", &percentage); + ret.fval = percentage / 100.0f; + } + else if (in.find('.') != -1) + { + ret.type = MultiParam::Float; + sscanf(*in, "%f", &ret.fval); + } + else + { + ret.type = MultiParam::Int; + sscanf(*in, "%i", &ret.ival); + } + return ret; +} +AudioEffect ParseCustomEffect(const KShootEffectDefinition& def, Vector& switchablePaths) +{ + static EffectTypeMap defaultEffects; + AudioEffect effect; + bool typeSet = false; + + Map params; + for (const auto& s : def.parameters) + { + // This one is easy + if (s.first == "type") + { + // Get the default effect for this name + const EffectType* type = defaultEffects.FindEffectType(s.second); + if (!type) + { + Logf("Unknown base effect type for custom effect type: %s", Logger::Severity::Warning, s.second); + continue; + } + effect = AudioEffect::GetDefault(*type); + typeSet = true; + } + else + { + // Special case for SwitchAudio effect + if (s.first == "fileName") + { + MultiParam switchableIndex; + switchableIndex.type = MultiParam::Int; + + auto it = std::find(switchablePaths.begin(), switchablePaths.end(), s.second); + if (it == switchablePaths.end()) + { + switchableIndex.ival = static_cast(switchablePaths.size()); + switchablePaths.Add(s.second); + } + else + { + switchableIndex.ival = static_cast(std::distance(switchablePaths.begin(), it)); + } + + params.Add("index", switchableIndex); + continue; + } + + size_t splitArrow = s.second.find('>', 1); + String param; + if (splitArrow != -1) + { + param = s.second.substr(splitArrow + 1); + } + else + { + param = s.second; + } + size_t split = param.find('-', 1); + if (split != -1) + { + String a, b; + a = param.substr(0, split); + b = param.substr(split + 1); + + MultiParamRange pr = { ParseParam(a), ParseParam(b) }; + if (pr.params[0].type != pr.params[1].type) + { + Logf("Non matching parameters types \"[%s, %s]\" for key: %s", Logger::Severity::Warning, s.first, param, s.first); + continue; + } + params.Add(s.first, pr); + } + else + { + params.Add(s.first, ParseParam(param)); + } + } + } + + if (!typeSet) + { + Logf("Type not set for custom effect type: %s", Logger::Severity::Warning, def.typeName); + return effect; + } + + auto AssignFloatIfSet = [&](EffectParam& target, const String& name) + { + auto* param = params.Find(name); + if (param) + { + target = param->ToFloatParam(); + } + }; + auto AssignDurationIfSet = [&](EffectParam& target, const String& name) + { + auto* param = params.Find(name); + if (param) + { + target = param->ToDurationParam(); + } + }; + auto AssignSamplesIfSet = [&](EffectParam& target, const String& name) + { + auto* param = params.Find(name); + if (param && param->params[0].type == MultiParam::Samples) + { + target = param->ToSamplesParam(); + } + }; + auto AssignIntIfSet = [&](EffectParam& target, const String& name) + { + auto* param = params.Find(name); + if (param) + { + target = param->ToSamplesParam(); + } + }; + + AssignFloatIfSet(effect.mix, "mix"); + + // Set individual parameters per effect based on if they are specified or not + // if they are not set the defaults will be kept (as aquired above) + switch (effect.type) + { + case EffectType::PitchShift: + AssignFloatIfSet(effect.pitchshift.amount, "pitch"); + break; + case EffectType::Bitcrush: + AssignSamplesIfSet(effect.bitcrusher.reduction, "amount"); + break; + case EffectType::Echo: + AssignDurationIfSet(effect.duration, "waveLength"); + AssignFloatIfSet(effect.echo.feedback, "feedbackLevel"); + break; + case EffectType::Phaser: + AssignDurationIfSet(effect.duration, "period"); + AssignIntIfSet(effect.phaser.stage, "stage"); + AssignFloatIfSet(effect.phaser.min, "loFreq"); + AssignFloatIfSet(effect.phaser.max, "hiFreq"); + AssignFloatIfSet(effect.phaser.q, "Q"); + AssignFloatIfSet(effect.phaser.feedback, "feedback"); + AssignFloatIfSet(effect.phaser.stereoWidth, "stereoWidth"); + AssignFloatIfSet(effect.phaser.hiCutGain, "hiCutGain"); + break; + case EffectType::Flanger: + AssignDurationIfSet(effect.duration, "period"); + AssignIntIfSet(effect.flanger.depth, "depth"); + AssignIntIfSet(effect.flanger.offset, "delay"); + AssignFloatIfSet(effect.flanger.feedback, "feedback"); + AssignFloatIfSet(effect.flanger.stereoWidth, "stereoWidth"); + AssignFloatIfSet(effect.flanger.volume, "volume"); + break; + case EffectType::Gate: + AssignDurationIfSet(effect.duration, "waveLength"); + AssignFloatIfSet(effect.gate.gate, "rate"); + break; + case EffectType::Retrigger: + AssignDurationIfSet(effect.duration, "waveLength"); + AssignFloatIfSet(effect.retrigger.gate, "rate"); + AssignDurationIfSet(effect.retrigger.reset, "updatePeriod"); + break; + case EffectType::Wobble: + AssignDurationIfSet(effect.duration, "waveLength"); + AssignFloatIfSet(effect.wobble.min, "loFreq"); + AssignFloatIfSet(effect.wobble.max, "hiFreq"); + AssignFloatIfSet(effect.wobble.q, "Q"); + break; + case EffectType::SideChain: + AssignDurationIfSet(effect.duration, "period"); + AssignDurationIfSet(effect.sidechain.attackTime, "attackTime"); + AssignDurationIfSet(effect.sidechain.holdTime, "holdTime"); + AssignDurationIfSet(effect.sidechain.releaseTime, "releaseTime"); + AssignFloatIfSet(effect.sidechain.ratio, "ratio"); + break; + case EffectType::TapeStop: + AssignDurationIfSet(effect.duration, "speed"); + break; + case EffectType::SwitchAudio: + AssignIntIfSet(effect.switchaudio.index, "index"); + break; + } + + return effect; +}; + +bool Beatmap::m_ProcessKShootMap(BinaryStream& input, bool metadataOnly) +{ + KShootMap kshootMap; + if (!kshootMap.Init(input, metadataOnly)) + return false; + + EffectTypeMap effectTypeMap; + EffectTypeMap filterTypeMap; + Map defaultEffectParams; + + // Set defaults + { + defaultEffectParams[EffectType::Bitcrush] = 4; + defaultEffectParams[EffectType::Gate] = 8; + defaultEffectParams[EffectType::Retrigger] = 8; + defaultEffectParams[EffectType::Phaser] = 2000; + defaultEffectParams[EffectType::Flanger] = 2000; + defaultEffectParams[EffectType::Wobble] = 12; + defaultEffectParams[EffectType::SideChain] = 8; + defaultEffectParams[EffectType::TapeStop] = 50; + } + + // Add all the custom effect types + for (auto it = kshootMap.fxDefines.begin(); it != kshootMap.fxDefines.end(); it++) + { + EffectType type = effectTypeMap.FindOrAddEffectType(it->first); + if (m_customAudioEffects.Contains(type)) + continue; + m_customAudioEffects.Add(type, ParseCustomEffect(it->second, m_switchablePaths)); + } + for (auto it = kshootMap.filterDefines.begin(); it != kshootMap.filterDefines.end(); it++) + { + EffectType type = filterTypeMap.FindOrAddEffectType(it->first); + if (m_customAudioFilters.Contains(type)) + continue; + m_customAudioFilters.Add(type, ParseCustomEffect(it->second, m_switchablePaths)); + } + + auto ParseFilterType = [&](const String& str) + { + EffectType type = EffectType::None; + if (str == "hpf1") + { + type = EffectType::HighPassFilter; + } + else if (str == "lpf1") + { + type = EffectType::LowPassFilter; + } + else if (str == "fx;bitc" || str == "bitc") + { + type = EffectType::Bitcrush; + } + else if (str == "peak") + { + type = EffectType::PeakingFilter; + } + else + { + const EffectType* foundType = filterTypeMap.FindEffectType(str); + if (foundType) + type = *foundType; + else + Logf("[KSH]Unknown filter type: %s", Logger::Severity::Warning, str); + } + return type; + }; + + // Process map settings + m_settings.previewOffset = 0; + m_settings.previewDuration = 0; + for (auto& s : kshootMap.settings) + { + if (s.first == "title") + m_settings.title = s.second; + else if (s.first == "artist") + m_settings.artist = s.second; + else if (s.first == "effect") + m_settings.effector = s.second; + else if (s.first == "illustrator") + m_settings.illustrator = s.second; + else if (s.first == "t") + m_settings.bpm = s.second; + else if (s.first == "jacket") + m_settings.jacketPath = s.second; + else if (s.first == "bg") + m_settings.backgroundPath = s.second; + else if (s.first == "layer") + m_settings.foregroundPath = s.second; + else if (s.first == "m") + { + if (s.second.find(';') != -1) + { + String audioFX, audioNoFX; + s.second.Split(";", &audioNoFX, &audioFX); + size_t splitMore = audioFX.find(';'); + if (splitMore != -1) + audioFX = audioFX.substr(0, splitMore); + m_settings.audioFX = audioFX; + m_settings.audioNoFX = audioNoFX; + } + else + { + m_settings.audioNoFX = s.second; + } + } + else if (s.first == "o") + { + m_settings.offset = atol(*s.second); + } + // TODO: Move initial laser effect settings to an event instead + else if (s.first == "filtertype") + { + m_settings.laserEffectType = ParseFilterType(s.second); + } + else if (s.first == "pfiltergain") + { + m_settings.laserEffectMix = (float)atol(*s.second) / 100.0f; + } + else if (s.first == "chokkakuvol") + { + m_settings.slamVolume = (float)atol(*s.second) / 100.0f; + } + // end TODO + else if (s.first == "level") + { + m_settings.level = atoi(*s.second); + } + else if (s.first == "difficulty") + { + m_settings.difficulty = 0; + if (s.second == "challenge") + { + m_settings.difficulty = 1; + } + else if (s.second == "extended") + { + m_settings.difficulty = 2; + } + else if (s.second == "infinite") + { + m_settings.difficulty = 3; + } + } + else if (s.first == "po") + { + m_settings.previewOffset = atoi(*s.second); + } + else if (s.first == "plength") + { + m_settings.previewDuration = atoi(*s.second); + } + else if (s.first == "total") + { + m_settings.total = atoi(*s.second); + } + else if (s.first == "mvol") + { + m_settings.musicVolume = (float)atoi(*s.second) / 100.0f; + } + else if (s.first == "to") + { + m_settings.speedBpm = atof(*s.second); + } + } + + const static int tickResolution = 240; + + const TimingPoint* currTimingPoint = nullptr; + + /// For more accurate tracking of ticks for each timing point + size_t refTimingPointInd = 0; + double refTimingPointTime = 0.0; + + Vector timingPointTicks = { 0 }; + + auto TickToMapTime = [&](uint32 tick) + { + if (tick < timingPointTicks[refTimingPointInd]) + { + refTimingPointInd = 0; + refTimingPointTime = static_cast(m_timingPoints[refTimingPointInd].time); + } + while (refTimingPointInd + 1 < timingPointTicks.size() && timingPointTicks[refTimingPointInd + 1] <= tick) + { + const MapTime timeDiff = timingPointTicks[refTimingPointInd + 1] - timingPointTicks[refTimingPointInd]; + refTimingPointTime += Math::MSFromTicks((double)timeDiff, m_timingPoints[refTimingPointInd].GetBPM(), static_cast(tickResolution)); + ++refTimingPointInd; + } + + const uint32 tickDiff = tick - timingPointTicks[refTimingPointInd]; + + double mapTime = refTimingPointTime; + mapTime += Math::MSFromTicks((double)tickDiff, m_timingPoints[refTimingPointInd].GetBPM(), static_cast(tickResolution)); + return Math::RoundToInt(mapTime); + }; + + { + TimingPoint firstTimingPoint; + firstTimingPoint.numerator = 4; + firstTimingPoint.denominator = 4; + + firstTimingPoint.time = atol(*kshootMap.settings["o"]); + refTimingPointTime = static_cast(firstTimingPoint.time); + + const double bpm = atof(*kshootMap.settings["t"]); + firstTimingPoint.beatDuration = 60000.0 / bpm; + + currTimingPoint = &(m_timingPoints.Add(std::move(firstTimingPoint))); + } + + // Add First Lane Toggle Point + { + LaneHideTogglePoint startLaneTogglePoint; + startLaneTogglePoint.time = 0; + startLaneTogglePoint.duration = 1; + m_laneTogglePoints.Add(std::move(startLaneTogglePoint)); + } + + // Stop here if we're only going for metadata + if (metadataOnly) + { + return true; + } + + // Button hold states + TempButtonState* buttonStates[6] = { nullptr }; + // Laser segment states + TempLaserState* laserStates[2] = { nullptr }; + + EffectType currentButtonEffectTypes[2] = { EffectType::None }; + // 2 per button + int16 currentButtonEffectParams[4] = { -1 }; + const uint32 maxEffectParamsPerButtons = 2; + float laserRanges[2] = { 1.0f, 1.0f }; + MapTime lastLaserPointTime[2] = { 0, 0 }; + + // Stops will be applied after the scroll speed graph is constructed. + // Tuple of (stopBegin, stopEnd, isOverlappingStop) + Vector> stops; + + MapTime lastMapTime = 0; + uint32 currentTick = 0; + + bool isManualTilt = false; + for (KShootMap::TickIterator it(kshootMap); it; ++it) + { + const KShootBlock& block = it.GetCurrentBlock(); + KShootTime time = it.GetTime(); + const KShootTick& tick = *it; + float fxSampleVolume[2] = { 1.0, 1.0 }; + bool useFxSample[2] = { false, false }; + uint8 fxSampleIndex[2] = { 0, 0 }; + MapTime mapTime = TickToMapTime(currentTick); + bool lastTick = &block == &kshootMap.blocks.back() && + &tick == &block.ticks.back(); + + // flag set when a new effect parameter is set and a new hold notes should be created + bool splitupHoldNotes[2] = { false, false }; + + uint32 tickSettingIndex = 0; + // Process settings + for (auto& p : tick.settings) + { + // Functions that adds a new timing point at current location if it's not yet there + auto AddTimingPoint = [&](double newDuration, uint32 newNum, uint32 newDenom, int8 tickrateOffset) + { + if (currTimingPoint->time != mapTime) + { + TimingPoint newTimingPoint = TimingPoint(*currTimingPoint); + newTimingPoint.time = mapTime; + + currTimingPoint = &(m_timingPoints.Add(std::move(newTimingPoint))); + timingPointTicks.Add(currentTick); + } + + TimingPoint& lastTimingPoint = *m_timingPoints.rbegin(); + + lastTimingPoint.numerator = newNum; + lastTimingPoint.denominator = newDenom; + lastTimingPoint.beatDuration = newDuration; + lastTimingPoint.tickrateOffset = tickrateOffset; + }; + + // Parser the effect and parameters of an FX button (1.60) + auto ParseFXAndParameters = [&](String in, int16* paramsOut) + { + // Clear parameters + memset(paramsOut, -1, sizeof(uint16) * maxEffectParamsPerButtons); + + String effectName = in; + size_t paramSplit = in.find_first_of(';'); + if (paramSplit != -1) + effectName = effectName.substr(0, paramSplit); + effectName.Trim(); + + // Clear effect instead? + if (effectName.empty()) + return EffectType::None; + + const EffectType* type = effectTypeMap.FindEffectType(effectName); + if (type == nullptr) + { + Logf("Invalid custom effect name in ksh map: %s", Logger::Severity::Warning, effectName); + return EffectType::None; + } + + if (paramSplit != -1) + { + String paramA, paramB; + String effectParams = p.second.substr(paramSplit + 1); + if (effectParams.Split(";", ¶mA, ¶mB)) + { + paramsOut[0] = atoi(*paramA); + paramsOut[1] = atoi(*paramB); + } + else + paramsOut[0] = atoi(*effectParams); + } + else // set default params + { + if (*type < EffectType::UserDefined0) + { + switch (*type) + { + case EffectType::Flanger: + paramsOut[0] = 45; + paramsOut[1] = 15; + break; + + default: + break; + } + } + else + { + m_customAudioEffects.at(*type).SetDefaultEffectParams(paramsOut); + } + } + + return *type; + }; + + if (p.first == "beat") + { + String n, d; + if (!p.second.Split("/", &n, &d)) + assert(false); + + uint32 num = atol(*n); + uint32 denom = atol(*d); + + AddTimingPoint(currTimingPoint->beatDuration, num, denom, currTimingPoint->tickrateOffset); + } + else if (p.first == "t") + { + const double bpm = atof(*p.second); + AddTimingPoint(60000.0 / bpm, currTimingPoint->numerator, currTimingPoint->denominator, currTimingPoint->tickrateOffset); + } + else if (p.first == "tickrate_offset") + { + int8 value = atoi(*p.second); + AddTimingPoint(currTimingPoint->beatDuration, currTimingPoint->numerator, currTimingPoint->denominator, value); + } + else if (p.first == "laserrange_l") + { + laserRanges[0] = 2.0f; + } + else if (p.first == "laserrange_r") + { + laserRanges[1] = 2.0f; + } + else if (p.first == "fx-l") // KSH 1.6 + { + currentButtonEffectTypes[0] = ParseFXAndParameters(p.second, currentButtonEffectParams); + splitupHoldNotes[0] = true; + } + else if (p.first == "fx-r") // KSH 1.6 + { + currentButtonEffectTypes[1] = ParseFXAndParameters(p.second, currentButtonEffectParams + maxEffectParamsPerButtons); + splitupHoldNotes[1] = true; + } + else if (p.first == "fx-l_param1") + { + currentButtonEffectParams[0] = atoi(*p.second); + splitupHoldNotes[0] = true; + } + else if (p.first == "fx-r_param1") + { + currentButtonEffectParams[maxEffectParamsPerButtons] = atoi(*p.second); + splitupHoldNotes[1] = true; + } + else if (p.first == "filtertype") + { + // Inser filter type change event + EventObjectState* evt = new EventObjectState(); + evt->interTickIndex = tickSettingIndex; + evt->time = mapTime; + evt->key = EventKey::LaserEffectType; + evt->data.effectVal = ParseFilterType(p.second); + m_objectStates.emplace_back(std::unique_ptr(*evt)); + } + else if (p.first == "pfiltergain") + { + // Inser filter type change event + float gain = (float)atol(*p.second) / 100.0f; + EventObjectState* evt = new EventObjectState(); + evt->interTickIndex = tickSettingIndex; + evt->time = mapTime; + evt->key = EventKey::LaserEffectMix; + evt->data.floatVal = gain; + m_objectStates.emplace_back(std::unique_ptr(*evt)); + } + else if (p.first == "chokkakuvol") + { + float vol = (float)atol(*p.second) / 100.0f; + EventObjectState* evt = new EventObjectState(); + evt->interTickIndex = tickSettingIndex; + evt->time = mapTime; + evt->key = EventKey::LaserEffectMix; + evt->data.floatVal = vol; + m_objectStates.emplace_back(std::unique_ptr(*evt)); + } + else if (p.first == "zoom_bottom") + { + const double value = atol(p.second.data()) / 100.0; + m_effects.InsertGraphValue(EffectTimeline::GraphType::ZOOM_BOTTOM, mapTime, value); + } + else if (p.first == "zoom_top") + { + const double value = atol(p.second.data()) / 100.0; + m_effects.InsertGraphValue(EffectTimeline::GraphType::ZOOM_TOP, mapTime, value); + } + else if (p.first == "zoom_side") + { + const double value = atol(p.second.data()) / 100.0; + m_effects.InsertGraphValue(EffectTimeline::GraphType::SHIFT_X, mapTime, value); + } + /* OLD USC MANUAL ROLL, KEPT JUST IN CASE + else if (p.first == "roll") + { + ZoomControlPoint* point = new ZoomControlPoint(); + point->time = mapTime; + point->index = 3; + point->zoom = (float)atol(*p.second) / 360.0f; + m_zoomControlPoints.Add(point); + CHECK_FIRST; + } + */ + else if (p.first == "lane_toggle") + { + LaneHideTogglePoint point; + point.time = mapTime; + point.duration = atol(*p.second); + m_laneTogglePoints.Add(std::move(point)); + } + else if (p.first == "center_split") + { + const double value = atol(*p.second) / 100.0; + m_centerSplit.Insert(mapTime, value); + } + else if (p.first == "tilt") + { + EventObjectState* evt = new EventObjectState(); + evt->time = mapTime; + evt->interTickIndex = tickSettingIndex; + evt->key = EventKey::TrackRollBehaviour; + evt->data.rollVal = TrackRollBehaviour::Zero; + + String v = p.second; + size_t f = v.find("keep_"); + if (f != -1) + { + evt->data.rollVal = TrackRollBehaviour::Keep; + v = v.substr(f + 5); + } + + if (v == "normal") + evt->data.rollVal = evt->data.rollVal | TrackRollBehaviour::Normal; + else if (v == "bigger") + evt->data.rollVal = evt->data.rollVal | TrackRollBehaviour::Bigger; + else if (v == "biggest") + evt->data.rollVal = evt->data.rollVal | TrackRollBehaviour::Biggest; + else if (v == "zero") + evt->data.rollVal = evt->data.rollVal | TrackRollBehaviour::Zero; + else + { + evt->data.rollVal = TrackRollBehaviour::Manual; + + const double rotation = atof(p.second.data()) * -(10.0 / 360.0); + m_effects.InsertGraphValue(EffectTimeline::GraphType::ROTATION_Z, mapTime, rotation); + + isManualTilt = true; + goto after_manual_check; + } + + if (isManualTilt) + { + m_effects.GetGraph(EffectTimeline::GraphType::ROTATION_Z).Extend(mapTime); + } + + after_manual_check: + m_objectStates.emplace_back(std::unique_ptr(*evt)); + } + else if (p.first == "fx-r_se") + { + String filename, vol; + int fxi = 1; + useFxSample[fxi] = true; + if (p.second.Split(";", &filename, &vol)) + { + fxSampleVolume[fxi] = (float)atoi(*vol) / 100.0f; + } + else + { + filename = p.second; + } + + auto it = std::find(m_samplePaths.begin(), m_samplePaths.end(), filename); + if (it == m_samplePaths.end()) + { + fxSampleIndex[fxi] = static_cast(m_samplePaths.size()); + m_samplePaths.Add(filename); + } + else + { + fxSampleIndex[fxi] = static_cast(std::distance(m_samplePaths.begin(), it)); + } + } + else if (p.first == "fx-l_se") + { + String filename, vol; + int fxi = 0; + useFxSample[fxi] = true; + if (p.second.Split(";", &filename, &vol)) + { + fxSampleVolume[fxi] = (float)atoi(*vol) / 100.0f; + } + else + { + filename = p.second; + } + + auto it = std::find(m_samplePaths.begin(), m_samplePaths.end(), filename); + if (it == m_samplePaths.end()) + { + fxSampleIndex[fxi] = static_cast(m_samplePaths.size()); + m_samplePaths.Add(filename); + } + else + { + fxSampleIndex[fxi] = std::distance(m_samplePaths.begin(), it); + } + } + else if (p.first == "stop") + { + // Stops will be applied after the scroll speed graph is constructed. + const MapTime stopDuration = Math::RoundToInt((atol(*p.second) / 192.0f) * (currTimingPoint->beatDuration) * 4); + bool isOverlappingStop = false; + + if (!stops.empty() && mapTime < std::get<1>(*stops.rbegin())) + { + isOverlappingStop = true; + std::get<2>(*stops.rbegin()) = true; + } + + stops.Add(std::make_tuple(mapTime, mapTime + stopDuration, isOverlappingStop)); + } + else if (p.first == "scroll_speed") + { + LineGraph& scrollSpeedGraph = m_effects.GetGraph(EffectTimeline::GraphType::SCROLL_SPEED); + scrollSpeedGraph.Insert(mapTime, atol(p.second.data()) / 100.0); + } + else + { + Logf("[KSH]Unkown map parameter at %d:%d: %s", Logger::Severity::Warning, it.GetTime().block, it.GetTime().tick, p.first); + } + tickSettingIndex++; + } + + // Set button states + for (uint32 i = 0; i < 6; i++) + { + char c = i < 4 ? tick.buttons[i] : tick.fx[i - 4]; + TempButtonState*& state = buttonStates[i]; + HoldObjectState* lastHoldObject = nullptr; + + auto IsHoldState = [&]() + { + return state && state->numTicks > 0 && state->fineSnap; + }; + auto CreateButton = [&]() + { + if (IsHoldState()) + { + HoldObjectState* obj = lastHoldObject = new HoldObjectState(); + obj->time = TickToMapTime(state->startTick); + obj->index = i; + obj->duration = TickToMapTime(currentTick) - obj->time; + obj->effectType = state->effectType; + if (state->lastHoldObject) + state->lastHoldObject->next = obj; + obj->prev = state->lastHoldObject; + memcpy(obj->effectParams, state->effectParams, sizeof(state->effectParams)); + m_objectStates.emplace_back(std::unique_ptr(*obj)); + ; + } + else + { + ButtonObjectState* obj = new ButtonObjectState(); + + obj->time = TickToMapTime(state->startTick); + obj->index = i; + obj->hasSample = state->usingSample; + obj->sampleIndex = state->sampleIndex; + obj->sampleVolume = state->sampleVolume; + m_objectStates.emplace_back(std::unique_ptr(*obj)); + } + + // Reset + delete state; + state = nullptr; + }; + + // Split up multiple hold notes + if (i > 3 && IsHoldState() && splitupHoldNotes[i - 4]) + { + CreateButton(); + } + + if (c == '0') + { + // Terminate hold button + if (state) + { + CreateButton(); + } + + if (i >= 4) + { + // Unset effect parameters + currentButtonEffectParams[i - 4] = -1; + } + } + else if (!state) + { + // Create new hold state + state = new TempButtonState(currentTick); + uint32 div = (uint32)block.ticks.size(); + + if (lastHoldObject) + state->lastHoldObject = lastHoldObject; + + if (i < 4) + { + // Normal '1' notes are always individual + state->fineSnap = c != '1'; + } + else + { + // FX object '2' is always individual + state->fineSnap = c != '2'; + + // Set effect + if (c == 'B') + { + state->effectType = EffectType::Bitcrush; + if (currentButtonEffectParams[i - 4] != -1) + state->effectParams[0] = currentButtonEffectParams[i - 4]; + else + state->effectParams[0] = 5; + } + else if (c >= 'G' && c <= 'L') // Gate 4/8/16/32/12/24 + { + state->effectType = EffectType::Gate; + int16 paramMap[] = { + 4, 8, 16, 32, 12, 24 }; + state->effectParams[0] = paramMap[c - 'G']; + } + else if (c >= 'S' && c <= 'W') // Retrigger 8/16/32/12/24 + { + state->effectType = EffectType::Retrigger; + int16 paramMap[] = { + 8, 16, 32, 12, 24 }; + state->effectParams[0] = paramMap[c - 'S']; + } + else if (c == 'Q') + { + state->effectType = EffectType::Phaser; + } + else if (c == 'F') + { + state->effectType = EffectType::Flanger; + state->effectParams[0] = 5000; + } + else if (c == 'X') + { + state->effectType = EffectType::Wobble; + state->effectParams[0] = 12; + } + else if (c == 'D') + { + state->effectType = EffectType::SideChain; + } + else if (c == 'A') + { + state->effectType = EffectType::TapeStop; + if (currentButtonEffectParams[i - 4] != -1) + memcpy(state->effectParams, currentButtonEffectParams + (i - 4) * maxEffectParamsPerButtons, + sizeof(state->effectParams)); + else + state->effectParams[0] = 50; + } + else if (c == '2') + { + state->sampleIndex = fxSampleIndex[i - 4]; + state->usingSample = useFxSample[i - 4]; + state->sampleVolume = fxSampleVolume[i - 4]; + } + else + { + // Use settings method of setting effects+params (1.60) + state->effectType = currentButtonEffectTypes[i - 4]; + if (currentButtonEffectParams[(i - 4) * maxEffectParamsPerButtons] != -1) + memcpy(state->effectParams, currentButtonEffectParams + (i - 4) * maxEffectParamsPerButtons, + sizeof(state->effectParams)); + else + { + state->effectParams[0] = defaultEffectParams[state->effectType]; + state->effectParams[1] = 0; + } + } + } + } + else + { + // For buttons not using the 1/32 grid + if (!state->fineSnap) + { + CreateButton(); + + // Create new hold state + state = new TempButtonState(currentTick); + uint32 div = (uint32)block.ticks.size(); + + if (i < 4) + { + // Normal '1' notes are always individual + state->fineSnap = c != '1'; + } + else + { + // Hold are always on a high enough snap to make suere they are seperate when needed + if (c == '2') + { + state->fineSnap = false; + state->sampleIndex = fxSampleIndex[i - 4]; + state->usingSample = useFxSample[i - 4]; + state->sampleVolume = fxSampleVolume[i - 4]; + } + else + { + state->fineSnap = true; + state->effectType = currentButtonEffectTypes[i - 4]; + memcpy(state->effectParams, currentButtonEffectParams + (i - 4) * maxEffectParamsPerButtons, + sizeof(state->effectParams)); + } + } + } + else + { + // Update current hold state + state->numTicks++; + } + } + + // Terminate last item + if (lastTick && state) + CreateButton(); + } + + // Set laser states + for (uint32 i = 0; i < 2; i++) + { + TempLaserState*& state = laserStates[i]; + char c = tick.laser[i]; + + // Function that creates a new segment out of the current state + auto CreateLaserSegment = [&](float endPos) + { + // Process existing segment + // assert(state->numTicks > 0); + + LaserObjectState* obj = new LaserObjectState(); + + obj->time = TickToMapTime(state->startTick); + obj->tick = state->startTick; + obj->duration = TickToMapTime(currentTick) - obj->time; + obj->index = i; + obj->points[0] = state->startPosition; + obj->points[1] = endPos; + uint32 tickDuration = currentTick - state->absoluteStartTick; + + if (laserRanges[i] > 1.0f) + { + obj->flags |= LaserObjectState::flag_Extended; + } + uint32 laserSlamThreshold = tickResolution / 8; + bool lastSlam = (state->last && (state->last->flags & LaserObjectState::flag_Instant) != 0); // Deal with super fast repeat slams + + if (tickDuration <= laserSlamThreshold && (obj->points[1] != obj->points[0])) + { + obj->flags |= LaserObjectState::flag_Instant; + obj->time = TickToMapTime(state->absoluteStartTick); + obj->tick = state->absoluteStartTick; + if (state->spinType != 0) + { + obj->spin.duration = state->spinDuration; + obj->spin.amplitude = state->spinBounceAmplitude; + obj->spin.frequency = state->spinBounceFrequency; + obj->spin.decay = state->spinBounceDecay; + + if (state->spinIsBounce) + obj->spin.type = SpinStruct::SpinType::Bounce; + else + { + switch (state->spinType) + { + case '(': + case ')': + obj->spin.type = SpinStruct::SpinType::Full; + break; + case '<': + case '>': + obj->spin.type = SpinStruct::SpinType::Quarter; + break; + default: + break; + } + } + + switch (state->spinType) + { + case '<': + case '(': + obj->spin.direction = -1.0f; + break; + case ')': + case '>': + obj->spin.direction = 1.0f; + break; + default: + break; + } + } + } + + // Link segments together + if (state->last) + { + // Always fixup duration so they are connected by duration as well + obj->prev = state->last; + MapTime actualPrevDuration = obj->time - obj->prev->time; + if (obj->prev->duration != actualPrevDuration) + { + obj->prev->duration = actualPrevDuration; + } + obj->prev->next = obj; + } + + if ((obj->flags & LaserObjectState::flag_Instant) != 0 && lastSlam) // add short straight segment between the slams + { + auto midobj = new LaserObjectState(); + midobj->flags = obj->prev->flags & ~LaserObjectState::flag_Instant; + midobj->points[0] = obj->points[0]; + midobj->points[1] = obj->points[0]; + midobj->time = obj->prev->time; + midobj->duration = lastLaserPointTime[i] - midobj->time; + midobj->index = obj->index; + + obj->time = lastLaserPointTime[i]; + + midobj->prev = obj->prev; + obj->prev = midobj; + midobj->next = obj; + midobj->prev->next = midobj; + + m_objectStates.emplace_back(std::unique_ptr(*midobj)); + } + + // Add to list of objects + + assert(obj->GetRoot() != nullptr); + + m_objectStates.emplace_back(std::unique_ptr(*obj)); + + return obj; + }; + + if (c == '-') + { + // Terminate laser + if (state) + { + // Reset state + delete state; + state = nullptr; + + // Reset range extension + laserRanges[i] = 1.0f; + } + } + else if (c == ':') + { + // Update current laser state + if (state) + { + state->numTicks++; + } + } + else + { + float pos = kshootMap.TranslateLaserChar(c); + LaserObjectState* last = nullptr; + if (state) + { + last = CreateLaserSegment(pos); + + // Reset state + delete state; + state = nullptr; + } + + uint32 startTick = currentTick; + if (last && (last->flags & LaserObjectState::flag_Instant) != 0) + { + // Move offset to be the same as last segment, as in ksh maps there is a 1 tick delay after laser slams + startTick = last->tick; + } + state = new TempLaserState(startTick, currentTick, 0); + state->last = last; // Link together + state->startPosition = pos; + + //@[Type][Speed] = spin + // Types + //) or ( = full spin + //> or < = quarter spin + // Speed is number of 192nd notes + if (!tick.add.empty() && (tick.add[0] == '@' || tick.add[0] == 'S')) + { + state->spinIsBounce = tick.add[0] == 'S'; + state->spinType = tick.add[1]; + + String add = tick.add.substr(2); + if (state->spinIsBounce) + { + String duration, amplitude, frequency, decay; + + add.Split(";", &duration, &litude); + amplitude.Split(";", &litude, &frequency); + frequency.Split(";", &frequency, &decay); + + state->spinDuration = std::stoi(duration); + state->spinBounceAmplitude = std::stoi(amplitude); + state->spinBounceFrequency = std::stoi(frequency); + state->spinBounceDecay = std::stoi(decay); + } + else + { + state->spinDuration = std::stoi(add); + if (state->spinType == '(' || state->spinType == ')') + state->spinDuration = state->spinDuration; + } + } + + lastLaserPointTime[i] = mapTime; + } + } + + lastMapTime = mapTime; + currentTick += static_cast((tickResolution * 4 * currTimingPoint->numerator / currTimingPoint->denominator) / block.ticks.size()); + } + + // Apply stops + for (const auto& stop : stops) + { + const MapTime stopBegin = std::get<0>(stop); + const MapTime stopEnd = std::get<1>(stop); + const bool isOverlapping = std::get<2>(stop); + + LineGraph& scrollSpeedGraph = m_effects.GetGraph(EffectTimeline::GraphType::SCROLL_SPEED); + + // In older versions of USC there was a bug where overlapping stop regions made notes scrolling backwards. + // In other words, stops weren't actually setting the scroll speed to 0, but instead decreased the speed by 1. + // This bug was utilized as gimmicks for several charts, so for backwards compatibility this behavior is reimplemented when stops are overlapping. + // For individual stops, scroll speed will actually set to 0 to make those behave nicely with manual scroll speed modifiers. + + if (isOverlapping) + { + scrollSpeedGraph.RangeAdd(stopBegin, stopEnd, -1.0); + } + else + { + scrollSpeedGraph.RangeSet(stopBegin, stopEnd, 0.0); + } + } + + // Add chart end event + EventObjectState* evt = new EventObjectState(); + evt->time = lastMapTime + 2000; + evt->key = EventKey::ChartEnd; + m_objectStates.emplace_back(std::unique_ptr(*evt)); + + // Re-sort collection to fix some inconsistencies caused by corrections after laser slams + ObjectState::SortArray(m_objectStates); + + return true; +} diff --git a/Beatmap/src/BeatmapObjects.cpp b/Beatmap/src/BeatmapObjects.cpp index 4fed918af..a5249dc33 100644 --- a/Beatmap/src/BeatmapObjects.cpp +++ b/Beatmap/src/BeatmapObjects.cpp @@ -4,187 +4,190 @@ // Object array sorting void TObjectState::SortArray(std::vector>& arr) { - std::sort(arr.begin(), arr.end(), [](const auto& l, const auto& r) - { - if(l->time == r->time) - { - // sort events on the same tick by their index - if (l->type == ObjectType::Event && r->type == ObjectType::Event) - return ((EventObjectState*) l.get())->interTickIndex < ((EventObjectState*) r.get())->interTickIndex; - - // Sort laser slams to come first - const bool ls = l->type == ObjectType::Laser && (((LaserObjectState*)l.get())->flags & LaserObjectState::flag_Instant); - const bool rs = r->type == ObjectType::Laser && (((LaserObjectState*)r.get())->flags & LaserObjectState::flag_Instant); - - return ls > rs; - } - - return l->time < r->time; - }); + std::sort(arr.begin(), arr.end(), [](const auto& l, const auto& r) + { + if (l->time == r->time) + { + // sort events on the same tick by their index + if (l->type == ObjectType::Event && r->type == ObjectType::Event) + return ((EventObjectState*)l.get())->interTickIndex < ((EventObjectState*)r.get())->interTickIndex; + + // Sort laser slams to come first + const bool ls = l->type == ObjectType::Laser && (((LaserObjectState*)l.get())->flags & LaserObjectState::flag_Instant); + const bool rs = r->type == ObjectType::Laser && (((LaserObjectState*)r.get())->flags & LaserObjectState::flag_Instant); + + return ls > rs; + } + + return l->time < r->time; }); } TObjectState* ObjectTypeData_Hold::GetRoot() { - TObjectState* ptr = (TObjectState*)this; - while(ptr->prev) - ptr = ptr->prev; - return ptr; + TObjectState* ptr = (TObjectState *)this; + while (ptr->prev) + ptr = ptr->prev; + return ptr; } TObjectState* ObjectTypeData_Laser::GetRoot() { - TObjectState* ptr = (TObjectState*)this; - while(ptr->prev) - ptr = ptr->prev; - return ptr; + TObjectState* ptr = (TObjectState *)this; + while (ptr->prev) + ptr = ptr->prev; + return ptr; } TObjectState* ObjectTypeData_Laser::GetTail() { - TObjectState* ptr = (TObjectState*)this; - while(ptr->next) - ptr = ptr->next; - return ptr; + TObjectState* ptr = (TObjectState *)this; + while (ptr->next) + ptr = ptr->next; + return ptr; } float ObjectTypeData_Laser::GetDirection() const { - return Math::Sign(points[1] - points[0]); + return Math::Sign(points[1] - points[0]); } float ObjectTypeData_Laser::SamplePosition(MapTime time) const { - const LaserObjectState* state = (LaserObjectState*)this; - while(state->next && (state->time + state->duration) < time) - { - state = state->next; - } - float f = Math::Clamp((float)(time - state->time) / (float)Math::Max(1, state->duration), 0.0f, 1.0f); - return (state->points[1] - state->points[0]) * f + state->points[0]; + const LaserObjectState* state = (LaserObjectState*)this; + while (state->next && (state->time + state->duration) < time) + { + state = state->next; + } + float f = Math::Clamp((float)(time - state->time) / (float)Math::Max(1, state->duration), 0.0f, 1.0f); + return (state->points[1] - state->points[0]) * f + state->points[0]; } float ObjectTypeData_Laser::ConvertToNormalRange(float inputRange) { - return (inputRange + 0.5f) * 0.5f; + return (inputRange + 0.5f) * 0.5f; } float ObjectTypeData_Laser::ConvertToExtendedRange(float inputRange) { - return inputRange * 2.0f - 0.5f; + return inputRange * 2.0f - 0.5f; } MapTime ObjectTypeData_Laser::GetTimeToDirectionChange(MapTime currentTime, MapTime maxDelta) { - const LaserObjectState* state = (LaserObjectState*)this; - MapTime result = -1; - LaserObjectState* n = state->next; - float currentDir = state->GetDirection(); - - //upcoming changes - while (n) { - if (n->time - currentTime > maxDelta) - break; - if (n->GetDirection() != currentDir) { - result = Math::Max(0, n->time - currentTime); - break; - } - n = n->next; - } - - //previous changes - n = state->prev; - while (n) { - MapTime changeTime = n->time + n->duration; - if (currentTime - changeTime > maxDelta) - break; - if (n->GetDirection() != currentDir) { - if (result == -1) - result = Math::Max(currentTime - changeTime, 0); - else - result = Math::Min(Math::Max(currentTime - changeTime, 0), result); - - break; - } - n = n->prev; - } - return result; + const LaserObjectState* state = (LaserObjectState*)this; + MapTime result = -1; + LaserObjectState* n = state->next; + float currentDir = state->GetDirection(); + + // upcoming changes + while (n) + { + if (n->time - currentTime > maxDelta) + break; + if (n->GetDirection() != currentDir) + { + result = Math::Max(0, n->time - currentTime); + break; + } + n = n->next; + } + + // previous changes + n = state->prev; + while (n) + { + MapTime changeTime = n->time + n->duration; + if (currentTime - changeTime > maxDelta) + break; + if (n->GetDirection() != currentDir) + { + if (result == -1) + result = Math::Max(currentTime - changeTime, 0); + else + result = Math::Min(Math::Max(currentTime - changeTime, 0), result); + + break; + } + n = n->prev; + } + return result; } // Enum OR, AND TrackRollBehaviour operator|(const TrackRollBehaviour& l, const TrackRollBehaviour& r) { - return (TrackRollBehaviour)((uint8)l | (uint8)r); + return (TrackRollBehaviour)((uint8)l | (uint8)r); } TrackRollBehaviour operator&(const TrackRollBehaviour& l, const TrackRollBehaviour& r) { - return (TrackRollBehaviour)((uint8)l & (uint8)r); + return (TrackRollBehaviour)((uint8)l & (uint8)r); } bool MultiObjectState::StaticSerialize(BinaryStream& stream, MultiObjectState*& obj) { - uint8 type = 0; - if (stream.IsReading()) - { - // Read type and create appropriate object - stream << type; - switch ((ObjectType)type) - { - case ObjectType::Single: - obj = (MultiObjectState*)new ButtonObjectState(); - break; - case ObjectType::Hold: - obj = (MultiObjectState*)new HoldObjectState(); - break; - case ObjectType::Laser: - obj = (MultiObjectState*)new LaserObjectState(); - break; - case ObjectType::Event: - obj = (MultiObjectState*)new EventObjectState(); - break; - } - } - else - { - // Write type - type = (uint8)obj->type; - stream << type; - } - - // Pointer is always initialized here, serialize data - stream << obj->time; // Time always set - switch (obj->type) - { - case ObjectType::Single: - stream << obj->button.index; - break; - case ObjectType::Hold: - stream << obj->hold.index; - stream << obj->hold.duration; - stream << (uint16&)obj->hold.effectType; - stream << (int16&)obj->hold.effectParams[0]; - stream << (int16&)obj->hold.effectParams[1]; - break; - case ObjectType::Laser: - stream << obj->laser.index; - stream << obj->laser.duration; - stream << obj->laser.points[0]; - stream << obj->laser.points[1]; - stream << obj->laser.flags; - break; - case ObjectType::Event: - stream << (uint8&)obj->event.key; - stream << *&obj->event.data; - break; - } - - return true; + uint8 type = 0; + if (stream.IsReading()) + { + // Read type and create appropriate object + stream << type; + switch ((ObjectType)type) + { + case ObjectType::Single: + obj = (MultiObjectState*)new ButtonObjectState(); + break; + case ObjectType::Hold: + obj = (MultiObjectState*)new HoldObjectState(); + break; + case ObjectType::Laser: + obj = (MultiObjectState*)new LaserObjectState(); + break; + case ObjectType::Event: + obj = (MultiObjectState*)new EventObjectState(); + break; + } + } + else + { + // Write type + type = (uint8)obj->type; + stream << type; + } + + // Pointer is always initialized here, serialize data + stream << obj->time; // Time always set + switch (obj->type) + { + case ObjectType::Single: + stream << obj->button.index; + break; + case ObjectType::Hold: + stream << obj->hold.index; + stream << obj->hold.duration; + stream << (uint16&)obj->hold.effectType; + stream << (int16&)obj->hold.effectParams[0]; + stream << (int16&)obj->hold.effectParams[1]; + break; + case ObjectType::Laser: + stream << obj->laser.index; + stream << obj->laser.duration; + stream << obj->laser.points[0]; + stream << obj->laser.points[1]; + stream << obj->laser.flags; + break; + case ObjectType::Event: + stream << (uint8&)obj->event.key; + stream << *&obj->event.data; + break; + } + + return true; } bool TimingPoint::StaticSerialize(BinaryStream& stream, TimingPoint*& out) { - if (stream.IsReading()) - out = new TimingPoint(); + if (stream.IsReading()) + out = new TimingPoint(); - stream << out->time; - stream << out->beatDuration; - stream << out->numerator; + stream << out->time; + stream << out->beatDuration; + stream << out->numerator; - return true; -} \ No newline at end of file + return true; +} diff --git a/Beatmap/src/BeatmapPlayback.cpp b/Beatmap/src/BeatmapPlayback.cpp index ffd1289e0..b379c8041 100644 --- a/Beatmap/src/BeatmapPlayback.cpp +++ b/Beatmap/src/BeatmapPlayback.cpp @@ -7,396 +7,405 @@ BeatmapPlayback::BeatmapPlayback(const Beatmap& beatmap) : m_beatmap(&beatmap) bool BeatmapPlayback::Reset(MapTime initTime, MapTime start) { - m_effectObjects.clear(); + m_effectObjects.clear(); - if (!m_beatmap || !m_beatmap->HasObjectState()) - { - return false; - } + if (!m_beatmap || !m_beatmap->HasObjectState()) + { + return false; + } - Logf("Resetting BeatmapPlayback, InitTime = %d, Start = %d", Logger::Severity::Debug, initTime, start); - m_playbackTime = initTime; + Logf("Resetting BeatmapPlayback, InitTime = %d, Start = %d", Logger::Severity::Debug, initTime, start); + m_playbackTime = initTime; - // Ensure that nothing could go wrong when the start is 0 - if (start <= 0) start = std::numeric_limits::min(); - m_playRange = { start, start }; + // Ensure that nothing could go wrong when the start is 0 + if (start <= 0) + start = std::numeric_limits::min(); + m_playRange = { start, start }; - m_currObject = m_beatmap->GetFirstObjectState(); + m_currObject = m_beatmap->GetFirstObjectState(); - m_currLaserObject = m_currObject; - m_currAlertObject = m_currObject; + m_currLaserObject = m_currObject; + m_currAlertObject = m_currObject; - m_currentTiming = m_beatmap->GetFirstTimingPoint(); - m_currentLaneTogglePoint = m_beatmap->GetFirstLaneTogglePoint(); + m_currentTiming = m_beatmap->GetFirstTimingPoint(); + m_currentLaneTogglePoint = m_beatmap->GetFirstLaneTogglePoint(); - m_currentTrackRollBehaviour = TrackRollBehaviour::Normal; - m_lastTrackRollBehaviourChange = 0; + m_currentTrackRollBehaviour = TrackRollBehaviour::Normal; + m_lastTrackRollBehaviourChange = 0; - m_objectsByTime.clear(); - m_objectsByLeaveTime.clear(); + m_objectsByTime.clear(); + m_objectsByLeaveTime.clear(); - m_barTime = 0; - m_beatTime = 0; - m_initialEffectStateSent = false; + m_barTime = 0; + m_beatTime = 0; + m_initialEffectStateSent = false; - return true; + return true; } void BeatmapPlayback::Update(MapTime newTime) { - MapTime delta = newTime - m_playbackTime; - - if (m_isCalibration) { - // Count bars - int32 beatID = 0; - uint32 nBeats = CountBeats(m_playbackTime - delta, delta, beatID); - const TimingPoint& tp = GetCurrentTimingPoint(); - double effectiveTime = ((double)newTime - tp.time); // Time with offset applied - m_barTime = (float)fmod(effectiveTime / (tp.beatDuration * tp.numerator), 1.0); - m_beatTime = (float)fmod(effectiveTime / tp.beatDuration, 1.0); - - // Set new time - m_playbackTime = newTime; - return; - } - - if (newTime < m_playbackTime) - { - // Don't allow backtracking - //Logf("New time was before last time %ull -> %ull", Logger::Warning, m_playbackTime, newTime); - return; - } - - // Fire initial effect changes (only once) - if (!m_initialEffectStateSent) - { - const BeatmapSettings& settings = m_beatmap->GetMapSettings(); - OnEventChanged.Call(EventKey::LaserEffectMix, settings.laserEffectMix); - OnEventChanged.Call(EventKey::LaserEffectType, settings.laserEffectType); - OnEventChanged.Call(EventKey::SlamVolume, settings.slamVolume); - m_initialEffectStateSent = true; - } - - // Count bars - int32 beatID = 0; - uint32 nBeats = CountBeats(m_playbackTime - delta, delta, beatID); - const TimingPoint& tp = GetCurrentTimingPoint(); - double effectiveTime = ((double)newTime - tp.time); // Time with offset applied - m_barTime = (float)fmod(effectiveTime / (tp.beatDuration * tp.numerator), 1.0); - m_beatTime = (float)fmod(effectiveTime / tp.beatDuration, 1.0); - - // Set new time - m_playbackTime = newTime; - - // Advance timing - Beatmap::TimingPointsIterator timingEnd = m_SelectTimingPoint(m_playbackTime); - if (timingEnd != m_currentTiming) - { - m_currentTiming = timingEnd; - /// TODO: Investigate why this causes score to be too high - //hittableLaserEnter = (*m_currentTiming)->beatDuration * 4.0; - //alertLaserThreshold = (*m_currentTiming)->beatDuration * 6.0; - OnTimingPointChanged.Call(m_currentTiming); - } - - // Advance lane toggle - Beatmap::LaneTogglePointsIterator laneToggleEnd = m_SelectLaneTogglePoint(m_playbackTime); - if (laneToggleEnd != m_currentLaneTogglePoint) - { - m_currentLaneTogglePoint = laneToggleEnd; - OnLaneToggleChanged.Call(m_currentLaneTogglePoint); - } - - // Advance objects - Beatmap::ObjectsIterator objEnd = m_SelectHitObject(m_playbackTime + hittableObjectEnter); - if (objEnd != m_currObject) - { - for (auto it = m_currObject; it < objEnd; it++) - { - MultiObjectState* obj = *(*it).get(); - if (obj->type == ObjectType::Laser) continue; - - if (!m_playRange.Includes(obj->time)) continue; - if (obj->type == ObjectType::Hold && !m_playRange.Includes(obj->time + obj->hold.duration, true)) continue; - - MapTime duration = 0; - if (obj->type == ObjectType::Hold) - { - duration = obj->hold.duration; - } - else if (obj->type == ObjectType::Event) - { - // Tiny offset to make sure events are triggered before they are needed - duration = -2; - } - - m_objectsByTime.Add(obj->time, (*it).get()); - m_objectsByLeaveTime.Add(obj->time + duration + hittableObjectLeave, (*it).get()); - - OnObjectEntered.Call((*it).get()); - } - - m_currObject = objEnd; - } - - - // Advance lasers - objEnd = m_SelectHitObject(m_playbackTime + hittableLaserEnter); - if (objEnd != m_currLaserObject) - { - for (auto it = m_currLaserObject; it < objEnd; it++) - { - MultiObjectState* obj = *(*it).get(); - if (obj->type != ObjectType::Laser) continue; - - if (!m_playRange.Includes(obj->time)) continue; - if (!m_playRange.Includes(obj->time + obj->laser.duration, true)) continue; - - m_objectsByTime.Add(obj->time, (*it).get()); - m_objectsByLeaveTime.Add(obj->time + obj->laser.duration + hittableObjectLeave, (*it).get()); - OnObjectEntered.Call((*it).get()); - } - - m_currLaserObject = objEnd; - } - - - // Check for lasers within the alert time - objEnd = m_SelectHitObject(m_playbackTime + alertLaserThreshold); - if (objEnd != m_currAlertObject) - { - for (auto it = m_currAlertObject; it < objEnd; it++) - { - MultiObjectState* obj = **it; - if (!m_playRange.Includes(obj->time)) continue; - - if (obj->type == ObjectType::Laser) - { - LaserObjectState* laser = (LaserObjectState*)obj; - if (!laser->prev) - OnLaserAlertEntered.Call(laser); - } - } - m_currAlertObject = objEnd; - } - - // Check passed objects - for (auto it = m_objectsByLeaveTime.begin(); it != m_objectsByLeaveTime.end() && it->first < m_playbackTime; it = m_objectsByLeaveTime.erase(it)) - { - ObjectState* objState = it->second; - MultiObjectState* obj = *(objState); - - // O(n^2) when there are n objects with same time, - // but n is usually small so let's ignore that issue for now... - { - auto pair = m_objectsByTime.equal_range(obj->time); - - for (auto it2 = pair.first; it2 != pair.second; ++it2) - { - if (it2->second == objState) - { - m_objectsByTime.erase(it2); - break; - } - } - } - - switch (obj->type) - { - case ObjectType::Hold: - OnObjectLeaved.Call(objState); - - if (m_effectObjects.Contains(objState)) - { - OnFXEnd.Call((HoldObjectState*)objState); - m_effectObjects.erase(objState); - } - break; - case ObjectType::Laser: - case ObjectType::Single: - OnObjectLeaved.Call(objState); - break; - case ObjectType::Event: - { - EventObjectState* evt = (EventObjectState*)obj; - - if (evt->key == EventKey::TrackRollBehaviour) - { - if (m_currentTrackRollBehaviour != evt->data.rollVal) - { - m_currentTrackRollBehaviour = evt->data.rollVal; - m_lastTrackRollBehaviourChange = obj->time; - } - } - - // Trigger event - OnEventChanged.Call(evt->key, evt->data); - m_eventMapping[evt->key] = evt->data; - } - default: - break; - } - } - - const MapTime audioPlaybackTime = m_playbackTime + audioOffset; - - // Process FX effects - for (auto& it : m_objectsByTime) - { - ObjectState* objState = it.second; - MultiObjectState* obj = *(objState); - - if (obj->type != ObjectType::Hold || obj->hold.effectType == EffectType::None) - { - continue; - } - - const MapTime endTime = obj->time + obj->hold.duration; - - // Send `OnFXBegin` a little bit earlier (the other side checks the exact timing again) - if (obj->time - 100 <= audioPlaybackTime && audioPlaybackTime <= endTime - 100) - { - if (!m_effectObjects.Contains(objState)) - { - OnFXBegin.Call((HoldObjectState*)objState); - m_effectObjects.Add(objState); - } - } - - if (endTime < audioPlaybackTime) - { - if (m_effectObjects.Contains(objState)) - { - OnFXEnd.Call((HoldObjectState*)objState); - m_effectObjects.erase(objState); - } - } - } + MapTime delta = newTime - m_playbackTime; + + if (m_isCalibration) + { + // Count bars + int32 beatID = 0; + uint32 nBeats = CountBeats(m_playbackTime - delta, delta, beatID); + const TimingPoint& tp = GetCurrentTimingPoint(); + double effectiveTime = ((double)newTime - tp.time); // Time with offset applied + m_barTime = (float)fmod(effectiveTime / (tp.beatDuration * tp.numerator), 1.0); + m_beatTime = (float)fmod(effectiveTime / tp.beatDuration, 1.0); + + // Set new time + m_playbackTime = newTime; + return; + } + + if (newTime < m_playbackTime) + { + // Don't allow backtracking + // Logf("New time was before last time %ull -> %ull", Logger::Warning, m_playbackTime, newTime); + return; + } + + // Fire initial effect changes (only once) + if (!m_initialEffectStateSent) + { + const BeatmapSettings& settings = m_beatmap->GetMapSettings(); + OnEventChanged.Call(EventKey::LaserEffectMix, settings.laserEffectMix); + OnEventChanged.Call(EventKey::LaserEffectType, settings.laserEffectType); + OnEventChanged.Call(EventKey::SlamVolume, settings.slamVolume); + m_initialEffectStateSent = true; + } + + // Count bars + int32 beatID = 0; + uint32 nBeats = CountBeats(m_playbackTime - delta, delta, beatID); + const TimingPoint& tp = GetCurrentTimingPoint(); + double effectiveTime = ((double)newTime - tp.time); // Time with offset applied + m_barTime = (float)fmod(effectiveTime / (tp.beatDuration * tp.numerator), 1.0); + m_beatTime = (float)fmod(effectiveTime / tp.beatDuration, 1.0); + + // Set new time + m_playbackTime = newTime; + + // Advance timing + Beatmap::TimingPointsIterator timingEnd = m_SelectTimingPoint(m_playbackTime); + if (timingEnd != m_currentTiming) + { + m_currentTiming = timingEnd; + /// TODO: Investigate why this causes score to be too high + // hittableLaserEnter = (*m_currentTiming)->beatDuration * 4.0; + // alertLaserThreshold = (*m_currentTiming)->beatDuration * 6.0; + OnTimingPointChanged.Call(m_currentTiming); + } + + // Advance lane toggle + Beatmap::LaneTogglePointsIterator laneToggleEnd = m_SelectLaneTogglePoint(m_playbackTime); + if (laneToggleEnd != m_currentLaneTogglePoint) + { + m_currentLaneTogglePoint = laneToggleEnd; + OnLaneToggleChanged.Call(m_currentLaneTogglePoint); + } + + // Advance objects + Beatmap::ObjectsIterator objEnd = m_SelectHitObject(m_playbackTime + hittableObjectEnter); + if (objEnd != m_currObject) + { + for (auto it = m_currObject; it < objEnd; it++) + { + MultiObjectState* obj = *(*it).get(); + if (obj->type == ObjectType::Laser) + continue; + + if (!m_playRange.Includes(obj->time)) + continue; + if (obj->type == ObjectType::Hold && !m_playRange.Includes(obj->time + obj->hold.duration, true)) + continue; + + MapTime duration = 0; + if (obj->type == ObjectType::Hold) + { + duration = obj->hold.duration; + } + else if (obj->type == ObjectType::Event) + { + // Tiny offset to make sure events are triggered before they are needed + duration = -2; + } + + m_objectsByTime.Add(obj->time, (*it).get()); + m_objectsByLeaveTime.Add(obj->time + duration + hittableObjectLeave, (*it).get()); + + OnObjectEntered.Call((*it).get()); + } + + m_currObject = objEnd; + } + + // Advance lasers + objEnd = m_SelectHitObject(m_playbackTime + hittableLaserEnter); + if (objEnd != m_currLaserObject) + { + for (auto it = m_currLaserObject; it < objEnd; it++) + { + MultiObjectState* obj = *(*it).get(); + if (obj->type != ObjectType::Laser) + continue; + + if (!m_playRange.Includes(obj->time)) + continue; + if (!m_playRange.Includes(obj->time + obj->laser.duration, true)) + continue; + + m_objectsByTime.Add(obj->time, (*it).get()); + m_objectsByLeaveTime.Add(obj->time + obj->laser.duration + hittableObjectLeave, (*it).get()); + OnObjectEntered.Call((*it).get()); + } + + m_currLaserObject = objEnd; + } + + // Check for lasers within the alert time + objEnd = m_SelectHitObject(m_playbackTime + alertLaserThreshold); + if (objEnd != m_currAlertObject) + { + for (auto it = m_currAlertObject; it < objEnd; it++) + { + MultiObjectState* obj = **it; + if (!m_playRange.Includes(obj->time)) + continue; + + if (obj->type == ObjectType::Laser) + { + LaserObjectState* laser = (LaserObjectState*)obj; + if (!laser->prev) + OnLaserAlertEntered.Call(laser); + } + } + m_currAlertObject = objEnd; + } + + // Check passed objects + for (auto it = m_objectsByLeaveTime.begin(); it != m_objectsByLeaveTime.end() && it->first < m_playbackTime; it = m_objectsByLeaveTime.erase(it)) + { + ObjectState* objState = it->second; + MultiObjectState* obj = *(objState); + + // O(n^2) when there are n objects with same time, + // but n is usually small so let's ignore that issue for now... + { + auto pair = m_objectsByTime.equal_range(obj->time); + + for (auto it2 = pair.first; it2 != pair.second; ++it2) + { + if (it2->second == objState) + { + m_objectsByTime.erase(it2); + break; + } + } + } + + switch (obj->type) + { + case ObjectType::Hold: + OnObjectLeaved.Call(objState); + + if (m_effectObjects.Contains(objState)) + { + OnFXEnd.Call((HoldObjectState*)objState); + m_effectObjects.erase(objState); + } + break; + case ObjectType::Laser: + case ObjectType::Single: + OnObjectLeaved.Call(objState); + break; + case ObjectType::Event: + { + EventObjectState* evt = (EventObjectState*)obj; + + if (evt->key == EventKey::TrackRollBehaviour) + { + if (m_currentTrackRollBehaviour != evt->data.rollVal) + { + m_currentTrackRollBehaviour = evt->data.rollVal; + m_lastTrackRollBehaviourChange = obj->time; + } + } + + // Trigger event + OnEventChanged.Call(evt->key, evt->data); + m_eventMapping[evt->key] = evt->data; + } + default: + break; + } + } + + const MapTime audioPlaybackTime = m_playbackTime + audioOffset; + + // Process FX effects + for (auto& it : m_objectsByTime) + { + ObjectState* objState = it.second; + MultiObjectState* obj = *(objState); + + if (obj->type != ObjectType::Hold || obj->hold.effectType == EffectType::None) + { + continue; + } + + const MapTime endTime = obj->time + obj->hold.duration; + + // Send `OnFXBegin` a little bit earlier (the other side checks the exact timing again) + if (obj->time - 100 <= audioPlaybackTime && audioPlaybackTime <= endTime - 100) + { + if (!m_effectObjects.Contains(objState)) + { + OnFXBegin.Call((HoldObjectState*)objState); + m_effectObjects.Add(objState); + } + } + + if (endTime < audioPlaybackTime) + { + if (m_effectObjects.Contains(objState)) + { + OnFXEnd.Call((HoldObjectState*)objState); + m_effectObjects.erase(objState); + } + } + } } void BeatmapPlayback::MakeCalibrationPlayback() { - m_isCalibration = true; - - for (size_t i = 0; i < 50; i++) - { - ButtonObjectState* newObject = new ButtonObjectState(); - newObject->index = i % 4; - newObject->time = static_cast(i * 500); - - m_calibrationObjects.Add(Ref((ObjectState*)newObject)); - } - - m_calibrationTiming = {}; - m_calibrationTiming.beatDuration = 500; - m_calibrationTiming.time = 0; - m_calibrationTiming.denominator = 4; - m_calibrationTiming.numerator = 4; + m_isCalibration = true; + + for (size_t i = 0; i < 50; i++) + { + ButtonObjectState* newObject = new ButtonObjectState(); + newObject->index = i % 4; + newObject->time = static_cast(i * 500); + + m_calibrationObjects.Add(Ref((ObjectState*)newObject)); + } + + m_calibrationTiming = {}; + m_calibrationTiming.beatDuration = 500; + m_calibrationTiming.time = 0; + m_calibrationTiming.denominator = 4; + m_calibrationTiming.numerator = 4; } const ObjectState* BeatmapPlayback::GetFirstButtonOrHoldAfterTime(MapTime time, int lane) const { - for (const auto& obj : m_beatmap->GetObjectStates()) - { - if (obj->time < time) - continue; + for (const auto& obj : m_beatmap->GetObjectStates()) + { + if (obj->time < time) + continue; - if (obj->type != ObjectType::Hold && obj->type != ObjectType::Single) - continue; + if (obj->type != ObjectType::Hold && obj->type != ObjectType::Single) + continue; - const MultiObjectState* mobj = *(obj.get()); + const MultiObjectState* mobj = *(obj.get()); - if (mobj->button.index != lane) - continue; + if (mobj->button.index != lane) + continue; - return obj.get(); - } + return obj.get(); + } - return nullptr; + return nullptr; } void BeatmapPlayback::GetObjectsInViewRange(float numBeats, Vector& objects) { - // TODO: properly implement backwards scroll speed support... - // numBeats *= 3; - - const static MapTime earlyVisibility = 200; - - if (m_isCalibration) { - for (auto& o : m_calibrationObjects) - { - if (o->time < (MapTime)(m_playbackTime - earlyVisibility)) - continue; - - if (o->time > m_playbackTime + static_cast(numBeats * m_calibrationTiming.beatDuration)) - break; - - objects.Add(o.get()); - } - - return; - } - - // Add objects - for (auto& it : m_objectsByTime) - { - objects.Add(it.second); - } - - Beatmap::TimingPointsIterator tp = m_SelectTimingPoint(m_playbackTime); - Beatmap::TimingPointsIterator tp_next = std::next(tp); - - // # of beats from m_playbackTime to curr TP - MapTime currRefTime = m_playbackTime; - float currBeats = 0.0f; - - for (Beatmap::ObjectsIterator obj = m_currObject; !IsEndObject(obj); ++obj) - { - const MapTime objTime = (*obj)->time; - - if (!m_playRange.Includes(objTime)) - { - if (m_playRange.HasEnd() && objTime >= m_playRange.end) - { - break; - } - - continue; - } - - if (!IsEndTiming(tp_next) && tp_next->time <= objTime) - { - currBeats += GetViewDistance(currRefTime, tp_next->time); - - tp = tp_next; - tp_next = std::next(tp_next); - currRefTime = tp->time; - } - - const float objBeats = currBeats + GetViewDistance(currRefTime, objTime); - if (objBeats >= numBeats) - { - break; - } - - // Lasers might be already added before - if ((*obj)->type == ObjectType::Laser && obj < m_currLaserObject) - { - continue; - } - - objects.Add(obj->get()); - } + // TODO: properly implement backwards scroll speed support... + // numBeats *= 3; + + const static MapTime earlyVisibility = 200; + + if (m_isCalibration) + { + for (auto& o : m_calibrationObjects) + { + if (o->time < (MapTime)(m_playbackTime - earlyVisibility)) + continue; + + if (o->time > m_playbackTime + static_cast(numBeats * m_calibrationTiming.beatDuration)) + break; + + objects.Add(o.get()); + } + + return; + } + + // Add objects + for (auto& it : m_objectsByTime) + { + objects.Add(it.second); + } + + Beatmap::TimingPointsIterator tp = m_SelectTimingPoint(m_playbackTime); + Beatmap::TimingPointsIterator tp_next = std::next(tp); + + // # of beats from m_playbackTime to curr TP + MapTime currRefTime = m_playbackTime; + float currBeats = 0.0f; + + for (Beatmap::ObjectsIterator obj = m_currObject; !IsEndObject(obj); ++obj) + { + const MapTime objTime = (*obj)->time; + + if (!m_playRange.Includes(objTime)) + { + if (m_playRange.HasEnd() && objTime >= m_playRange.end) + { + break; + } + + continue; + } + + if (!IsEndTiming(tp_next) && tp_next->time <= objTime) + { + currBeats += GetViewDistance(currRefTime, tp_next->time); + + tp = tp_next; + tp_next = std::next(tp_next); + currRefTime = tp->time; + } + + const float objBeats = currBeats + GetViewDistance(currRefTime, objTime); + if (objBeats >= numBeats) + { + break; + } + + // Lasers might be already added before + if ((*obj)->type == ObjectType::Laser && obj < m_currLaserObject) + { + continue; + } + + objects.Add(obj->get()); + } } inline MapTime GetLastBarPosition(const TimingPoint& tp, MapTime currTime, /* out*/ uint64& measureNo) { - MapTime offset = currTime - tp.time; - if (offset < 0) offset = 0; + MapTime offset = currTime - tp.time; + if (offset < 0) + offset = 0; - measureNo = static_cast(static_cast(offset) / tp.GetBarDuration()); + measureNo = static_cast(static_cast(offset) / tp.GetBarDuration()); - return tp.time + static_cast(measureNo * tp.GetBarDuration()); + return tp.time + static_cast(measureNo * tp.GetBarDuration()); } // Arbitrary cutoff @@ -404,328 +413,329 @@ constexpr uint64 MAX_DISP_BAR_COUNT = 1000; void BeatmapPlayback::GetBarPositionsInViewRange(float numBeats, Vector& barPositions) const { - uint64 measureNo = 0; - MapTime currTime = 0; - - if (m_isCalibration) - { - const TimingPoint& ctp = m_calibrationTiming; - currTime = GetLastBarPosition(ctp, m_playbackTime, measureNo); - - while (barPositions.size() < MAX_DISP_BAR_COUNT) - { - barPositions.Add(TimeToViewDistance(currTime)); - currTime = ctp.time + static_cast(++measureNo * ctp.GetBarDuration()); - - if (currTime - m_playbackTime >= ctp.beatDuration * numBeats) - { - return; - } - } - - return; - } - - Beatmap::TimingPointsIterator tp = m_SelectTimingPoint(m_playbackTime); - assert(!IsEndTiming(tp)); - - currTime = GetLastBarPosition(*tp, m_playbackTime, measureNo); - - while (barPositions.size() < MAX_DISP_BAR_COUNT) - { - barPositions.Add(TimeToViewDistance(currTime)); - - Beatmap::TimingPointsIterator ntp = next(tp); - currTime = tp->time + static_cast(++measureNo * tp->GetBarDuration()); - - if (!IsEndTiming(ntp) && currTime >= ntp->time) - { - tp = ntp; - currTime = ntp->time; - measureNo = 0; - } - - if (GetViewDistance(m_playbackTime, currTime) >= numBeats) - { - return; - } - } + uint64 measureNo = 0; + MapTime currTime = 0; + + if (m_isCalibration) + { + const TimingPoint& ctp = m_calibrationTiming; + currTime = GetLastBarPosition(ctp, m_playbackTime, measureNo); + + while (barPositions.size() < MAX_DISP_BAR_COUNT) + { + barPositions.Add(TimeToViewDistance(currTime)); + currTime = ctp.time + static_cast(++measureNo * ctp.GetBarDuration()); + + if (currTime - m_playbackTime >= ctp.beatDuration * numBeats) + { + return; + } + } + + return; + } + + Beatmap::TimingPointsIterator tp = m_SelectTimingPoint(m_playbackTime); + assert(!IsEndTiming(tp)); + + currTime = GetLastBarPosition(*tp, m_playbackTime, measureNo); + + while (barPositions.size() < MAX_DISP_BAR_COUNT) + { + barPositions.Add(TimeToViewDistance(currTime)); + + Beatmap::TimingPointsIterator ntp = next(tp); + currTime = tp->time + static_cast(++measureNo * tp->GetBarDuration()); + + if (!IsEndTiming(ntp) && currTime >= ntp->time) + { + tp = ntp; + currTime = ntp->time; + measureNo = 0; + } + + if (GetViewDistance(m_playbackTime, currTime) >= numBeats) + { + return; + } + } } const TimingPoint& BeatmapPlayback::GetCurrentTimingPoint() const { - if (m_isCalibration) - { - return m_calibrationTiming; - } + if (m_isCalibration) + { + return m_calibrationTiming; + } - if (IsEndTiming(m_currentTiming)) - { - return *(m_beatmap->GetFirstTimingPoint()); - } + if (IsEndTiming(m_currentTiming)) + { + return *(m_beatmap->GetFirstTimingPoint()); + } - return *m_currentTiming; + return *m_currentTiming; } const TimingPoint* BeatmapPlayback::GetTimingPointAt(MapTime time) const { - if (m_isCalibration) - { - return &m_calibrationTiming; - } - - Beatmap::TimingPointsIterator it = const_cast(this)->m_SelectTimingPoint(time); - if (IsEndTiming(it)) - { - return nullptr; - } - else - { - return &(*it); - } + if (m_isCalibration) + { + return &m_calibrationTiming; + } + + Beatmap::TimingPointsIterator it = const_cast(this)->m_SelectTimingPoint(time); + if (IsEndTiming(it)) + { + return nullptr; + } + else + { + return &(*it); + } } uint32 BeatmapPlayback::CountBeats(MapTime start, MapTime range, int32& startIndex, uint32 multiplier /*= 1*/) const { - const TimingPoint& tp = GetCurrentTimingPoint(); - int64 delta = (int64)start - (int64)tp.time; - double beatDuration = tp.GetWholeNoteLength() / tp.denominator; - int64 beatStart = (int64)floor((double)delta / (beatDuration / multiplier)); - int64 beatEnd = (int64)floor((double)(delta + range) / (beatDuration / multiplier)); - startIndex = ((int32)beatStart + 1) % tp.numerator; - return (uint32)Math::Max(beatEnd - beatStart, 0); + const TimingPoint& tp = GetCurrentTimingPoint(); + int64 delta = (int64)start - (int64)tp.time; + double beatDuration = tp.GetWholeNoteLength() / tp.denominator; + int64 beatStart = (int64)floor((double)delta / (beatDuration / multiplier)); + int64 beatEnd = (int64)floor((double)(delta + range) / (beatDuration / multiplier)); + startIndex = ((int32)beatStart + 1) % tp.numerator; + return (uint32)Math::Max(beatEnd - beatStart, 0); } float BeatmapPlayback::GetViewDistance(MapTime startTime, MapTime endTime) const { - if (startTime == endTime) - { - return 0.0f; - } + if (startTime == endTime) + { + return 0.0f; + } - if (cMod || m_isCalibration) - { - return GetViewDistanceIgnoringScrollSpeed(startTime, endTime); - } + if (cMod || m_isCalibration) + { + return GetViewDistanceIgnoringScrollSpeed(startTime, endTime); + } - return m_beatmap->GetBeatCountWithScrollSpeedApplied(startTime, endTime, m_currentTiming); + return m_beatmap->GetBeatCountWithScrollSpeedApplied(startTime, endTime, m_currentTiming); } float BeatmapPlayback::GetViewDistanceIgnoringScrollSpeed(MapTime startTime, MapTime endTime) const { - if (startTime == endTime) - { - return 0.0f; - } - - if (cMod) - { - return static_cast(endTime - startTime) / 480000.0f; - } - - if (m_isCalibration) - { - return static_cast((endTime - startTime) / m_calibrationTiming.beatDuration); - } - - return m_beatmap->GetBeatCount(startTime, endTime, m_currentTiming); + if (startTime == endTime) + { + return 0.0f; + } + + if (cMod) + { + return static_cast(endTime - startTime) / 480000.0f; + } + + if (m_isCalibration) + { + return static_cast((endTime - startTime) / m_calibrationTiming.beatDuration); + } + + return m_beatmap->GetBeatCount(startTime, endTime, m_currentTiming); } float BeatmapPlayback::GetZoom(uint8 index) const { - EffectTimeline::GraphType graphType; - - switch (index) - { - case 0: - graphType = EffectTimeline::GraphType::ZOOM_BOTTOM; - break; - case 1: - graphType = EffectTimeline::GraphType::ZOOM_TOP; - break; - case 2: - graphType = EffectTimeline::GraphType::SHIFT_X; - break; - case 3: - graphType = EffectTimeline::GraphType::ROTATION_Z; - break; - case 4: - return m_beatmap->GetCenterSplitValueAt(m_playbackTime); - break; - default: - assert(false); - break; - } - - return m_beatmap->GetGraphValueAt(graphType, m_playbackTime); + EffectTimeline::GraphType graphType; + + switch (index) + { + case 0: + graphType = EffectTimeline::GraphType::ZOOM_BOTTOM; + break; + case 1: + graphType = EffectTimeline::GraphType::ZOOM_TOP; + break; + case 2: + graphType = EffectTimeline::GraphType::SHIFT_X; + break; + case 3: + graphType = EffectTimeline::GraphType::ROTATION_Z; + break; + case 4: + return m_beatmap->GetCenterSplitValueAt(m_playbackTime); + break; + default: + assert(false); + break; + } + + return m_beatmap->GetGraphValueAt(graphType, m_playbackTime); } float BeatmapPlayback::GetScrollSpeed() const { - return m_beatmap->GetScrollSpeedAt(m_playbackTime); + return m_beatmap->GetScrollSpeedAt(m_playbackTime); } bool BeatmapPlayback::CheckIfManualTiltInstant() { - if (m_currentTrackRollBehaviour != TrackRollBehaviour::Manual) - { - return false; - } + if (m_currentTrackRollBehaviour != TrackRollBehaviour::Manual) + { + return false; + } - return m_beatmap->CheckIfManualTiltInstant(m_lastTrackRollBehaviourChange, m_playbackTime); + return m_beatmap->CheckIfManualTiltInstant(m_lastTrackRollBehaviourChange, m_playbackTime); } Beatmap::TimingPointsIterator BeatmapPlayback::m_SelectTimingPoint(MapTime time, bool allowReset) const { - assert(!m_isCalibration); + assert(!m_isCalibration); - return m_beatmap->GetTimingPoint(time, m_currentTiming, !allowReset); + return m_beatmap->GetTimingPoint(time, m_currentTiming, !allowReset); } Beatmap::LaneTogglePointsIterator BeatmapPlayback::m_SelectLaneTogglePoint(MapTime time, bool allowReset) const { - Beatmap::LaneTogglePointsIterator objStart = m_currentLaneTogglePoint; - - if (IsEndLaneToggle(objStart)) - return objStart; - - // Start at front of array if current object lies ahead of given input time - if (objStart->time > time && allowReset) - objStart = m_beatmap->GetFirstLaneTogglePoint(); - - // Keep advancing the start pointer while the next object's starting time lies before the input time - while (true) - { - if (!IsEndLaneToggle(objStart + 1) && (objStart + 1)->time <= time) - { - objStart = objStart + 1; - } - else - break; - } - - return objStart; + Beatmap::LaneTogglePointsIterator objStart = m_currentLaneTogglePoint; + + if (IsEndLaneToggle(objStart)) + return objStart; + + // Start at front of array if current object lies ahead of given input time + if (objStart->time > time && allowReset) + objStart = m_beatmap->GetFirstLaneTogglePoint(); + + // Keep advancing the start pointer while the next object's starting time lies before the input time + while (true) + { + if (!IsEndLaneToggle(objStart + 1) && (objStart + 1)->time <= time) + { + objStart = objStart + 1; + } + else + break; + } + + return objStart; } Beatmap::ObjectsIterator BeatmapPlayback::m_SelectHitObject(MapTime time, bool allowReset) const { - Beatmap::ObjectsIterator objStart = m_currObject; - if (IsEndObject(objStart)) - return objStart; - - // Start at front of array if current object lies ahead of given input time - if (objStart[0]->time > time && allowReset) - objStart = m_beatmap->GetFirstObjectState(); - - // Keep advancing the start pointer while the next object's starting time lies before the input time - while (true) - { - if (!IsEndObject(objStart) && objStart[0]->time < time) - { - objStart = std::next(objStart); - } - else - break; - } - - return objStart; + Beatmap::ObjectsIterator objStart = m_currObject; + if (IsEndObject(objStart)) + return objStart; + + // Start at front of array if current object lies ahead of given input time + if (objStart[0]->time > time && allowReset) + objStart = m_beatmap->GetFirstObjectState(); + + // Keep advancing the start pointer while the next object's starting time lies before the input time + while (true) + { + if (!IsEndObject(objStart) && objStart[0]->time < time) + { + objStart = std::next(objStart); + } + else + break; + } + + return objStart; } bool BeatmapPlayback::IsEndObject(const Beatmap::ObjectsIterator& obj) const { - return obj == m_beatmap->GetEndObjectState(); + return obj == m_beatmap->GetEndObjectState(); } bool BeatmapPlayback::IsEndTiming(const Beatmap::TimingPointsIterator& obj) const { - return obj == m_beatmap->GetEndTimingPoint(); + return obj == m_beatmap->GetEndTimingPoint(); } bool BeatmapPlayback::IsEndLaneToggle(const Beatmap::LaneTogglePointsIterator& obj) const { - return obj == m_beatmap->GetEndLaneTogglePoint(); + return obj == m_beatmap->GetEndLaneTogglePoint(); } Vector BeatmapPlayback::GetStateString() const { - auto ObjectStateToStr = [](const ObjectState* state) - { - if (state == nullptr) - { - return String{"null"}; - } - - const auto* obj = (const MultiObjectState*) state; - - switch (obj->type) - { - case ObjectType::Single: - { - const auto* state = (const ButtonObjectState*)obj; - return Utility::Sprintf("%d(type=single index=%u)", obj->time, static_cast(state->index)); - } - break; - case ObjectType::Hold: - { - const auto* state = (const HoldObjectState*)obj; - return Utility::Sprintf("%d(type=hold index=%u len=%d)", obj->time, static_cast(state->index), state->duration); - } - break; - case ObjectType::Laser: - { - const auto* state = (const LaserObjectState*)obj; - return Utility::Sprintf("%d(type=laser %s len=%d start=%.3f end=%.3f flags=%x)", - obj->time, state->index == 0 ? "left" : state->index == 1 ? "right" : "invalid", state->duration, state->points[0], state->points[1], static_cast(state->flags)); - } - break; - case ObjectType::Event: - { - const auto* state = (const EventObjectState*)obj; - return Utility::Sprintf("%d(type=event key=%d)", - obj->time, static_cast(state->key)); - } - break; - default: - return Utility::Sprintf("%d(type=%u)", obj->time, static_cast(obj->type)); - } - }; - - auto BeatmapObjectToStr = [&](const Beatmap::ObjectsIterator& it) - { - if (IsEndObject(it)) - { - return String{"end"}; - } - - return ObjectStateToStr(it->get()); - }; - - Vector lines; - - const auto& tp = GetCurrentTimingPoint(); - const float currentBPM = (float)(60000.0 / tp.beatDuration); - lines.Add(Utility::Sprintf("TimingPoint: time=%d bpm=%.3f sig=%d/%d", tp.time, currentBPM, tp.numerator, tp.denominator)); - - lines.Add(Utility::Sprintf( - "PlayRange: %d to %d | Playback: time=%d", - m_playRange.begin, m_playRange.end, m_playbackTime - )); - - lines.Add(Utility::Sprintf("CurrObject: %s", BeatmapObjectToStr(m_currObject))); - lines.Add(Utility::Sprintf("CurrLaser: %s", BeatmapObjectToStr(m_currLaserObject))); - lines.Add(Utility::Sprintf("CurrAlert: %s", BeatmapObjectToStr(m_currAlertObject))); - - if (m_objectsByTime.empty()) - { - lines.Add("Objects: none"); - } - else - { - const auto* firstObj = m_objectsByTime.begin()->second; - const auto* lastObj = m_objectsByTime.rbegin()->second; - - lines.Add(Utility::Sprintf("Objects: %u objects, %s to %s", m_objectsByTime.size(), ObjectStateToStr(firstObj), ObjectStateToStr(lastObj))); - } - - return lines; + auto ObjectStateToStr = [](const ObjectState* state) + { + if (state == nullptr) + { + return String{ "null" }; + } + + const auto* obj = (const MultiObjectState*)state; + + switch (obj->type) + { + case ObjectType::Single: + { + const auto* state = (const ButtonObjectState*)obj; + return Utility::Sprintf("%d(type=single index=%u)", obj->time, static_cast(state->index)); + } + break; + case ObjectType::Hold: + { + const auto* state = (const HoldObjectState*)obj; + return Utility::Sprintf("%d(type=hold index=%u len=%d)", obj->time, static_cast(state->index), state->duration); + } + break; + case ObjectType::Laser: + { + const auto* state = (const LaserObjectState*)obj; + return Utility::Sprintf("%d(type=laser %s len=%d start=%.3f end=%.3f flags=%x)", + obj->time, state->index == 0 ? "left" : state->index == 1 ? "right" + : "invalid", + state->duration, state->points[0], state->points[1], static_cast(state->flags)); + } + break; + case ObjectType::Event: + { + const auto* state = (const EventObjectState*)obj; + return Utility::Sprintf("%d(type=event key=%d)", + obj->time, static_cast(state->key)); + } + break; + default: + return Utility::Sprintf("%d(type=%u)", obj->time, static_cast(obj->type)); + } + }; + + auto BeatmapObjectToStr = [&](const Beatmap::ObjectsIterator& it) + { + if (IsEndObject(it)) + { + return String{ "end" }; + } + + return ObjectStateToStr(it->get()); + }; + + Vector lines; + + const auto& tp = GetCurrentTimingPoint(); + const float currentBPM = (float)(60000.0 / tp.beatDuration); + lines.Add(Utility::Sprintf("TimingPoint: time=%d bpm=%.3f sig=%d/%d", tp.time, currentBPM, tp.numerator, tp.denominator)); + + lines.Add(Utility::Sprintf( + "PlayRange: %d to %d | Playback: time=%d", + m_playRange.begin, m_playRange.end, m_playbackTime)); + + lines.Add(Utility::Sprintf("CurrObject: %s", BeatmapObjectToStr(m_currObject))); + lines.Add(Utility::Sprintf("CurrLaser: %s", BeatmapObjectToStr(m_currLaserObject))); + lines.Add(Utility::Sprintf("CurrAlert: %s", BeatmapObjectToStr(m_currAlertObject))); + + if (m_objectsByTime.empty()) + { + lines.Add("Objects: none"); + } + else + { + const auto* firstObj = m_objectsByTime.begin()->second; + const auto* lastObj = m_objectsByTime.rbegin()->second; + + lines.Add(Utility::Sprintf("Objects: %u objects, %s to %s", m_objectsByTime.size(), ObjectStateToStr(firstObj), ObjectStateToStr(lastObj))); + } + + return lines; } diff --git a/Beatmap/src/ChallengeIndex.cpp b/Beatmap/src/ChallengeIndex.cpp index ae9a1daff..a6ac034f3 100644 --- a/Beatmap/src/ChallengeIndex.cpp +++ b/Beatmap/src/ChallengeIndex.cpp @@ -4,108 +4,63 @@ nlohmann::json ChallengeIndex::LoadJson(const Buffer& jsonBuf, const String& path) { - String jsonData((char*)jsonBuf.data(), jsonBuf.size()); - Logf("JSON loaded: %s", Logger::Severity::Debug, *jsonData); - - try - { - return nlohmann::json::parse(*jsonData); - } - catch (const std::exception& e) - { - Logf("Encountered JSON error with %s: %s", Logger::Severity::Warning, path, e.what()); - } - return nlohmann::json(); + String jsonData((char*)jsonBuf.data(), jsonBuf.size()); + Logf("JSON loaded: %s", Logger::Severity::Debug, *jsonData); + + try + { + return nlohmann::json::parse(*jsonData); + } + catch (const std::exception& e) + { + Logf("Encountered JSON error with %s: %s", Logger::Severity::Warning, path, e.what()); + } + return nlohmann::json(); } nlohmann::json ChallengeIndex::LoadJson(const String& path) { - File chalFile; - if (!chalFile.OpenRead(path)) - return false; - - Buffer jsonBuf; - jsonBuf.resize(chalFile.GetSize()); - chalFile.Read(jsonBuf.data(), jsonBuf.size()); - return ChallengeIndex::LoadJson(jsonBuf, path); + File chalFile; + if (!chalFile.OpenRead(path)) + return false; + + Buffer jsonBuf; + jsonBuf.resize(chalFile.GetSize()); + chalFile.Read(jsonBuf.data(), jsonBuf.size()); + return ChallengeIndex::LoadJson(jsonBuf, path); } int32 jsonIntValue(nlohmann::json j, const String& k, int32 def) { - if (j[k].is_string()) - { - } - return j.value(k, def); + if (j[k].is_string()) + { + } + return j.value(k, def); } // Each of these has a string key and then a pair of strings to use // The first is for global reqs and the second is for overriden reqs const Map> ChallengeIndex::ChallengeDescriptionStrings = { - // Overrideable reqs - {"clear", { - "Clear all charts", - "Clear this chart" - }}, - {"excessive clear", { - "Clear all charts on Excessive Rate", - "Clear this chart on Excessive Rate" - }}, - {"permissive clear", { - "Clear all charts on Permissive Rate", - "Clear this chart on Permissive Rate" - }}, - {"blastive clear", { - "Clear all charts on Blastive %.1f* Rate", - "Clear this chart on Blastive %.1f* Rate" - }}, - {"permissive clear", { - "Play on Permissive Rate", - "" - }}, - {"min_percentage", { - "Get %d%% completion on each chart", - "Get %d%% completion" - }}, - {"min_gauge",{ - "Get %d%% gauge on each chart", - "Get %d%% gauge" - }}, - {"max_errors",{ - "Get less than %d errors per chart", - "Get less than %d errors" - }}, - {"max_nears",{ - "Get less than %d nears per chart", - "Get less than %d nears" - }}, - {"min_crits",{ - "Get at least %d crits per chart", - "Get at least %d crits" - }}, - {"min_chain",{ - "Get a chain at least %d long each chart", - "Get a chain at least %d long" - }}, - - // Global reqs - {"min_average_percentage", { - "Get %d%% completion overall", "" - }}, - {"min_average_gauge", { - "Get %d%% gauge overall", "" - }}, - {"max_overall_errors", { - "Get less than %d errors total", "" - }}, - {"max_overall_nears", { - "Get less than %d nears total", "" - }}, - {"min_overall_crits", { - "Get at least %d crits total", "" - }}, - {"gauge_carry_over", { - "Gauge does not reset", "" - }}, + // Overrideable reqs + {"clear", {"Clear all charts", "Clear this chart"}}, + {"excessive clear", {"Clear all charts on Excessive Rate", "Clear this chart on Excessive Rate"}}, + {"permissive clear", {"Clear all charts on Permissive Rate", "Clear this chart on Permissive Rate"}}, + {"blastive clear", {"Clear all charts on Blastive %.1f* Rate", "Clear this chart on Blastive %.1f* Rate"}}, + {"permissive clear", {"Play on Permissive Rate", ""}}, + {"min_percentage", {"Get %d%% completion on each chart", "Get %d%% completion"}}, + {"min_gauge", {"Get %d%% gauge on each chart", "Get %d%% gauge"}}, + {"max_errors", {"Get less than %d errors per chart", "Get less than %d errors"}}, + {"max_nears", {"Get less than %d nears per chart", "Get less than %d nears"}}, + {"min_crits", {"Get at least %d crits per chart", "Get at least %d crits"}}, + {"min_chain", {"Get a chain at least %d long each chart", "Get a chain at least %d long"}}, + + // Global reqs + {"min_average_percentage", {"Get %d%% completion overall", ""}}, + {"min_average_gauge", {"Get %d%% gauge overall", ""}}, + {"max_overall_errors", {"Get less than %d errors total", ""}}, + {"max_overall_nears", {"Get less than %d nears total", ""}}, + {"min_overall_crits", {"Get at least %d crits total", ""}}, + {"gauge_carry_over", {"Gauge does not reset", ""}}, }; // Generate a description for a challenge requirement @@ -116,320 +71,317 @@ const Map> ChallengeIndex::ChallengeDescriptio // mult is a value multiplier // add is a value addition String ChallengeIndex::ChallengeDescriptionVal( - const nlohmann::json& j, - const String& keyName, - const String& stringName, - bool isOverride, - int mult, - int add) + const nlohmann::json& j, + const String& keyName, + const String& stringName, + bool isOverride, + int mult, + int add) { - // This function requires a %d or %u formater, so just sanity check in debug - assert(ChallengeDescriptionStrings.Find(stringName)); - assert(ChallengeDescriptionStrings.Find(stringName)->first.find("%d")!=String::npos - || ChallengeDescriptionStrings.Find(stringName)->first.find("%u")!=String::npos); - assert(!isOverride || ChallengeDescriptionStrings.Find(stringName)->first.find("%d")!=String::npos - || ChallengeDescriptionStrings.Find(stringName)->first.find("%u")!=String::npos); - - if (!j.contains(keyName)) - return ""; - - const auto* str = ChallengeDescriptionStrings.Find(stringName); - - const auto& k = j[keyName]; - int32 value = 0; - if (isOverride && k.is_null()) - { - String name = keyName; - // TODO(itszn) better way to insert name into string - std::replace(name.begin(), name.end(), '_', ' '); - return " > Ignore " + name + " requirement for this chart\n"; - } - - if (k.is_number_float()) - value = static_cast(j.value(keyName, 0.0f) * mult + add); - else if (k.is_number_integer()) - value = j.value(keyName, 0) * mult + add; - else if (k.is_string()) - { - String str; - k.get_to(str); - value = atoi(*str) * mult + add; - } - else - { - // Unknown type, skip - return ""; - } - return (isOverride? " - ":"- ") + Utility::Sprintf(*(isOverride ? str->second : str->first), value) + "\n"; + // This function requires a %d or %u formater, so just sanity check in debug + assert(ChallengeDescriptionStrings.Find(stringName)); + assert(ChallengeDescriptionStrings.Find(stringName)->first.find("%d") != String::npos || ChallengeDescriptionStrings.Find(stringName)->first.find("%u") != String::npos); + assert(!isOverride || ChallengeDescriptionStrings.Find(stringName)->first.find("%d") != String::npos || ChallengeDescriptionStrings.Find(stringName)->first.find("%u") != String::npos); + + if (!j.contains(keyName)) + return ""; + + const auto* str = ChallengeDescriptionStrings.Find(stringName); + + const auto& k = j[keyName]; + int32 value = 0; + if (isOverride && k.is_null()) + { + String name = keyName; + // TODO(itszn) better way to insert name into string + std::replace(name.begin(), name.end(), '_', ' '); + return " > Ignore " + name + " requirement for this chart\n"; + } + + if (k.is_number_float()) + value = static_cast(j.value(keyName, 0.0f) * mult + add); + else if (k.is_number_integer()) + value = j.value(keyName, 0) * mult + add; + else if (k.is_string()) + { + String str; + k.get_to(str); + value = atoi(*str) * mult + add; + } + else + { + // Unknown type, skip + return ""; + } + return (isOverride ? " - " : "- ") + Utility::Sprintf(*(isOverride ? str->second : str->first), value) + "\n"; } // Use this if the keyName is the same as the stringName String ChallengeIndex::ChallengeDescriptionVal( - const nlohmann::json& j, - const String& keyName, - bool isOverride, - int mult, - int add) + const nlohmann::json& j, + const String& keyName, + bool isOverride, + int mult, + int add) { - return ChallengeIndex::ChallengeDescriptionVal(j, keyName, keyName, isOverride, mult, add); + return ChallengeIndex::ChallengeDescriptionVal(j, keyName, keyName, isOverride, mult, add); } void ChallengeIndex::GenerateDescription() { - String desc = ""; - if (!settings.contains("global")) - { - reqText = "No requirements"; - return; - } - - const auto& j = settings["global"]; - if (j.value("clear", false)) - { - if (j.value("excessive_gauge", false)) - desc += "- " + ChallengeDescriptionStrings.Find("excessive clear")->first; - else if (j.value("permissive_gauge", false)) - desc += "- " + ChallengeDescriptionStrings.Find("permissive clear")->first; - else if (j.value("blastive_gauge", false)) - { - float level = j.value("gauge_level", 0.5f); - desc += "- " + Utility::Sprintf(*ChallengeDescriptionStrings.Find("blastive clear")->first, level); - } - else - desc += "- " + ChallengeDescriptionStrings.Find("clear")->first; - - if (j.value("gauge_carry_over", false)) - desc += " (" + ChallengeDescriptionStrings.Find("gauge_carry_over")->first + ")"; - - desc += "\n"; - } - - desc += ChallengeDescriptionVal(j, String("min_percentage"), false); - desc += ChallengeDescriptionVal(j, String("min_average_percentage"), false); - desc += ChallengeDescriptionVal(j, String("min_gauge"), false, /*mult=*/100); - desc += ChallengeDescriptionVal(j, String("min_average_gauge"), false, /*mult=*/100); - desc += ChallengeDescriptionVal(j, String("max_errors"), false, /*mult=*/1, /*add=*/1); - desc += ChallengeDescriptionVal(j, String("max_overall_errors"), false, /*mult=*/1, /*add=*/1); - desc += ChallengeDescriptionVal(j, String("max_average_errors"), // Reuse overall string ^ - String("max_overall_errors"), false, /*mult=*/totalNumCharts, /*add=*/1); - desc += ChallengeDescriptionVal(j, String("max_nears"), false, /*mult=*/1, /*add=*/1); - desc += ChallengeDescriptionVal(j, String("max_overall_nears"), false, /*mult=*/1, /*add=*/1); - desc += ChallengeDescriptionVal(j, String("max_average_nears"), // Reuse overall string ^ - String("max_overall_nears"), false, /*mult=*/totalNumCharts, /*add=*/1); - desc += ChallengeDescriptionVal(j, String("min_crits"), false); - desc += ChallengeDescriptionVal(j, String("min_overall_crits"), false); - desc += ChallengeDescriptionVal(j, String("min_average_crits"), // Reuse overall string ^ - String("min_overall_crits"), false, /*mult=*/totalNumCharts, /*add=*/0); - desc += ChallengeDescriptionVal(j, String("min_chain"), false); - - // If no overrides don't do anything - if (!settings.contains("overrides")) - { - this->reqText = desc; - return; - } - - desc += "\n"; - - const auto& o = settings["overrides"]; - unsigned int maxNum = static_cast(std::min(std::min((size_t)totalNumCharts, charts.size()), o.size())); - - for (unsigned int i = 0; i < maxNum; i++) - { - String overdesc = ""; - const auto& j = o[i]; - - // Special case for clear overriding global clear/excessive - if (j.contains("clear") || j.contains("excessive_gauge") || - j.contains("blastive_gauge") || j.contains("permissive_gauge")) - { - if (j.contains("clear") && !j.value("clear", false)) - { - // If we are disabling the global clear req - if (settings["global"].value("clear", false)) - overdesc += " > Ignore clear requirement for this chart\n"; - } - else - { - bool isHard = j.value("excessive_gauge", false) || settings["global"].value("excessive_gauge", false); - bool isPermissive = j.value("permissive_gauge", false) || settings["global"].value("permissive_gauge", false); - bool isBlastive = j.value("blastive_gauge", false) || settings["global"].value("blastive_gauge", false); - if (isBlastive) - { - float level = 0.5; - if (j.contains("gauge_level")) - level = j.value("gauge_level", 0.5f); - else - level = settings["global"].value("gauge_level", 0.5f); - - } - else if (isPermissive) - overdesc += " - " + ChallengeDescriptionStrings.Find("permissive clear")->second + "\n"; - else if (isHard) - overdesc += " - " + ChallengeDescriptionStrings.Find("excessive clear")->second + "\n"; - else - overdesc += " - " + ChallengeDescriptionStrings.Find("clear")->second + "\n"; - } - } - - overdesc += ChallengeDescriptionVal(j, String("min_percentage"), true); - overdesc += ChallengeDescriptionVal(j, String("min_gauge"), true, /*mult=*/100); - overdesc += ChallengeDescriptionVal(j, String("max_errors"), true, /*mult=*/1, /*add=*/1); - overdesc += ChallengeDescriptionVal(j, String("max_nears"), true, /*mult=*/1, /*add=*/1); - overdesc += ChallengeDescriptionVal(j, String("min_crits"), true); - overdesc += ChallengeDescriptionVal(j, String("min_chain"), true); - - if (!overdesc.empty()) - desc += charts[i]->title + ":\n" + overdesc; - } - - if (desc.empty()) - desc = "No requirements"; - - this->reqText = desc; + String desc = ""; + if (!settings.contains("global")) + { + reqText = "No requirements"; + return; + } + + const auto& j = settings["global"]; + if (j.value("clear", false)) + { + if (j.value("excessive_gauge", false)) + desc += "- " + ChallengeDescriptionStrings.Find("excessive clear")->first; + else if (j.value("permissive_gauge", false)) + desc += "- " + ChallengeDescriptionStrings.Find("permissive clear")->first; + else if (j.value("blastive_gauge", false)) + { + float level = j.value("gauge_level", 0.5f); + desc += "- " + Utility::Sprintf(*ChallengeDescriptionStrings.Find("blastive clear")->first, level); + } + else + desc += "- " + ChallengeDescriptionStrings.Find("clear")->first; + + if (j.value("gauge_carry_over", false)) + desc += " (" + ChallengeDescriptionStrings.Find("gauge_carry_over")->first + ")"; + + desc += "\n"; + } + + desc += ChallengeDescriptionVal(j, String("min_percentage"), false); + desc += ChallengeDescriptionVal(j, String("min_average_percentage"), false); + desc += ChallengeDescriptionVal(j, String("min_gauge"), false, /*mult=*/100); + desc += ChallengeDescriptionVal(j, String("min_average_gauge"), false, /*mult=*/100); + desc += ChallengeDescriptionVal(j, String("max_errors"), false, /*mult=*/1, /*add=*/1); + desc += ChallengeDescriptionVal(j, String("max_overall_errors"), false, /*mult=*/1, /*add=*/1); + desc += ChallengeDescriptionVal(j, String("max_average_errors"), // Reuse overall string ^ + String("max_overall_errors"), false, /*mult=*/totalNumCharts, /*add=*/1); + desc += ChallengeDescriptionVal(j, String("max_nears"), false, /*mult=*/1, /*add=*/1); + desc += ChallengeDescriptionVal(j, String("max_overall_nears"), false, /*mult=*/1, /*add=*/1); + desc += ChallengeDescriptionVal(j, String("max_average_nears"), // Reuse overall string ^ + String("max_overall_nears"), false, /*mult=*/totalNumCharts, /*add=*/1); + desc += ChallengeDescriptionVal(j, String("min_crits"), false); + desc += ChallengeDescriptionVal(j, String("min_overall_crits"), false); + desc += ChallengeDescriptionVal(j, String("min_average_crits"), // Reuse overall string ^ + String("min_overall_crits"), false, /*mult=*/totalNumCharts, /*add=*/0); + desc += ChallengeDescriptionVal(j, String("min_chain"), false); + + // If no overrides don't do anything + if (!settings.contains("overrides")) + { + this->reqText = desc; + return; + } + + desc += "\n"; + + const auto& o = settings["overrides"]; + unsigned int maxNum = static_cast(std::min(std::min((size_t)totalNumCharts, charts.size()), o.size())); + + for (unsigned int i = 0; i < maxNum; i++) + { + String overdesc = ""; + const auto& j = o[i]; + + // Special case for clear overriding global clear/excessive + if (j.contains("clear") || j.contains("excessive_gauge") || + j.contains("blastive_gauge") || j.contains("permissive_gauge")) + { + if (j.contains("clear") && !j.value("clear", false)) + { + // If we are disabling the global clear req + if (settings["global"].value("clear", false)) + overdesc += " > Ignore clear requirement for this chart\n"; + } + else + { + bool isHard = j.value("excessive_gauge", false) || settings["global"].value("excessive_gauge", false); + bool isPermissive = j.value("permissive_gauge", false) || settings["global"].value("permissive_gauge", false); + bool isBlastive = j.value("blastive_gauge", false) || settings["global"].value("blastive_gauge", false); + if (isBlastive) + { + float level = 0.5; + if (j.contains("gauge_level")) + level = j.value("gauge_level", 0.5f); + else + level = settings["global"].value("gauge_level", 0.5f); + } + else if (isPermissive) + overdesc += " - " + ChallengeDescriptionStrings.Find("permissive clear")->second + "\n"; + else if (isHard) + overdesc += " - " + ChallengeDescriptionStrings.Find("excessive clear")->second + "\n"; + else + overdesc += " - " + ChallengeDescriptionStrings.Find("clear")->second + "\n"; + } + } + + overdesc += ChallengeDescriptionVal(j, String("min_percentage"), true); + overdesc += ChallengeDescriptionVal(j, String("min_gauge"), true, /*mult=*/100); + overdesc += ChallengeDescriptionVal(j, String("max_errors"), true, /*mult=*/1, /*add=*/1); + overdesc += ChallengeDescriptionVal(j, String("max_nears"), true, /*mult=*/1, /*add=*/1); + overdesc += ChallengeDescriptionVal(j, String("min_crits"), true); + overdesc += ChallengeDescriptionVal(j, String("min_chain"), true); + + if (!overdesc.empty()) + desc += charts[i]->title + ":\n" + overdesc; + } + + if (desc.empty()) + desc = "No requirements"; + + this->reqText = desc; } void ChallengeIndex::FindCharts(MapDatabase* db, const nlohmann::json& chartsToFind) { - totalNumCharts = 0; - - if (chartsToFind.is_discarded() || !chartsToFind.is_array()) - { - Logf("Unable to understand charts for challenge %s", Logger::Severity::Warning, path); - return; - } - - totalNumCharts = static_cast(chartsToFind.size()); - for (auto& el : chartsToFind.items()) - { - ChartIndex* chart = nullptr; - if (el.value().is_string()) - { - String val; - String kshMatch = ".ksh"; - el.value().get_to(val); - - // https://stackoverflow.com/questions/874134/find-out-if-string-ends-with-another-string-in-c/876704#876704 - if (std::mismatch(kshMatch.rbegin(), kshMatch.rend(), val.rbegin()).first == kshMatch.rend()) - { - // Look up as path - chart = db->FindFirstChartByPath(val); - if (chart == nullptr) - Logf("Could not find chart by path %s for challenge %s", Logger::Severity::Warning, *val, *path); - } - else - { - chart = db->FindFirstChartByHash(val); - if (chart == nullptr) - Logf("Could not find chart by *hash* %s for challenge %s. If you are using a path, make sure it ends with `.ksh`", Logger::Severity::Warning, *val, *path); - } - } - else if (el.value().is_object()) - { - const auto& o = el.value(); - if (!o.contains("name") || !o["name"].is_string() || !o.contains("level") || !o["level"].is_number_integer()) - { - Logf("Found invalid name+level `%s` for challenge %s in database", Logger::Severity::Warning, o.dump().c_str(), *path); - missingChart = true; - continue; - } - String name; - o["name"].get_to(name); - int32 level = o.value("level", 0); - chart = db->FindFirstChartByNameAndLevel(name, level); - if (chart == nullptr) - Logf("Could not find chart %s for challenge %s", Logger::Severity::Warning, o.dump().c_str(), *path); - } - else - { - if (!el.value().is_discarded()) - Logf("Found non string/object chart entry `%s` for challenge %s in database", Logger::Severity::Warning, el.value().dump().c_str(), *path); - else - Logf("Found invalid json chart entry for challenge %s in database", Logger::Severity::Warning, *path); - missingChart = true; - continue; - } - - if (chart) - charts.push_back(chart); - else - missingChart = true; - } - + totalNumCharts = 0; + + if (chartsToFind.is_discarded() || !chartsToFind.is_array()) + { + Logf("Unable to understand charts for challenge %s", Logger::Severity::Warning, path); + return; + } + + totalNumCharts = static_cast(chartsToFind.size()); + for (auto& el : chartsToFind.items()) + { + ChartIndex* chart = nullptr; + if (el.value().is_string()) + { + String val; + String kshMatch = ".ksh"; + el.value().get_to(val); + + // https://stackoverflow.com/questions/874134/find-out-if-string-ends-with-another-string-in-c/876704#876704 + if (std::mismatch(kshMatch.rbegin(), kshMatch.rend(), val.rbegin()).first == kshMatch.rend()) + { + // Look up as path + chart = db->FindFirstChartByPath(val); + if (chart == nullptr) + Logf("Could not find chart by path %s for challenge %s", Logger::Severity::Warning, *val, *path); + } + else + { + chart = db->FindFirstChartByHash(val); + if (chart == nullptr) + Logf("Could not find chart by *hash* %s for challenge %s. If you are using a path, make sure it ends with `.ksh`", Logger::Severity::Warning, *val, *path); + } + } + else if (el.value().is_object()) + { + const auto& o = el.value(); + if (!o.contains("name") || !o["name"].is_string() || !o.contains("level") || !o["level"].is_number_integer()) + { + Logf("Found invalid name+level `%s` for challenge %s in database", Logger::Severity::Warning, o.dump().c_str(), *path); + missingChart = true; + continue; + } + String name; + o["name"].get_to(name); + int32 level = o.value("level", 0); + chart = db->FindFirstChartByNameAndLevel(name, level); + if (chart == nullptr) + Logf("Could not find chart %s for challenge %s", Logger::Severity::Warning, o.dump().c_str(), *path); + } + else + { + if (!el.value().is_discarded()) + Logf("Found non string/object chart entry `%s` for challenge %s in database", Logger::Severity::Warning, el.value().dump().c_str(), *path); + else + Logf("Found invalid json chart entry for challenge %s in database", Logger::Severity::Warning, *path); + missingChart = true; + continue; + } + + if (chart) + charts.push_back(chart); + else + missingChart = true; + } } bool ChallengeIndex::BasicValidate(const nlohmann::json& settings, const String& path) { - if (settings.is_discarded() || !settings.is_object()) - return false; - - if (!settings.contains("title") || !settings["title"].is_string()) - { - Logf("Encountered error loading challenge %s: missing or invalid title", Logger::Severity::Warning, *path); - return false; - } - if (!settings.contains("level") || !settings["level"].is_number_integer()) - { - Logf("Encountered error loading challenge %s: missing or invalid level", Logger::Severity::Warning, *path); - return false; - } - - if (!settings.contains("charts") || !settings["charts"].is_array()) - { - Logf("Encountered error loading challenge %s: missing or invalid chart array", Logger::Severity::Warning, *path); - return false; - } - if (settings["charts"].size() == 0) - { - Logf("Encountered error loading challenge %s: Must have at least one chart", Logger::Severity::Warning, *path); - return false; - } - for (auto& el : settings["charts"].items()) - { - if (el.value().is_string()) {} - else if (el.value().is_object()) - { - const auto& v = el.value(); - if (!v.contains("name") || !v["name"].is_string()) - { - Logf("Encountered error loading challenge %s: Chart entry `%s`: \"name\" must be a string", Logger::Severity::Warning, *path, v.dump().c_str()); - return false; - } - if (!v.contains("level") || !v["level"].is_number_integer()) - { - Logf("Encountered error loading challenge %s: Chart entry `%s`: \"level\" must be an integer", Logger::Severity::Warning, *path, v.dump().c_str()); - return false; - } - } - else - { - Logf("Encountered error loading challenge %s: Chart entry `%s`: must be string for hash/path or object for name+level", Logger::Severity::Warning, *path, el.value().dump().c_str()); - return false; - } - } - - if (settings.contains("global") && !settings["global"].is_object()) - { - Logf("Encountered error loading challenge %s: global must be an object", Logger::Severity::Warning, *path); - return false; - } - - if (settings.contains("overrides")) - { - if (!settings["overrides"].is_array()) - { - Logf("Encountered error loading challenge %s: overrides must be an array", Logger::Severity::Warning, *path); - return false; - } - for (auto& el : settings["overrides"].items()) - { - if (!el.value().is_object()) - { - Logf("Encountered error loading challenge %s: Override entries must be objects", Logger::Severity::Warning, *path); - return false; - } - } - } - return true; - + if (settings.is_discarded() || !settings.is_object()) + return false; + + if (!settings.contains("title") || !settings["title"].is_string()) + { + Logf("Encountered error loading challenge %s: missing or invalid title", Logger::Severity::Warning, *path); + return false; + } + if (!settings.contains("level") || !settings["level"].is_number_integer()) + { + Logf("Encountered error loading challenge %s: missing or invalid level", Logger::Severity::Warning, *path); + return false; + } + + if (!settings.contains("charts") || !settings["charts"].is_array()) + { + Logf("Encountered error loading challenge %s: missing or invalid chart array", Logger::Severity::Warning, *path); + return false; + } + if (settings["charts"].size() == 0) + { + Logf("Encountered error loading challenge %s: Must have at least one chart", Logger::Severity::Warning, *path); + return false; + } + for (auto& el : settings["charts"].items()) + { + if (el.value().is_string()) + { + } + else if (el.value().is_object()) + { + const auto& v = el.value(); + if (!v.contains("name") || !v["name"].is_string()) + { + Logf("Encountered error loading challenge %s: Chart entry `%s`: \"name\" must be a string", Logger::Severity::Warning, *path, v.dump().c_str()); + return false; + } + if (!v.contains("level") || !v["level"].is_number_integer()) + { + Logf("Encountered error loading challenge %s: Chart entry `%s`: \"level\" must be an integer", Logger::Severity::Warning, *path, v.dump().c_str()); + return false; + } + } + else + { + Logf("Encountered error loading challenge %s: Chart entry `%s`: must be string for hash/path or object for name+level", Logger::Severity::Warning, *path, el.value().dump().c_str()); + return false; + } + } + + if (settings.contains("global") && !settings["global"].is_object()) + { + Logf("Encountered error loading challenge %s: global must be an object", Logger::Severity::Warning, *path); + return false; + } + + if (settings.contains("overrides")) + { + if (!settings["overrides"].is_array()) + { + Logf("Encountered error loading challenge %s: overrides must be an array", Logger::Severity::Warning, *path); + return false; + } + for (auto& el : settings["overrides"].items()) + { + if (!el.value().is_object()) + { + Logf("Encountered error loading challenge %s: Override entries must be objects", Logger::Severity::Warning, *path); + return false; + } + } + } + return true; } diff --git a/Beatmap/src/Database.cpp b/Beatmap/src/Database.cpp index 69404966b..3511455ef 100644 --- a/Beatmap/src/Database.cpp +++ b/Beatmap/src/Database.cpp @@ -4,179 +4,180 @@ DBStatement::DBStatement(const String& statement, Database* db) : m_db(*db) { - m_queryResult = 0; - m_compileResult = sqlite3_prepare(m_db.db, *statement, (int)statement.size()+1, &m_stmt, nullptr); - if(m_compileResult != SQLITE_OK) - { - Logf("Failed to compile statement:\n%s\n-> %s", Logger::Severity::Error, statement, sqlite3_errmsg(m_db.db)); - } + m_queryResult = 0; + m_compileResult = sqlite3_prepare(m_db.db, *statement, (int)statement.size() + 1, &m_stmt, nullptr); + if (m_compileResult != SQLITE_OK) + { + Logf("Failed to compile statement:\n%s\n-> %s", Logger::Severity::Error, statement, sqlite3_errmsg(m_db.db)); + } } DBStatement::DBStatement(DBStatement&& other) : m_db(other.m_db) { - m_stmt = other.m_stmt; - m_compileResult = other.m_compileResult; - m_queryResult = other.m_queryResult; - other.m_stmt = nullptr; + m_stmt = other.m_stmt; + m_compileResult = other.m_compileResult; + m_queryResult = other.m_queryResult; + other.m_stmt = nullptr; } DBStatement::~DBStatement() { - Finish(); + Finish(); } bool DBStatement::Step() { - assert(m_stmt); - m_queryResult = sqlite3_step(m_stmt); - bool res = m_queryResult >= SQLITE_ROW; // Row or Done - if(m_queryResult < SQLITE_ROW) - { - Logf("Query Failed -> %s", Logger::Severity::Warning, sqlite3_errmsg(m_db.db)); - } - return res; + assert(m_stmt); + m_queryResult = sqlite3_step(m_stmt); + bool res = m_queryResult >= SQLITE_ROW; // Row or Done + if (m_queryResult < SQLITE_ROW) + { + Logf("Query Failed -> %s", Logger::Severity::Warning, sqlite3_errmsg(m_db.db)); + } + return res; } bool DBStatement::StepRow() { - assert(m_stmt); - m_queryResult = sqlite3_step(m_stmt); - bool res = m_queryResult == SQLITE_ROW; // Row only - if(m_queryResult < SQLITE_ROW) - { - Logf("Query Failed -> %s", Logger::Severity::Warning, sqlite3_errmsg(m_db.db)); - } - return res; + assert(m_stmt); + m_queryResult = sqlite3_step(m_stmt); + bool res = m_queryResult == SQLITE_ROW; // Row only + if (m_queryResult < SQLITE_ROW) + { + Logf("Query Failed -> %s", Logger::Severity::Warning, sqlite3_errmsg(m_db.db)); + } + return res; } void DBStatement::Rewind() { - sqlite3_reset(m_stmt); + sqlite3_reset(m_stmt); } void DBStatement::Finish() { - if(m_stmt) - { - sqlite3_finalize(m_stmt); - m_stmt = nullptr; - } + if (m_stmt) + { + sqlite3_finalize(m_stmt); + m_stmt = nullptr; + } } int32 DBStatement::IntColumn(int32 index) const { - assert(m_stmt && m_queryResult == SQLITE_ROW); - return sqlite3_column_int(m_stmt, index); + assert(m_stmt && m_queryResult == SQLITE_ROW); + return sqlite3_column_int(m_stmt, index); } int64 DBStatement::Int64Column(int32 index /*= 0*/) const { - assert(m_stmt && m_queryResult == SQLITE_ROW); - return sqlite3_column_int64(m_stmt, index); + assert(m_stmt && m_queryResult == SQLITE_ROW); + return sqlite3_column_int64(m_stmt, index); } double DBStatement::DoubleColumn(int32 index) const { - assert(m_stmt && m_queryResult == SQLITE_ROW); - return sqlite3_column_double(m_stmt, index); + assert(m_stmt && m_queryResult == SQLITE_ROW); + return sqlite3_column_double(m_stmt, index); } String DBStatement::StringColumn(int32 index /*= 0*/) const { - assert(m_stmt && m_queryResult == SQLITE_ROW); - return String((char*)sqlite3_column_text(m_stmt, index)); + assert(m_stmt && m_queryResult == SQLITE_ROW); + return String((char*)sqlite3_column_text(m_stmt, index)); } String DBStatement::StringColumnEmptyOnNull(int32 index /*= 0*/) const { - assert(m_stmt && m_queryResult == SQLITE_ROW); - if (sqlite3_column_type(m_stmt, index) == SQLITE_NULL) { - return ""; - } - return String((char*)sqlite3_column_text(m_stmt, index)); + assert(m_stmt && m_queryResult == SQLITE_ROW); + if (sqlite3_column_type(m_stmt, index) == SQLITE_NULL) + { + return ""; + } + return String((char*)sqlite3_column_text(m_stmt, index)); } Buffer DBStatement::BlobColumn(int32 index /*= 0*/) const { - assert(m_stmt && m_queryResult == SQLITE_ROW); - int32 blobLen = sqlite3_column_bytes(m_stmt, index); - uint8* data = (uint8*)sqlite3_column_blob(m_stmt, index); - return Buffer(data, data + blobLen); + assert(m_stmt && m_queryResult == SQLITE_ROW); + int32 blobLen = sqlite3_column_bytes(m_stmt, index); + uint8* data = (uint8*)sqlite3_column_blob(m_stmt, index); + return Buffer(data, data + blobLen); } void DBStatement::BindInt(int32 index, const int32& value) { - assert(m_stmt); - sqlite3_bind_int(m_stmt, index, value); + assert(m_stmt); + sqlite3_bind_int(m_stmt, index, value); } void DBStatement::BindDouble(int32 index, const double& value) { - assert(m_stmt); - sqlite3_bind_double(m_stmt, index, value); + assert(m_stmt); + sqlite3_bind_double(m_stmt, index, value); } void DBStatement::BindInt64(int32 index, const int64& value) { - assert(m_stmt); - sqlite3_bind_int64(m_stmt, index, value); + assert(m_stmt); + sqlite3_bind_int64(m_stmt, index, value); } static void FreeData(char* data) { - delete[] data; + delete[] data; } void DBStatement::BindString(int32 index, const String& value) { - assert(m_stmt); - char* copy = new char[value.size()]; - memcpy(copy, *value, value.size()); - sqlite3_bind_text(m_stmt, index, copy, (int32)value.size(), (void(*)(void*))&FreeData); + assert(m_stmt); + char* copy = new char[value.size()]; + memcpy(copy, *value, value.size()); + sqlite3_bind_text(m_stmt, index, copy, (int32)value.size(), (void (*)(void*)) & FreeData); } void DBStatement::BindBlob(int32 index, const Buffer& value) { - assert(m_stmt); - char* copy = new char[value.size()]; - memcpy(copy, value.data(), value.size()); - sqlite3_bind_blob(m_stmt, index, copy, (int32)value.size(), (void(*)(void*))&FreeData); + assert(m_stmt); + char* copy = new char[value.size()]; + memcpy(copy, value.data(), value.size()); + sqlite3_bind_blob(m_stmt, index, copy, (int32)value.size(), (void (*)(void*)) & FreeData); } int32 DBStatement::ColumnCount() const { - assert(m_stmt && m_queryResult == SQLITE_ROW); - return sqlite3_column_count(m_stmt); + assert(m_stmt && m_queryResult == SQLITE_ROW); + return sqlite3_column_count(m_stmt); } DBStatement::operator bool() { - return m_stmt != nullptr; + return m_stmt != nullptr; } Database::~Database() { - Close(); + Close(); } void Database::Close() { - if(db) - { - sqlite3_close(db); - } - db = nullptr; + if (db) + { + sqlite3_close(db); + } + db = nullptr; } bool Database::Open(const String& path) { - Close(); - int32 r = sqlite3_open(*path, &db); - if(r != 0) - { - return false; - } - return true; + Close(); + int32 r = sqlite3_open(*path, &db); + if (r != 0) + { + return false; + } + return true; } DBStatement Database::Query(const String& queryString) { - DBStatement statement(queryString, this); - return std::move(statement); + DBStatement statement(queryString, this); + return std::move(statement); } bool Database::Exec(const String& queryString) { - DBStatement stmt = Query(queryString); - if(!stmt) - return false; - return stmt.Step(); + DBStatement stmt = Query(queryString); + if (!stmt) + return false; + return stmt.Step(); } bool Database::ExecDirect(const String& queryString) { - char* err; - if(sqlite3_exec(db, *queryString, nullptr, nullptr, &err) != SQLITE_OK) - { - Logf("sqlite3_exec failed -> %s", Logger::Severity::Error, err); - return false; - } - return true; + char* err; + if (sqlite3_exec(db, *queryString, nullptr, nullptr, &err) != SQLITE_OK) + { + Logf("sqlite3_exec failed -> %s", Logger::Severity::Error, err); + return false; + } + return true; } diff --git a/Beatmap/src/EffectTimeline.cpp b/Beatmap/src/EffectTimeline.cpp index a3f98ffc8..e27235188 100644 --- a/Beatmap/src/EffectTimeline.cpp +++ b/Beatmap/src/EffectTimeline.cpp @@ -1,2 +1,2 @@ #include "stdafx.h" -#include "EffectTimeline.hpp" \ No newline at end of file +#include "EffectTimeline.hpp" diff --git a/Beatmap/src/KShootMap.cpp b/Beatmap/src/KShootMap.cpp index 777424779..023d5d331 100644 --- a/Beatmap/src/KShootMap.cpp +++ b/Beatmap/src/KShootMap.cpp @@ -7,13 +7,13 @@ String KShootTick::ToString() const { - return Sprintf("%s|%s|%s", *buttons, *fx, *laser); + return Sprintf("%s|%s|%s", *buttons, *fx, *laser); } void KShootTick::Clear() { - buttons = "0000"; - fx = "00"; - laser = "--"; + buttons = "0000"; + fx = "00"; + laser = "--"; } KShootTime::KShootTime() : block(-1), tick(-1) @@ -23,387 +23,386 @@ KShootTime::KShootTime(uint32_t block, uint32_t tick) : block(block), tick(tick) { } KShootTime::operator bool() const -{ - return block != -1; +{ + return block != -1; } KShootMap::TickIterator::TickIterator(KShootMap& map, KShootTime start /*= KShootTime(0, 0)*/) : m_time(start), m_map(map) { - if(!m_map.GetBlock(m_time, m_currentBlock)) - m_currentBlock = nullptr; + if (!m_map.GetBlock(m_time, m_currentBlock)) + m_currentBlock = nullptr; } KShootMap::TickIterator& KShootMap::TickIterator::operator++() { - m_time.tick++; - if(m_time.tick >= m_currentBlock->ticks.size()) - { - m_time.tick = 0; - m_time.block++; - if(!m_map.GetBlock(m_time, m_currentBlock)) - m_currentBlock = nullptr; - } - return *this; + m_time.tick++; + if (m_time.tick >= m_currentBlock->ticks.size()) + { + m_time.tick = 0; + m_time.block++; + if (!m_map.GetBlock(m_time, m_currentBlock)) + m_currentBlock = nullptr; + } + return *this; } KShootMap::TickIterator::operator bool() const { - return m_currentBlock != nullptr; + return m_currentBlock != nullptr; } KShootTick& KShootMap::TickIterator::operator*() { - return m_currentBlock->ticks[m_time.tick]; + return m_currentBlock->ticks[m_time.tick]; } KShootTick* KShootMap::TickIterator::operator->() { - return &m_currentBlock->ticks[m_time.tick]; + return &m_currentBlock->ticks[m_time.tick]; } const KShootTime& KShootMap::TickIterator::GetTime() const { - return m_time; + return m_time; } const KShootBlock& KShootMap::TickIterator::GetCurrentBlock() const { - return *m_currentBlock; + return *m_currentBlock; } bool ParseKShootCourse(BinaryStream& input, Map& settings, Vector& charts) { - StringEncoding chartEncoding = StringEncoding::Unknown; - - // Read Byte Order Mark - uint32_t bom = 0; - input.Serialize(&bom, 3); - - // If the BOM is not present, the chart might not be UTF-8. - // This is forbidden by the spec, but there are old charts which did not use UTF-8. (#314) - if (bom == 0x00bfbbef) - { - chartEncoding = StringEncoding::UTF8; - } - else - { - input.Seek(0); - } - - uint32_t lineNumber = 0; - String line; - static const String lineEnding = "\r\n"; - - // Parse header (encoding-agnostic) - while(TextStream::ReadLine(input, line, lineEnding)) - { - line.Trim(); - lineNumber++; - if(line == "--") - { - break; - } - - String k, v; - if (line.empty()) - continue; - if (line.substr(0, 2).compare("//") == 0) - continue; - if(!line.Split("=", &k, &v)) - return false; - - settings.FindOrAdd(k) = v; - } - - if (chartEncoding == StringEncoding::Unknown) - { - chartEncoding = StringEncodingDetector::Detect(input, 0, input.Tell()); - - if (chartEncoding != StringEncoding::Unknown) - Logf("Course encoding is assumed to be %s", Logger::Severity::Info, GetDisplayString(chartEncoding)); - else - Log("Course encoding couldn't be assumed. (Assuming UTF-8)", Logger::Severity::Warning); - - } - if (chartEncoding != StringEncoding::Unknown) - { - for (auto& it : settings) - { - const String& value = it.second; - if (value.empty()) continue; - - it.second = StringEncodingConverter::ToUTF8(chartEncoding, value); - } - } - - while (TextStream::ReadLine(input, line, lineEnding)) - { - line.Trim(); - lineNumber++; - if (line.empty() || line[0] != '[') - continue; - - line.TrimFront('['); - line.TrimBack(']'); - if (line.empty()) - { - Logf("Empty course chart found on line %u", Logger::Severity::Warning, lineNumber); - return false; - } - - line = Path::Normalize(line); - charts.push_back(line); - } - return true; + StringEncoding chartEncoding = StringEncoding::Unknown; + + // Read Byte Order Mark + uint32_t bom = 0; + input.Serialize(&bom, 3); + + // If the BOM is not present, the chart might not be UTF-8. + // This is forbidden by the spec, but there are old charts which did not use UTF-8. (#314) + if (bom == 0x00bfbbef) + { + chartEncoding = StringEncoding::UTF8; + } + else + { + input.Seek(0); + } + + uint32_t lineNumber = 0; + String line; + static const String lineEnding = "\r\n"; + + // Parse header (encoding-agnostic) + while (TextStream::ReadLine(input, line, lineEnding)) + { + line.Trim(); + lineNumber++; + if (line == "--") + { + break; + } + + String k, v; + if (line.empty()) + continue; + if (line.substr(0, 2).compare("//") == 0) + continue; + if (!line.Split("=", &k, &v)) + return false; + + settings.FindOrAdd(k) = v; + } + + if (chartEncoding == StringEncoding::Unknown) + { + chartEncoding = StringEncodingDetector::Detect(input, 0, input.Tell()); + + if (chartEncoding != StringEncoding::Unknown) + Logf("Course encoding is assumed to be %s", Logger::Severity::Info, GetDisplayString(chartEncoding)); + else + Log("Course encoding couldn't be assumed. (Assuming UTF-8)", Logger::Severity::Warning); + } + if (chartEncoding != StringEncoding::Unknown) + { + for (auto& it : settings) + { + const String& value = it.second; + if (value.empty()) + continue; + + it.second = StringEncodingConverter::ToUTF8(chartEncoding, value); + } + } + + while (TextStream::ReadLine(input, line, lineEnding)) + { + line.Trim(); + lineNumber++; + if (line.empty() || line[0] != '[') + continue; + + line.TrimFront('['); + line.TrimBack(']'); + if (line.empty()) + { + Logf("Empty course chart found on line %u", Logger::Severity::Warning, lineNumber); + return false; + } + + line = Path::Normalize(line); + charts.push_back(line); + } + return true; } KShootMap::KShootMap() { - } KShootMap::~KShootMap() { - } bool KShootMap::Init(BinaryStream& input, bool metadataOnly) { - ProfilerScope $("Load KShootMap"); - - StringEncoding chartEncoding = StringEncoding::Unknown; - - // Read Byte Order Mark - uint32_t bom = 0; - input.Serialize(&bom, 3); - - // If the BOM is not present, the chart might not be UTF-8. - // This is forbidden by the spec, but there are old charts which did not use UTF-8. (#314) - if (bom == 0x00bfbbef) - { - chartEncoding = StringEncoding::UTF8; - } - else - { - input.Seek(0); - } - - uint32_t lineNumber = 0; - String line; - static const String lineEnding = "\r\n"; - - // Parse header (encoding-agnostic) - while(TextStream::ReadLine(input, line, lineEnding)) - { - line.Trim(); - lineNumber++; - if(line == c_sep) - { - break; - } - - String k, v; - if (line.empty()) - continue; - if (line.substr(0, 2).compare("//") == 0) - continue; - if(!line.Split("=", &k, &v)) - return false; - - settings.FindOrAdd(k) = v; - } - - if (chartEncoding == StringEncoding::Unknown) - { - chartEncoding = StringEncodingDetector::Detect(input, 0, input.Tell()); - - if (chartEncoding != StringEncoding::Unknown) - Logf("Chart encoding is assumed to be %s", Logger::Severity::Info, GetDisplayString(chartEncoding)); - else - Log("Chart encoding couldn't be assumed. (Assuming UTF-8)", Logger::Severity::Warning); - - if (chartEncoding != StringEncoding::Unknown) - { - for (auto& it : settings) - { - const String& value = it.second; - if (value.empty()) continue; - - it.second = StringEncodingConverter::ToUTF8(chartEncoding, value); - } - } - } - - if(metadataOnly) - return true; - - // Line by line parser - KShootBlock block; - KShootTick tick; - KShootTime time = KShootTime(0, 0); - while(TextStream::ReadLine(input, line, lineEnding)) - { - if(line.empty()) - { - continue; - } - - lineNumber++; - if(line == c_sep) - { - // End this block - blocks.push_back(block); - block = KShootBlock(); // Reset block - time.block++; - time.tick = 0; - } - else - { - if(line.empty()) - continue; - if (line.substr(0, 2).compare("//") == 0) - continue; - if (line.substr(0, 1).compare(";") == 0) - continue; - - String k, v; - if(line[0] == '#') - { - Vector strings = line.Explode(" ", false); - String type = strings[0]; - if(strings.size() != 3) - { - Logf("Invalid define found in ksh file @%d: %s", Logger::Severity::Warning, lineNumber, line); - continue; - } - - KShootEffectDefinition def; - def.typeName = strings[1]; - - // Split up parameters - Vector paramsString = strings[2].Explode(";"); - for(auto param : paramsString) - { - String k, v; - if(!param.Split("=", &k, &v)) - { - Logf("Invalid parameter in custom effect definition for [%s]@%d: \"%s\"", Logger::Severity::Warning, def.typeName, lineNumber, line); - continue; - } - def.parameters.Add(k, v); - } - - if(strings[0] == "#define_fx") - { - fxDefines.Add(def.typeName, def); - } - else if(strings[0] == "#define_filter") - { - filterDefines.Add(def.typeName, def); - } - else - { - Logf("Unkown define statement in ksh @%d: \"%s\"", Logger::Severity::Warning, lineNumber, line); - } - } - else if(line.Split("=", &k, &v)) - { - KShootTickSetting ts; - ts.first = k; - ts.second = v; - tick.settings.Add(ts); - } - else - { - // Parse tick content string - // The format looks like: - // buttons*4|fx buttons*2|lasers*2 + additional things? - // (fx) buttons are either '1' for normal '2' for hold, '0' for nothing - // - // lasers use a char to indicate position from left to right ASCII characters '0' -> 'o' respectively - // '-' means no laser, ':' indicates a linear interpolation from previous point to the last point - - line.Split("|", &tick.buttons, &tick.fx); - tick.fx.Split("|", &tick.fx, &tick.laser); - if(tick.buttons.length() != 4) - { - Logf("Invalid buttons at line %d", Logger::Severity::Error, lineNumber); - return false; - } - if(tick.fx.length() != 2) - { - Logf("Invalid FX buttons at line %d", Logger::Severity::Error, lineNumber); - return false; - } - if(tick.laser.length() < 2) - { - Logf("Invalid lasers at line %d", Logger::Severity::Error, lineNumber); - return false; - } - if(tick.laser.length() > 2) - { - tick.add = tick.laser.substr(2); - tick.laser = tick.laser.substr(0, 2); - } - - block.ticks.push_back(tick); - tick = KShootTick(); // Reset tick - time.tick++; - } - } - } - - return true; + ProfilerScope $("Load KShootMap"); + + StringEncoding chartEncoding = StringEncoding::Unknown; + + // Read Byte Order Mark + uint32_t bom = 0; + input.Serialize(&bom, 3); + + // If the BOM is not present, the chart might not be UTF-8. + // This is forbidden by the spec, but there are old charts which did not use UTF-8. (#314) + if (bom == 0x00bfbbef) + { + chartEncoding = StringEncoding::UTF8; + } + else + { + input.Seek(0); + } + + uint32_t lineNumber = 0; + String line; + static const String lineEnding = "\r\n"; + + // Parse header (encoding-agnostic) + while (TextStream::ReadLine(input, line, lineEnding)) + { + line.Trim(); + lineNumber++; + if (line == c_sep) + { + break; + } + + String k, v; + if (line.empty()) + continue; + if (line.substr(0, 2).compare("//") == 0) + continue; + if (!line.Split("=", &k, &v)) + return false; + + settings.FindOrAdd(k) = v; + } + + if (chartEncoding == StringEncoding::Unknown) + { + chartEncoding = StringEncodingDetector::Detect(input, 0, input.Tell()); + + if (chartEncoding != StringEncoding::Unknown) + Logf("Chart encoding is assumed to be %s", Logger::Severity::Info, GetDisplayString(chartEncoding)); + else + Log("Chart encoding couldn't be assumed. (Assuming UTF-8)", Logger::Severity::Warning); + + if (chartEncoding != StringEncoding::Unknown) + { + for (auto& it : settings) + { + const String& value = it.second; + if (value.empty()) + continue; + + it.second = StringEncodingConverter::ToUTF8(chartEncoding, value); + } + } + } + + if (metadataOnly) + return true; + + // Line by line parser + KShootBlock block; + KShootTick tick; + KShootTime time = KShootTime(0, 0); + while (TextStream::ReadLine(input, line, lineEnding)) + { + if (line.empty()) + { + continue; + } + + lineNumber++; + if (line == c_sep) + { + // End this block + blocks.push_back(block); + block = KShootBlock(); // Reset block + time.block++; + time.tick = 0; + } + else + { + if (line.empty()) + continue; + if (line.substr(0, 2).compare("//") == 0) + continue; + if (line.substr(0, 1).compare(";") == 0) + continue; + + String k, v; + if (line[0] == '#') + { + Vector strings = line.Explode(" ", false); + String type = strings[0]; + if (strings.size() != 3) + { + Logf("Invalid define found in ksh file @%d: %s", Logger::Severity::Warning, lineNumber, line); + continue; + } + + KShootEffectDefinition def; + def.typeName = strings[1]; + + // Split up parameters + Vector paramsString = strings[2].Explode(";"); + for (auto param : paramsString) + { + String k, v; + if (!param.Split("=", &k, &v)) + { + Logf("Invalid parameter in custom effect definition for [%s]@%d: \"%s\"", Logger::Severity::Warning, def.typeName, lineNumber, line); + continue; + } + def.parameters.Add(k, v); + } + + if (strings[0] == "#define_fx") + { + fxDefines.Add(def.typeName, def); + } + else if (strings[0] == "#define_filter") + { + filterDefines.Add(def.typeName, def); + } + else + { + Logf("Unkown define statement in ksh @%d: \"%s\"", Logger::Severity::Warning, lineNumber, line); + } + } + else if (line.Split("=", &k, &v)) + { + KShootTickSetting ts; + ts.first = k; + ts.second = v; + tick.settings.Add(ts); + } + else + { + // Parse tick content string + // The format looks like: + // buttons*4|fx buttons*2|lasers*2 + additional things? + // (fx) buttons are either '1' for normal '2' for hold, '0' for nothing + // + // lasers use a char to indicate position from left to right ASCII characters '0' -> 'o' respectively + // '-' means no laser, ':' indicates a linear interpolation from previous point to the last point + + line.Split("|", &tick.buttons, &tick.fx); + tick.fx.Split("|", &tick.fx, &tick.laser); + if (tick.buttons.length() != 4) + { + Logf("Invalid buttons at line %d", Logger::Severity::Error, lineNumber); + return false; + } + if (tick.fx.length() != 2) + { + Logf("Invalid FX buttons at line %d", Logger::Severity::Error, lineNumber); + return false; + } + if (tick.laser.length() < 2) + { + Logf("Invalid lasers at line %d", Logger::Severity::Error, lineNumber); + return false; + } + if (tick.laser.length() > 2) + { + tick.add = tick.laser.substr(2); + tick.laser = tick.laser.substr(0, 2); + } + + block.ticks.push_back(tick); + tick = KShootTick(); // Reset tick + time.tick++; + } + } + } + + return true; } bool KShootMap::GetBlock(const KShootTime& time, KShootBlock*& tickOut) { - if(!time) - return false; - if(time.block >= blocks.size()) - return false; - tickOut = &blocks[time.block]; - return tickOut->ticks.size() > 0; + if (!time) + return false; + if (time.block >= blocks.size()) + return false; + tickOut = &blocks[time.block]; + return tickOut->ticks.size() > 0; } bool KShootMap::GetTick(const KShootTime& time, KShootTick*& tickOut) { - if(!time) - return false; - if(time.block >= blocks.size()) - return false; - KShootBlock& b = blocks[time.block]; - if(time.tick >= b.ticks.size() || time.tick < 0) - return false; - tickOut = &b.ticks[time.tick]; - return true; + if (!time) + return false; + if (time.block >= blocks.size()) + return false; + KShootBlock& b = blocks[time.block]; + if (time.tick >= b.ticks.size() || time.tick < 0) + return false; + tickOut = &b.ticks[time.tick]; + return true; } float KShootMap::TimeToFloat(const KShootTime& time) const { - KShootBlock* block; - if(!const_cast(this)->GetBlock(time, block)) - return -1.0f; - float seg = (float)time.tick / (float)block->ticks.size(); - return (float)time.block + seg; + KShootBlock* block; + if (!const_cast(this)->GetBlock(time, block)) + return -1.0f; + float seg = (float)time.tick / (float)block->ticks.size(); + return (float)time.block + seg; } float KShootMap::TranslateLaserChar(char c) const { - class LaserCharacters : public Map - { - public: - LaserCharacters() - { - uint32 numChars = 0; - auto AddRange = [&](char start, char end) - { - for(char c = start; c <= end; c++) - { - Add(c, numChars++); - } - }; - AddRange('0', '9'); - AddRange('A', 'Z'); - AddRange('a', 'o'); - } - }; - static LaserCharacters laserCharacters; - - uint32* index = laserCharacters.Find(c); - if(!index) - { - Logf("Invalid laser control point '%c'", Logger::Severity::Warning, c); - return 0.0f; - } - return (float)index[0] / (float)(laserCharacters.size()-1); + class LaserCharacters : public Map + { + public: + LaserCharacters() + { + uint32 numChars = 0; + auto AddRange = [&](char start, char end) + { + for (char c = start; c <= end; c++) + { + Add(c, numChars++); + } + }; + AddRange('0', '9'); + AddRange('A', 'Z'); + AddRange('a', 'o'); + } + }; + static LaserCharacters laserCharacters; + + uint32* index = laserCharacters.Find(c); + if (!index) + { + Logf("Invalid laser control point '%c'", Logger::Severity::Warning, c); + return 0.0f; + } + return (float)index[0] / (float)(laserCharacters.size() - 1); } -const char* KShootMap::c_sep = "--"; \ No newline at end of file +const char* KShootMap::c_sep = "--"; diff --git a/Beatmap/src/LineGraph.cpp b/Beatmap/src/LineGraph.cpp index be6f4baa6..0635acedf 100644 --- a/Beatmap/src/LineGraph.cpp +++ b/Beatmap/src/LineGraph.cpp @@ -8,7 +8,7 @@ void LineGraph::Insert(MapTime mapTime, double point) auto it = m_points.find(mapTime); if (it == m_points.end()) { - m_points.emplace_hint(it, mapTime, Point{point}); + m_points.emplace_hint(it, mapTime, Point{ point }); } else { @@ -38,18 +38,23 @@ void LineGraph::Insert(MapTime mapTime, const std::string& point) { Insert(mapTime, std::stod(point)); } - catch (const std::invalid_argument&) {} - catch (const std::out_of_range&) {} + catch (const std::invalid_argument&) + { + } + catch (const std::out_of_range&) + { + } } else { - Insert(mapTime, LineGraph::Point{std::stod(point.substr(semicolonIdx + 1)), std::stod(point.substr(semicolonIdx + 1))}); + Insert(mapTime, LineGraph::Point{ std::stod(point.substr(semicolonIdx + 1)), std::stod(point.substr(semicolonIdx + 1)) }); } } void LineGraph::RangeSet(MapTime begin, MapTime end, double value) { - if (begin >= end) return; + if (begin >= end) + return; const double beginValue = ValueAt(begin); const double endValue = ValueAt(end); @@ -57,15 +62,17 @@ void LineGraph::RangeSet(MapTime begin, MapTime end, double value) const auto beginIt = m_points.lower_bound(begin); const auto endIt = m_points.upper_bound(end); - for (auto it = beginIt; it != endIt; it = m_points.erase(it)); + for (auto it = beginIt; it != endIt; it = m_points.erase(it)) + ; - Insert(begin, LineGraph::Point{beginValue, value}); - Insert(end, LineGraph::Point{value, endValue}); + Insert(begin, LineGraph::Point{ beginValue, value }); + Insert(end, LineGraph::Point{ value, endValue }); } void LineGraph::RangeAdd(MapTime begin, MapTime end, double delta) { - if (begin >= end) return; + if (begin >= end) + return; const double beginValue = ValueAt(begin); const double endValue = ValueAt(end); @@ -79,14 +86,15 @@ void LineGraph::RangeAdd(MapTime begin, MapTime end, double delta) it->second.value.second += delta; } - Insert(begin, LineGraph::Point{beginValue, beginValue + delta}); + Insert(begin, LineGraph::Point{ beginValue, beginValue + delta }); if (endIt != m_points.end() && endIt->first == end) { endIt->second.value.first += delta; - } else + } + else { - Insert(end, LineGraph::Point{endValue + delta, endValue}); + Insert(end, LineGraph::Point{ endValue + delta, endValue }); } } @@ -304,4 +312,4 @@ double LineGraph::ValueAt(MapTime mapTime) const } return Math::Lerp(firstValue, secondValue, (mapTime - firstTime) / static_cast(secondTime - firstTime)); -} \ No newline at end of file +} diff --git a/Beatmap/src/MapDatabase.cpp b/Beatmap/src/MapDatabase.cpp index 1d104591e..78236d79b 100644 --- a/Beatmap/src/MapDatabase.cpp +++ b/Beatmap/src/MapDatabase.cpp @@ -14,2392 +14,2383 @@ #include #include #include -using std::thread; using std::mutex; +using std::thread; using namespace std; class MapDatabase_Impl { public: - // For calling delegates - MapDatabase& m_outer; - - thread m_thread; - condition_variable m_cvPause; - mutex m_pauseMutex; - std::atomic m_paused; - bool m_searching = false; - bool m_interruptSearch = false; - Set m_searchPaths; - Database m_database; - - Map m_folders; - Map m_charts; - Map m_challenges; - Map m_practiceSetups; - - Map m_chartsByHash; - Map m_foldersByPath; - Multimap m_practiceSetupsByChartId; - - int32 m_nextFolderId = 1; - int32 m_nextChartId = 1; - int32 m_nextChalId = 1; - String m_sortField = "title"; - bool m_transferScores = true; - - struct SearchState - { - struct ExistingFileEntry - { - int32 id; - uint64 lwt; - }; - // Maps file paths to the id's and last write time's for difficulties already in the database - Map difficulties; - Map challenges; - } m_searchState; - - // Represents an event produced from a scan - // a difficulty can be removed/added/updated - // a BeatmapSettings structure will be provided for added/updated events - struct Event - { - enum Type{ - Chart, - Challenge - }; - Type type; - enum Action - { - Added, - Removed, - Updated - }; - Action action; - String path; - // Current lwt of file - uint64 lwt; - // Id of the map - int32 id; - // Scanned map data, for added/updated maps - BeatmapSettings* mapData = nullptr; - nlohmann::json json; - String hash; - }; - List m_pendingChanges; - mutex m_pendingChangesLock; - - static const int32 m_version = 20; + // For calling delegates + MapDatabase& m_outer; + + thread m_thread; + condition_variable m_cvPause; + mutex m_pauseMutex; + std::atomic m_paused; + bool m_searching = false; + bool m_interruptSearch = false; + Set m_searchPaths; + Database m_database; + + Map m_folders; + Map m_charts; + Map m_challenges; + Map m_practiceSetups; + + Map m_chartsByHash; + Map m_foldersByPath; + Multimap m_practiceSetupsByChartId; + + int32 m_nextFolderId = 1; + int32 m_nextChartId = 1; + int32 m_nextChalId = 1; + String m_sortField = "title"; + bool m_transferScores = true; + + struct SearchState + { + struct ExistingFileEntry + { + int32 id; + uint64 lwt; + }; + // Maps file paths to the id's and last write time's for difficulties already in the database + Map difficulties; + Map challenges; + } m_searchState; + + // Represents an event produced from a scan + // a difficulty can be removed/added/updated + // a BeatmapSettings structure will be provided for added/updated events + struct Event + { + enum Type + { + Chart, + Challenge + }; + Type type; + enum Action + { + Added, + Removed, + Updated + }; + Action action; + String path; + // Current lwt of file + uint64 lwt; + // Id of the map + int32 id; + // Scanned map data, for added/updated maps + BeatmapSettings* mapData = nullptr; + nlohmann::json json; + String hash; + }; + List m_pendingChanges; + mutex m_pendingChangesLock; + + static const int32 m_version = 20; public: - MapDatabase_Impl(MapDatabase& outer, bool transferScores) : m_outer(outer) - { - m_transferScores = transferScores; - String databasePath = Path::Absolute("maps.db"); - if(!m_database.Open(databasePath)) - { - Logf("Failed to open database [%s]", Logger::Severity::Warning, databasePath); - assert(false); - } - m_paused.store(false); - bool rebuild = false; - bool update = false; - DBStatement versionQuery = m_database.Query("SELECT version FROM `Database`"); - int32 gotVersion = 0; - if(versionQuery && versionQuery.Step()) - { - gotVersion = versionQuery.IntColumn(0); - if(gotVersion != m_version) - { - update = true; - } - } - else - { - // Create DB - m_database.Exec("DROP TABLE IF EXISTS Database"); - m_database.Exec("CREATE TABLE Database(version INTEGER)"); - m_database.Exec(Utility::Sprintf("INSERT OR REPLACE INTO Database(rowid, version) VALUES(1, %d)", m_version)); - rebuild = true; - } - versionQuery.Finish(); - - if(rebuild) - { - m_CreateTables(); - - // Update database version - m_database.Exec(Utility::Sprintf("UPDATE Database SET `version`=%d WHERE `rowid`=1", m_version)); - } - else if (update) - { - ProfilerScope $(Utility::Sprintf("Upgrading db (%d -> %d)", gotVersion, m_version)); - - //back up old db file - Path::Copy(Path::Absolute("maps.db"), Path::Absolute("maps.db_" + Shared::Time::Now().ToString() + ".bak")); - - m_outer.OnDatabaseUpdateStarted.Call(1); - - - ///TODO: Make loop for doing iterative upgrades - if (gotVersion == 8) //upgrade from 8 to 9 - { - m_database.Exec("ALTER TABLE Scores ADD COLUMN hitstats BLOB"); - gotVersion = 9; - } - if (gotVersion == 9) //upgrade from 9 to 10 - { - m_database.Exec("ALTER TABLE Scores ADD COLUMN timestamp INTEGER"); - gotVersion = 10; - } - if (gotVersion == 10) //upgrade from 10 to 11 - { - m_database.Exec("ALTER TABLE Difficulties ADD COLUMN hash TEXT"); - gotVersion = 11; - } - if (gotVersion == 11) //upgrade from 11 to 12 - { - m_database.Exec("CREATE TABLE Collections" - "(collection TEXT, mapid INTEGER, " - "UNIQUE(collection,mapid), " - "FOREIGN KEY(mapid) REFERENCES Maps(rowid))"); - gotVersion = 12; - } - if (gotVersion == 12) //upgrade from 12 to 13 - { - - int diffCount = 1; - { - // Do in its own scope so that it will destruct before we modify anything else - DBStatement diffCountStmt = m_database.Query("SELECT COUNT(rowid) FROM Difficulties"); - if (diffCountStmt.StepRow()) - { - diffCount = diffCountStmt.IntColumn(0); - } - } - m_outer.OnDatabaseUpdateProgress.Call(0, diffCount); - - int progress = 0; - int totalScoreCount = 0; - - DBStatement diffScan = m_database.Query("SELECT rowid,path FROM Difficulties"); - - Vector scoresToAdd; - while (diffScan.StepRow()) - { - if (progress % 16 == 0) - m_outer.OnDatabaseUpdateProgress.Call(progress, diffCount); - progress++; - - bool noScores = true; - int diffid = diffScan.IntColumn(0); - String diffpath = diffScan.StringColumn(1); - DBStatement scoreCount = m_database.Query("SELECT COUNT(*) FROM scores WHERE diffid=?"); - scoreCount.BindInt(1, diffid); - if (scoreCount.StepRow()) - { - noScores = scoreCount.IntColumn(0) == 0; - } - - if (noScores) - { - continue; - } - - String hash; - File diffFile; - if (diffFile.OpenRead(diffpath)) - { - char data_buffer[0x80]; - uint32_t digest[5]; - sha1::SHA1 s; - - size_t amount_read = 0; - size_t read_size; - do - { - read_size = diffFile.Read(data_buffer, sizeof(data_buffer)); - amount_read += read_size; - s.processBytes(data_buffer, read_size); - } while (read_size != 0); - - s.getDigest(digest); - hash = Utility::Sprintf("%08x%08x%08x%08x%08x", digest[0], digest[1], digest[2], digest[3], digest[4]); - } - else { - Logf("Could not open chart file at \"%s\" scores will be lost.", Logger::Severity::Warning, diffpath); - continue; - } - - - DBStatement scoreScan = m_database.Query("SELECT rowid,score,crit,near,miss,gauge,gameflags,hitstats,timestamp,diffid FROM Scores WHERE diffid=?"); - scoreScan.BindInt(1, diffid); - while (scoreScan.StepRow()) - { - ScoreIndex score; - score.id = scoreScan.IntColumn(0); - score.score = scoreScan.IntColumn(1); - score.crit = scoreScan.IntColumn(2); - score.almost = scoreScan.IntColumn(3); - score.miss = scoreScan.IntColumn(4); - score.gauge = (float) scoreScan.DoubleColumn(5); - score.gaugeOption = scoreScan.IntColumn(6); - Buffer hitstats = scoreScan.BlobColumn(7); - score.timestamp = scoreScan.Int64Column(8); - auto timestamp = Shared::Time(score.timestamp); - score.chartHash = hash; - score.replayPath = Path::Normalize(Path::Absolute("replays/" + hash + "/" + timestamp.ToString() + ".urf")); - Path::CreateDir(Path::Absolute("replays/" + hash)); - File replayFile; - if (replayFile.OpenWrite(score.replayPath)) - { - replayFile.Write(hitstats.data(), hitstats.size()); - } - else - { - Logf("Could not open replay file at \"%s\" replay data will be lost.", Logger::Severity::Warning, score.replayPath); - } - scoresToAdd.Add(score); - totalScoreCount++; - } - - } - - progress = 0; - - m_database.Exec("DROP TABLE IF EXISTS Maps"); - m_database.Exec("DROP TABLE IF EXISTS Difficulties"); - m_CreateTables(); - - DBStatement addScore = m_database.Query("INSERT INTO Scores(score,crit,near,miss,gauge,gameflags,replay,timestamp,chart_hash) VALUES(?,?,?,?,?,?,?,?,?)"); - - m_database.Exec("BEGIN"); - for (ScoreIndex& score : scoresToAdd) - { - addScore.BindInt(1, score.score); - addScore.BindInt(2, score.crit); - addScore.BindInt(3, score.almost); - addScore.BindInt(4, score.miss); - addScore.BindDouble(5, score.gauge); - addScore.BindInt(6, score.gaugeOption); - addScore.BindString(7, score.replayPath); - addScore.BindInt64(8, score.timestamp); - addScore.BindString(9, score.chartHash); - - addScore.Step(); - addScore.Rewind(); - - if (progress % 16 == 0) - { - m_outer.OnDatabaseUpdateProgress.Call(progress, totalScoreCount); - } - progress++; - } - m_database.Exec("END"); - m_database.Exec("VACUUM"); - gotVersion = 13; - } - if (gotVersion == 13) //from 13 to 14 - { - m_database.Exec("ALTER TABLE Charts ADD COLUMN custom_offset INTEGER"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN user_name TEXT"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN user_id TEXT"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN local_score INTEGER"); - m_database.Exec("UPDATE Charts SET custom_offset=0"); - m_database.Exec("UPDATE Scores SET local_score=1"); - m_database.Exec("UPDATE Scores SET local_score=1"); - m_database.Exec("UPDATE Scores SET user_name=\"\""); - m_database.Exec("UPDATE Scores SET user_id=\"\""); - gotVersion = 14; - } - if (gotVersion == 14) - { - m_database.Exec("CREATE TABLE PracticeSetups (" - "chart_id INTEGER," - "setup_title TEXT," - "loop_success INTEGER," - "loop_fail INTEGER," - "range_begin INTEGER," - "range_end INTEGER," - "fail_cond_type INTEGER," - "fail_cond_value INTEGER," - "playback_speed REAL," - "inc_speed_on_success INTEGER," - "inc_speed REAL," - "inc_streak INTEGER," - "dec_speed_on_fail INTEGER," - "dec_speed REAL," - "min_playback_speed REAL," - "max_rewind INTEGER," - "max_rewind_measure INTEGER," - "FOREIGN KEY(chart_id) REFERENCES Charts(rowid)" - ")"); - gotVersion = 15; - } - if (gotVersion == 15) - { - m_database.Exec("ALTER TABLE Scores ADD COLUMN window_perfect INTEGER"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN window_good INTEGER"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN window_hold INTEGER"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN window_miss INTEGER"); - m_database.Exec("UPDATE Scores SET window_perfect=46"); - m_database.Exec("UPDATE Scores SET window_good=150"); - m_database.Exec("UPDATE Scores SET window_hold=150"); - m_database.Exec("UPDATE Scores SET window_miss=300"); - gotVersion = 16; - } - if (gotVersion == 16) - { - m_database.Exec("CREATE TABLE Challenges" - "(" - "title TEXT," - "charts TEXT," - "chart_meta TEXT," // used for search - "clear_mark INTEGER," - "best_score INTEGER," - "req_text TEXT," - "path TEXT," - "hash TEXT," - "level INTEGER," - "lwt INTEGER" - ")"); - gotVersion = 17; - } - if (gotVersion == 17) - { - Map optionMap; - int totalScoreCount = 0; - DBStatement scoreScan = m_database.Query("SELECT rowid,gameflags FROM Scores"); - while (scoreScan.StepRow()) - { - optionMap.Add(scoreScan.IntColumn(0), PlaybackOptions::FromFlags(scoreScan.IntColumn(1))); - totalScoreCount++; - } - - m_outer.OnDatabaseUpdateProgress.Call(0, totalScoreCount); - int progress = 0; - - //alter table. - // if we were on a newer sqlite version the gameflags column could easily be renamed but it will - // instead still exist in the db after this update but will be unused. - m_database.Exec("BEGIN"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN gauge_type INTEGER"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN auto_flags INTEGER"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN gauge_opt INTEGER"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN mirror INTEGER"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN random INTEGER"); - - DBStatement setScoreOpt = m_database.Query("UPDATE Scores set gauge_type=?, gauge_opt=?, mirror=?, random=?, auto_flags=? WHERE rowid=?"); - for (auto& o : optionMap) - { - setScoreOpt.BindInt(1, (int32)o.second.gaugeType); - setScoreOpt.BindInt(2, o.second.gaugeOption); - setScoreOpt.BindInt(3, o.second.mirror ? 1 : 0); - setScoreOpt.BindInt(4, o.second.random ? 1 : 0); - setScoreOpt.BindInt(5, (int32)o.second.autoFlags); - setScoreOpt.BindInt(6, o.first); - - setScoreOpt.StepRow(); - setScoreOpt.Rewind(); - - progress++; - m_outer.OnDatabaseUpdateProgress.Call(progress, totalScoreCount); - } - m_database.Exec("END"); - gotVersion = 18; - - } - if (gotVersion == 18) - { - m_database.Exec("ALTER TABLE Scores ADD COLUMN window_slam INTEGER"); - m_database.Exec("UPDATE Scores SET window_slam=84"); - gotVersion = 19; - } - if (gotVersion == 19) - { - m_database.Exec("ALTER TABLE Scores ADD COLUMN early INTEGER"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN late INTEGER"); - m_database.Exec("ALTER TABLE Scores ADD COLUMN combo INTEGER"); - m_database.Exec("UPDATE Scores SET early=?"); - m_database.Exec("UPDATE Scores SET late=?"); - m_database.Exec("UPDATE Scores SET combo=?"); - gotVersion = 20; - } - m_database.Exec(Utility::Sprintf("UPDATE Database SET `version`=%d WHERE `rowid`=1", m_version)); - - m_outer.OnDatabaseUpdateDone.Call(); - } - else - { - // NOTE: before we loaded the database here. This was redundant since we always do StartSearching - // Plus we don't do this when doing an update so if it was a problem to not do it here we would - // have already noticed it being broken in a launch where the db is updated - // Also with challenges we can't do this until the constructor is done so we can access the - // MapDatabase wrapper while loading challenges - //m_LoadInitialData(); - } - } - ~MapDatabase_Impl() - { - StopSearching(); - m_CleanupMapIndex(); - - //discard pending changes, probably should apply them(?) - auto changes = FlushChanges(); - for (auto& c : changes) - { - if(c.mapData) - delete c.mapData; - } - } - void LoadDatabaseWithoutSearching() - { - // Apply previous diff to prevent duplicated entry - Update(); - // Create initial data set to compare to when evaluating if a file is added/removed/updated - m_LoadInitialData(); - } - void StartSearching() - { - if(m_searching) - return; - - if(m_thread.joinable()) - m_thread.join(); - // Apply previous diff to prevent duplicated entry - Update(); - // Create initial data set to compare to when evaluating if a file is added/removed/updated - m_LoadInitialData(); - ResumeSearching(); - m_interruptSearch = false; - m_searching = true; - m_thread = thread(&MapDatabase_Impl::m_SearchThread, this); - } - void StopSearching() - { - ResumeSearching(); - m_interruptSearch = true; - m_searching = false; - if(m_thread.joinable()) - { - m_thread.join(); - } - } - void AddSearchPath(const String& path) - { - String normalizedPath = Path::Normalize(Path::Absolute(path)); - if(m_searchPaths.Contains(normalizedPath)) - return; - - m_searchPaths.Add(normalizedPath); - } - void RemoveSearchPath(const String& path) - { - String normalizedPath = Path::Normalize(Path::Absolute(path)); - if(!m_searchPaths.Contains(normalizedPath)) - return; - - m_searchPaths.erase(normalizedPath); - } - - /* Thread safe event queue functions */ - // Add a new change to the change queue - void AddChange(Event change) - { - m_pendingChangesLock.lock(); - m_pendingChanges.emplace_back(change); - m_pendingChangesLock.unlock(); - } - // Removes changes from the queue and returns them - // additionally you can specify the maximum amount of changes to remove from the queue - List FlushChanges(size_t maxChanges = -1) - { - List changes; - m_pendingChangesLock.lock(); - if(maxChanges == -1) - { - changes = std::move(m_pendingChanges); // All changes - } - else - { - for(size_t i = 0; i < maxChanges && !m_pendingChanges.empty(); i++) - { - changes.AddFront(m_pendingChanges.front()); - m_pendingChanges.pop_front(); - } - } - m_pendingChangesLock.unlock(); - return std::move(changes); - } - - // TODO(itszn) make sure this is not case sensitive - ChartIndex* FindFirstChartByPath(const String& searchString) - { - String stmt = "SELECT DISTINCT rowid FROM Charts WHERE path LIKE ? LIMIT 1"; - - DBStatement search = m_database.Query(stmt); - search.BindString(1, "%"+searchString+"%"); - while(search.StepRow()) - { - int32 id = search.IntColumn(0); - ChartIndex** chart = m_charts.Find(id); - if (!chart) - return nullptr; - return *chart; - } - - return nullptr; - } - - ChartIndex* FindFirstChartByNameAndLevel(const String& name, uint32 level, bool exact=true) - { - String stmt = "SELECT DISTINCT rowid FROM Charts WHERE title LIKE ? and level=? LIMIT 1"; - - DBStatement search = m_database.Query(stmt); - if (exact) - search.BindString(1, name); - else - search.BindString(1, "%"+name+"%"); - search.BindInt(2, level); - while(search.StepRow()) - { - int32 id = search.IntColumn(0); - ChartIndex** chart = m_charts.Find(id); - if (!chart) - return nullptr; - return *chart; - } - - // Try non exact now - if (exact) - return FindFirstChartByNameAndLevel(name, level, false); - - return nullptr; - } - - ChartIndex* FindFirstChartByHash(const String& hash) - { - ChartIndex** chart = m_chartsByHash.Find(hash); - if (!chart) - return nullptr; - return *chart; - } - - Map FindFoldersByHash(const String& hash) - { - String stmt = "SELECT DISTINCT folderId FROM Charts WHERE hash = ?"; - DBStatement search = m_database.Query(stmt); - search.BindString(1, hash); - - Map res; - while (search.StepRow()) - { - int32 id = search.IntColumn(0); - FolderIndex** folder = m_folders.Find(id); - if (folder) - { - res.Add(id, *folder); - } - } - - return res; - } - - // TODO(itszn) make this not case sensitive - Map FindFoldersByPath(const String& searchString) - { - String stmt = "SELECT DISTINCT folderId FROM Charts WHERE path LIKE ?"; - DBStatement search = m_database.Query(stmt); - search.BindString(1, "%" + searchString + "%"); - - Map res; - while(search.StepRow()) - { - int32 id = search.IntColumn(0); - FolderIndex** folder = m_folders.Find(id); - if(folder) - { - res.Add(id, *folder); - } - } - - return res; - } - - Map FindChallenges(const String& searchString) - { - WString test = Utility::ConvertToWString(searchString); - String stmt = "SELECT DISTINCT rowid FROM Challenges WHERE"; - - Vector terms = searchString.Explode(" "); - int32 i = 0; - for (auto term : terms) - { - if (i > 0) - stmt += " AND"; - stmt += String(" (title LIKE ?") + - " OR chart_meta LIKE ?" - " OR path LIKE ?)"; - i++; - } - DBStatement search = m_database.Query(stmt); - - i = 1; - for (auto term : terms) - { - // Bind all the terms - for (int j = 0; j < 3; j++) - { - search.BindString(i+j, "%" + term + "%"); - } - i+=6; - } - - Map res; - while(search.StepRow()) - { - int32 id = search.IntColumn(0); - ChallengeIndex** challenge = m_challenges.Find(id); - if(challenge) - { - res.Add(id, *challenge); - } - } - - return res; - } - - Map FindFoldersWithFilter(const String& searchString, const Vector> filters) - { - WString test = Utility::ConvertToWString(searchString); - String stmt = "SELECT DISTINCT folderId FROM Charts"; - - String conds = ""; - - Vector binds; - - if (!searchString.empty()) { - Vector terms = searchString.Explode(" "); - for (const auto& term : terms) - { - if (term.empty()) - continue; - - if (!conds.empty()) - conds += " AND"; - conds += String(" (artist LIKE ?") + - " OR title LIKE ?" + - " OR path LIKE ?" + - " OR effector LIKE ?" + - " OR artist_translit LIKE ?" + - " OR title_translit LIKE ?)"; - - for (int j = 0; j < 6; j++) - binds.push_back("%" + term + "%"); - } - } - - for (const auto& filter : filters) - { - if (filter.second.empty()) - continue; - - if(!conds.empty()) - conds += " AND"; - conds += " (" + filter.first + " == ?)"; - binds.push_back(filter.second); - } - - if (!conds.empty()) - stmt += " WHERE" + conds; - - DBStatement search = m_database.Query(stmt); - - int32 num = 1; - for (const auto& bind : binds) - { - search.BindString(num, bind); - num++; - } - - Map res; - while(search.StepRow()) - { - int32 id = search.IntColumn(0); - FolderIndex** folder = m_folders.Find(id); - if(folder) - { - res.Add(id, *folder); - } - } - - return res; - } - - Map FindFolders(const String& searchString) - { - return FindFoldersWithFilter(searchString, {}); - } - - Vector GetCollections() - { - Vector res; - DBStatement search = m_database.Query("SELECT DISTINCT collection FROM collections"); - while (search.StepRow()) - { - res.Add(search.StringColumn(0)); - } - return res; - } - - Vector GetCollectionsForMap(int32 mapid) - { - Vector res; - DBStatement search = m_database.Query(Utility::Sprintf("SELECT DISTINCT collection FROM collections WHERE folderid==%d", mapid)); - while (search.StepRow()) - { - res.Add(search.StringColumn(0)); - } - return res; - } - - Vector GetOrAddPracticeSetups(int32 chartId, const PracticeSetupIndex& defaultOptions) - { - Vector res; - - auto it = m_practiceSetupsByChartId.equal_range(chartId); - for (auto it1 = it.first; it1 != it.second; ++it1) - { - res.Add(it1->second); - } - - if (res.empty()) - { - PracticeSetupIndex* practiceSetup = new PracticeSetupIndex(defaultOptions); - practiceSetup->id = -1; - practiceSetup->chartId = chartId; - - practiceSetup->setupTitle = ""; - - UpdateOrAddPracticeSetup(practiceSetup); - res.emplace_back(practiceSetup); - } - - return res; - } - - Map FindFoldersByCollection(const String& collection) - { - String stmt = "SELECT folderid FROM Collections WHERE collection==?"; - DBStatement search = m_database.Query(stmt); - search.BindString(1, collection); - - Map res; - while (search.StepRow()) - { - int32 id = search.IntColumn(0); - FolderIndex** folder = m_folders.Find(id); - if (folder) - { - res.Add(id, *folder); - } - } - - return res; - } - - - Map FindFoldersByFolder(const String& folder) - { - char csep[2]; - csep[0] = Path::sep; - csep[1] = 0; - String sep(csep); - String stmt = "SELECT rowid FROM folders WHERE path LIKE ?"; - DBStatement search = m_database.Query(stmt); - search.BindString(1, "%" + sep + folder + sep + "%"); - - - Map res; - while (search.StepRow()) - { - int32 id = search.IntColumn(0); - FolderIndex** folder = m_folders.Find(id); - if (folder) - { - res.Add(id, *folder); - } - } - - return res; - } - // Processes pending database changes - void Update() - { - List changes = FlushChanges(); - if(changes.empty()) - return; - - DBStatement addChart = m_database.Query("INSERT INTO Charts(" - "folderId,path,title,artist,title_translit,artist_translit,jacket_path,effector,illustrator," - "diff_name,diff_shortname,bpm,diff_index,level,hash,preview_file,preview_offset,preview_length,lwt) " - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); - DBStatement addFolder = m_database.Query("INSERT INTO Folders(path,rowid) VALUES(?,?)"); - DBStatement addChallenge = m_database.Query("INSERT INTO Challenges(" - "title,charts,chart_meta,clear_mark,best_score,req_text,path,hash,level,lwt) " - "VALUES(?,?,?,?,?,?,?,?,?,?)"); - DBStatement update = m_database.Query("UPDATE Charts SET path=?,title=?,artist=?,title_translit=?,artist_translit=?,jacket_path=?,effector=?,illustrator=?," - "diff_name=?,diff_shortname=?,bpm=?,diff_index=?,level=?,hash=?,preview_file=?,preview_offset=?,preview_length=?,lwt=? WHERE rowid=?"); //TODO: update - DBStatement updateChallenge = m_database.Query("UPDATE Challenges SET title=?,charts=?,chart_meta=?,clear_mark=?,best_score=?,req_text=?,path=?,hash=?,level=?,lwt=? WHERE rowid=?"); - DBStatement removeChart = m_database.Query("DELETE FROM Charts WHERE rowid=?"); - DBStatement removeChallenge = m_database.Query("DELETE FROM Challenges WHERE rowid=?"); - DBStatement removeFolder = m_database.Query("DELETE FROM Folders WHERE rowid=?"); - DBStatement scoreScan = m_database.Query("SELECT " - "rowid,score,crit,near,early,late,combo,miss,gauge,auto_flags,replay,timestamp,chart_hash,user_name,user_id,local_score,window_perfect,window_good,window_hold,window_miss,window_slam,gauge_type,gauge_opt,mirror,random " - "FROM Scores WHERE chart_hash=?"); - DBStatement moveScores = m_database.Query("UPDATE Scores set chart_hash=? where chart_hash=?"); - - Set addedChartEvents; - Set removeChartEvents; - Set updatedChartEvents; - - Set addedChalEvents; - Set removeChalEvents; - Set updatedChalEvents; - - const String diffShortNames[4] = { "NOV", "ADV", "EXH", "INF" }; - const String diffNames[4] = { "Novice", "Advanced", "Exhaust", "Infinite" }; - - m_database.Exec("BEGIN"); - for(Event& e : changes) - { - if (e.type == Event::Challenge && (e.action == Event::Added || e.action == Event::Updated)) - { - ChallengeIndex* chal; - if (e.action == Event::Added) - { - chal = new ChallengeIndex(); - chal->id = m_nextChalId++; - } - else - { - auto itChal = m_challenges.find(e.id); - assert(itChal != m_challenges.end()); - chal = itChal->second; - } - - if (e.json.is_discarded() || e.json.is_null()) - { - Log("Tried to process invalid json in Challenge Add event", Logger::Severity::Warning); - continue; - } - chal->settings = e.json; - chal->settings["title"].get_to(chal->title); - chal->path = e.path; - chal->settings["level"].get_to(chal->level); - if (e.action == Event::Added) - { - chal->clearMark = 0; - chal->bestScore = 0; - } - chal->hash = e.hash; - chal->missingChart = false; - chal->lwt = e.lwt; - chal->charts.clear(); - - String chartMeta = ""; - // Grab the charts - chal->FindCharts(&m_outer, chal->settings["charts"]); - chal->GenerateDescription(); - - String chartString = chal->settings["charts"].dump(); - - if (e.action == Event::Added) - { - m_challenges.Add(chal->id, chal); - - // Add Chart - // ("title,charts,chart_meta,clear_mark,best_score,req_text,path,hash,level,lwt) " - addChallenge.BindString(1, chal->title); - addChallenge.BindString(2, chartString); - addChallenge.BindString(3, chartMeta); - addChallenge.BindInt(4, chal->clearMark); - addChallenge.BindInt(5, chal->bestScore); - addChallenge.BindString(6, chal->reqText); - addChallenge.BindString(7, chal->path); - addChallenge.BindString(8, chal->hash); - addChallenge.BindInt(9, chal->level); - addChallenge.BindInt64(10, chal->lwt); - - addChallenge.Step(); - addChallenge.Rewind(); - - addedChalEvents.Add(chal); - } - else if (e.action == Event::Updated) - { - updateChallenge.BindString(1, chal->title); - updateChallenge.BindString(2, chartString); - updateChallenge.BindString(3, chartMeta); - updateChallenge.BindInt(4, chal->clearMark); - updateChallenge.BindInt(5, chal->bestScore); - updateChallenge.BindString(6, chal->reqText); - updateChallenge.BindString(7, chal->path); - updateChallenge.BindString(8, chal->hash); - updateChallenge.BindInt(9, chal->level); - updateChallenge.BindInt64(10, chal->lwt); - updateChallenge.BindInt(11, e.id); - - updateChallenge.Step(); - updateChallenge.Rewind(); - - updatedChalEvents.Add(chal); - } - } - else if(e.type == Event::Challenge && e.action == Event::Removed) - { - auto itChal = m_challenges.find(e.id); - assert(itChal != m_challenges.end()); - - delete itChal->second; - m_challenges.erase(e.id); - - // Remove diff in db - removeChallenge.BindInt(1, e.id); - removeChallenge.Step(); - removeChallenge.Rewind(); - } - if(e.type == Event::Chart && e.action == Event::Added) - { - String folderPath = Path::RemoveLast(e.path, nullptr); - bool existingUpdated; - FolderIndex* folder; - - // Add or get folder - auto folderIt = m_foldersByPath.find(folderPath); - if(folderIt == m_foldersByPath.end()) - { - // Add folder - folder = new FolderIndex(); - folder->id = m_nextFolderId++; - folder->path = folderPath; - folder->selectId = (int32) m_folders.size(); - - m_folders.Add(folder->id, folder); - m_foldersByPath.Add(folder->path, folder); - - addFolder.BindString(1, folder->path); - addFolder.BindInt(2, folder->id); - addFolder.Step(); - addFolder.Rewind(); - - existingUpdated = false; // New folder - } - else - { - folder = folderIt->second; - existingUpdated = true; // Existing folder - } - - - ChartIndex* chart = new ChartIndex(); - chart->id = m_nextChartId++; - chart->lwt = e.lwt; - chart->folderId = folder->id; - chart->path = e.path; - chart->title = e.mapData->title; - chart->artist = e.mapData->artist; - chart->level = e.mapData->level; - chart->effector = e.mapData->effector; - chart->preview_file = e.mapData->audioNoFX; - chart->preview_offset = e.mapData->previewOffset; - chart->preview_length = e.mapData->previewDuration; - chart->diff_index = e.mapData->difficulty; - chart->diff_name = diffNames[e.mapData->difficulty]; - chart->diff_shortname = diffShortNames[e.mapData->difficulty]; - chart->bpm = e.mapData->bpm; - chart->illustrator = e.mapData->illustrator; - chart->jacket_path = e.mapData->jacketPath; - chart->hash = e.hash; - - // Check for existing scores for this chart - scoreScan.BindString(1, chart->hash); - while (scoreScan.StepRow()) - { - ScoreIndex* score = new ScoreIndex(); - score->id = scoreScan.IntColumn(0); - score->score = scoreScan.IntColumn(1); - score->crit = scoreScan.IntColumn(2); - score->almost = scoreScan.IntColumn(3); - score->early = scoreScan.IntColumn(4); - score->late = scoreScan.IntColumn(5); - score->combo = scoreScan.IntColumn(6); - score->miss = scoreScan.IntColumn(7); - score->gauge = (float)scoreScan.DoubleColumn(8); - score->autoFlags = (AutoFlags)scoreScan.IntColumn(9); - score->replayPath = scoreScan.StringColumn(10); - - score->timestamp = scoreScan.Int64Column(11); - score->chartHash = scoreScan.StringColumn(12); - score->userName = scoreScan.StringColumn(13); - score->userId = scoreScan.StringColumn(14); - score->localScore = scoreScan.IntColumn(15); - - score->hitWindowPerfect = scoreScan.IntColumn(16); - score->hitWindowGood = scoreScan.IntColumn(17); - score->hitWindowHold = scoreScan.IntColumn(18); - score->hitWindowMiss = scoreScan.IntColumn(19); - score->hitWindowSlam = scoreScan.IntColumn(20); - - score->gaugeType = (GaugeType)scoreScan.IntColumn(21); - score->gaugeOption = scoreScan.IntColumn(22); - score->mirror = scoreScan.IntColumn(23) == 1; - score->random = scoreScan.IntColumn(24) == 1; - chart->scores.Add(score); - } - scoreScan.Rewind(); - - m_SortScores(chart); - - m_charts.Add(chart->id, chart); - m_chartsByHash.Add(chart->hash, chart); - // Add diff to map and resort - folder->charts.Add(chart); - m_SortCharts(folder); - - // Add Chart - addChart.BindInt(1, chart->folderId); - addChart.BindString(2, chart->path); - addChart.BindString(3, chart->title); - addChart.BindString(4, chart->artist); - addChart.BindString(5, chart->title_translit); - addChart.BindString(6, chart->artist_translit); - addChart.BindString(7, chart->jacket_path); - addChart.BindString(8, chart->effector); - addChart.BindString(9, chart->illustrator); - addChart.BindString(10, chart->diff_name); - addChart.BindString(11, chart->diff_shortname); - addChart.BindString(12, chart->bpm); - addChart.BindInt(13, chart->diff_index); - addChart.BindInt(14, chart->level); - addChart.BindString(15, chart->hash); - addChart.BindString(16, chart->preview_file); - addChart.BindInt(17, chart->preview_offset); - addChart.BindInt(18, chart->preview_length); - addChart.BindInt64(19, chart->lwt); - - addChart.Step(); - addChart.Rewind(); - - // Send appropriate notification - if(existingUpdated) - { - updatedChartEvents.Add(folder); - } - else - { - addedChartEvents.Add(folder); - } - } - else if(e.type == Event::Chart && e.action == Event::Updated) - { - update.BindString(1, e.path); - update.BindString(2, e.mapData->title); - update.BindString(3, e.mapData->artist); - update.BindString(4, ""); - update.BindString(5, ""); - update.BindString(6, e.mapData->jacketPath); - update.BindString(7, e.mapData->effector); - update.BindString(8, e.mapData->illustrator); - update.BindString(9, diffNames[e.mapData->difficulty]); - update.BindString(10, diffShortNames[e.mapData->difficulty]); - update.BindString(11, e.mapData->bpm); - update.BindInt(12, e.mapData->difficulty); - update.BindInt(13, e.mapData->level); - update.BindString(14, e.hash); - update.BindString(15, e.mapData->audioNoFX); - update.BindInt(16, e.mapData->previewOffset); - update.BindInt(17, e.mapData->previewDuration); - update.BindInt64(18, e.lwt); - update.BindInt(19, e.id); - - update.Step(); - update.Rewind(); - - auto itChart = m_charts.find(e.id); - assert(itChart != m_charts.end()); - - ChartIndex* chart = itChart->second; - chart->lwt = e.lwt; - chart->path = e.path; - chart->title = e.mapData->title; - chart->artist = e.mapData->artist; - chart->level = e.mapData->level; - chart->effector = e.mapData->effector; - chart->preview_file = e.mapData->audioNoFX; - chart->preview_offset = e.mapData->previewOffset; - chart->preview_length = e.mapData->previewDuration; - chart->diff_index = e.mapData->difficulty; - chart->diff_name = diffNames[e.mapData->difficulty]; - chart->diff_shortname = diffShortNames[e.mapData->difficulty]; - chart->bpm = e.mapData->bpm; - chart->illustrator = e.mapData->illustrator; - chart->jacket_path = e.mapData->jacketPath; - - - // Check if the hash has changed... - if (chart->hash != e.hash && m_transferScores) { - moveScores.BindString(1, e.hash); - moveScores.BindString(2, chart->hash); - moveScores.Step(); - moveScores.Rewind(); - } - chart->hash = e.hash; - - - auto itFolder = m_folders.find(chart->folderId); - assert(itFolder != m_folders.end()); - - // Send notification - updatedChartEvents.Add(itFolder->second); - } - else if(e.type == Event::Chart && e.action == Event::Removed) - { - auto itChart = m_charts.find(e.id); - assert(itChart != m_charts.end()); - - auto itFolder = m_folders.find(itChart->second->folderId); - assert(itFolder != m_folders.end()); - - itFolder->second->charts.Remove(itChart->second); - - for (auto s : itChart->second->scores) - { - delete s; - } - itChart->second->scores.clear(); - delete itChart->second; - m_charts.erase(e.id); - - // Remove diff in db - removeChart.BindInt(1, e.id); - removeChart.Step(); - removeChart.Rewind(); - - if(itFolder->second->charts.empty()) // Remove map as well - { - removeChartEvents.Add(itFolder->second); - - removeFolder.BindInt(1, itFolder->first); - removeFolder.Step(); - removeFolder.Rewind(); - - m_foldersByPath.erase(itFolder->second->path); - m_folders.erase(itFolder); - } - else - { - updatedChartEvents.Add(itFolder->second); - } - } - if(e.mapData) - delete e.mapData; - } - m_database.Exec("END"); - - // Fire events - if(!removeChartEvents.empty()) - { - Vector eventsArray; - for(auto i : removeChartEvents) - { - // Don't send 'updated' or 'added' events for removed maps - addedChartEvents.erase(i); - updatedChartEvents.erase(i); - eventsArray.Add(i); - } - - m_outer.OnFoldersRemoved.Call(eventsArray); - for(auto e : eventsArray) - { - delete e; - } - } - if(!addedChartEvents.empty()) - { - Vector eventsArray; - for(auto i : addedChartEvents) - { - // Don't send 'updated' events for added maps - updatedChartEvents.erase(i); - eventsArray.Add(i); - } - - m_outer.OnFoldersAdded.Call(eventsArray); - } - if(!addedChalEvents.empty()) - { - Vector eventsArray; - for(auto i : addedChalEvents) - { - // Don't send 'updated' events for added maps - updatedChalEvents.erase(i); - eventsArray.Add(i); - } - - m_outer.OnChallengesAdded.Call(eventsArray); - } - if(!updatedChartEvents.empty()) - { - Vector eventsArray; - for(auto i : updatedChartEvents) - { - eventsArray.Add(i); - } - - m_outer.OnFoldersUpdated.Call(eventsArray); - } - if(!updatedChalEvents.empty()) - { - Vector eventsArray; - for(auto i : updatedChalEvents) - { - eventsArray.Add(i); - } - - m_outer.OnChallengesUpdated.Call(eventsArray); - } - } - - void AddScore(ScoreIndex* score) - { - DBStatement addScore = m_database.Query("INSERT INTO " - "Scores(score,crit,near,early,late,combo,miss,gauge,auto_flags,replay,timestamp,chart_hash,user_name,user_id,local_score,window_perfect,window_good,window_hold,window_miss,window_slam,gauge_type,gauge_opt,mirror,random) " - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); - - m_database.Exec("BEGIN"); - addScore.BindInt(1, score->score); - addScore.BindInt(2, score->crit); - addScore.BindInt(3, score->almost); - addScore.BindInt(4, score->early); - addScore.BindInt(5, score->late); - addScore.BindInt(6, score->combo); - addScore.BindInt(7, score->miss); - addScore.BindDouble(8, score->gauge); - addScore.BindInt(9, (int32)score->autoFlags); - addScore.BindString(10, score->replayPath); - addScore.BindInt64(11, score->timestamp); - addScore.BindString(12, score->chartHash); - addScore.BindString(13, score->userName); - addScore.BindString(14, score->userId); - addScore.BindInt(15, score->localScore); - addScore.BindInt(16, score->hitWindowPerfect); - addScore.BindInt(17, score->hitWindowGood); - addScore.BindInt(18, score->hitWindowHold); - addScore.BindInt(19, score->hitWindowMiss); - addScore.BindInt(20, score->hitWindowSlam); - addScore.BindInt(21, (int32)score->gaugeType); - addScore.BindInt(22, score->gaugeOption); - addScore.BindInt(23, score->mirror ? 1 : 0); - addScore.BindInt(24, score->random ? 1 : 0); - - addScore.Step(); - addScore.Rewind(); - - m_database.Exec("END"); - } - - void UpdateChallengeResult(ChallengeIndex* chal, uint32 clearMark, uint32 bestScore) - { - assert(chal != nullptr); - assert(m_challenges.Contains(chal->id)); - - chal->clearMark = clearMark; - chal->bestScore = bestScore; - - const constexpr char* updateQuery = "UPDATE Challenges SET " - "clear_mark=?, best_score=?" - " WHERE rowid=?"; - - DBStatement statement = m_database.Query(updateQuery); - - m_database.Exec("BEGIN"); - - statement.BindInt(1, clearMark); - statement.BindInt(2, bestScore); - statement.BindInt(3, chal->id); - if (!statement.Step()) - { - Log("Failed to execute query for UpdateChallengeResult", Logger::Severity::Warning); - } - - statement.Rewind(); - - m_database.Exec("END"); - } - - void UpdateOrAddPracticeSetup(PracticeSetupIndex* practiceSetup) - { - if (!m_charts.Contains(practiceSetup->chartId)) - { - Logf("UpdateOrAddPracticeSetup called for invalid chart %d", Logger::Severity::Warning, practiceSetup->chartId); - return; - } - - const bool isUpdate = practiceSetup->id >= 0; - - if (isUpdate && !m_practiceSetups.Contains(practiceSetup->id)) - { - Logf("UpdateOrAddPracticeSetup called for invalid index %d", Logger::Severity::Warning, practiceSetup->id); - return; - } - - const constexpr char* addQuery = "INSERT INTO PracticeSetups(" - "chart_id, setup_title, loop_success, loop_fail, range_begin, range_end, fail_cond_type, fail_cond_value, " - "playback_speed, inc_speed_on_success, inc_speed, inc_streak, dec_speed_on_fail, dec_speed, min_playback_speed, max_rewind, max_rewind_measure" - ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - - const constexpr char* updateQuery = "UPDATE PracticeSetups SET " - "chart_id=?, setup_title=?, loop_success=?, loop_fail=?, range_begin=?, range_end=?, fail_cond_type=?, fail_cond_value=?, " - "playback_speed=?, inc_speed_on_success=?, inc_speed=?, inc_streak=?, dec_speed_on_fail=?, dec_speed=?, min_playback_speed=?, max_rewind=?, max_rewind_measure=?" - " WHERE rowid=?"; - - DBStatement statement = m_database.Query(isUpdate ? updateQuery : addQuery); - - m_database.Exec("BEGIN"); - - statement.BindInt(1, practiceSetup->chartId); - statement.BindString(2, practiceSetup->setupTitle); - statement.BindInt(3, practiceSetup->loopSuccess); - statement.BindInt(4, practiceSetup->loopFail); - statement.BindInt(5, practiceSetup->rangeBegin); - statement.BindInt(6, practiceSetup->rangeEnd); - statement.BindInt(7, practiceSetup->failCondType); - statement.BindInt(8, practiceSetup->failCondValue); - statement.BindDouble(9, practiceSetup->playbackSpeed); - statement.BindInt(10, practiceSetup->incSpeedOnSuccess); - statement.BindDouble(11, practiceSetup->incSpeed); - statement.BindInt(12, practiceSetup->incStreak); - statement.BindInt(13, practiceSetup->decSpeedOnFail); - statement.BindDouble(14, practiceSetup->decSpeed); - statement.BindDouble(15, practiceSetup->minPlaybackSpeed); - statement.BindInt(16, practiceSetup->maxRewind); - statement.BindInt(17, practiceSetup->maxRewindMeasure); - - if (isUpdate) - statement.BindInt(18, practiceSetup->id); - - if (statement.Step()) - { - if (!isUpdate) - { - DBStatement rowidStatement = m_database.Query("SELECT last_insert_rowid()"); - if (rowidStatement.StepRow()) - { - practiceSetup->id = rowidStatement.IntColumn(0); - assert(!m_practiceSetups.Contains(practiceSetup->id)); - - m_practiceSetups.Add(practiceSetup->id, practiceSetup); - m_practiceSetupsByChartId.Add(practiceSetup->chartId, practiceSetup); - } - else - { - Log("Failed to retrieve rowid for UpdateOrAddPracticeSetup", Logger::Severity::Warning); - } - - rowidStatement.Rewind(); - } - } - else - { - Log("Failed to execute query for UpdateOrAddPracticeSetup", Logger::Severity::Warning); - } - - statement.Rewind(); - - m_database.Exec("END"); - } - - void UpdateChartOffset(const ChartIndex* chart) - { - // Safe from sqli bc hash will be alphanum - m_database.Exec(Utility::Sprintf("UPDATE Charts SET custom_offset=%d WHERE hash LIKE '%s'", chart->custom_offset, *chart->hash)); - } - - void AddOrRemoveToCollection(const String& name, int32 mapid) - { - DBStatement addColl = m_database.Query("INSERT INTO Collections(folderid,collection) VALUES(?,?)"); - m_database.Exec("BEGIN"); - - addColl.BindInt(1, mapid); - addColl.BindString(2, name); - - bool result = addColl.Step(); - addColl.Rewind(); - - m_database.Exec("END"); - - if (!result) //Failed to add, try to remove - { - DBStatement remColl = m_database.Query("DELETE FROM collections WHERE folderid==? AND collection==?"); - remColl.BindInt(1, mapid); - remColl.BindString(2, name); - remColl.Step(); - remColl.Rewind(); - } - } - - ChartIndex* GetRandomChart() - { - auto it = m_charts.begin(); - uint32 selection = Random::IntRange(0, (int32)m_charts.size() - 1); - std::advance(it, selection); - return it->second; - } - const auto& GetFolderMap() - { - assert(!m_searching); // Unsafe when searching - return m_folders; - } - const auto& GetChartMap() - { - assert(!m_searching); // Unsafe when searching - return m_charts; - } - const auto& GetChallengeMap() - { - assert(!m_searching); // Unsafe when searching - return m_challenges; - } - - // TODO: Research thread pausing more - // ugly but should work - void PauseSearching() { - if (m_paused.load()) - return; - - m_paused.store(true); - } - - void ResumeSearching() { - if (!m_paused.load()) - return; - - m_paused.store(false); - m_cvPause.notify_all(); - } - - void SetChartUpdateBehavior(bool transferScores) { - m_transferScores = transferScores; - } + MapDatabase_Impl(MapDatabase& outer, bool transferScores) : m_outer(outer) + { + m_transferScores = transferScores; + String databasePath = Path::Absolute("maps.db"); + if (!m_database.Open(databasePath)) + { + Logf("Failed to open database [%s]", Logger::Severity::Warning, databasePath); + assert(false); + } + m_paused.store(false); + bool rebuild = false; + bool update = false; + DBStatement versionQuery = m_database.Query("SELECT version FROM `Database`"); + int32 gotVersion = 0; + if (versionQuery && versionQuery.Step()) + { + gotVersion = versionQuery.IntColumn(0); + if (gotVersion != m_version) + { + update = true; + } + } + else + { + // Create DB + m_database.Exec("DROP TABLE IF EXISTS Database"); + m_database.Exec("CREATE TABLE Database(version INTEGER)"); + m_database.Exec(Utility::Sprintf("INSERT OR REPLACE INTO Database(rowid, version) VALUES(1, %d)", m_version)); + rebuild = true; + } + versionQuery.Finish(); + + if (rebuild) + { + m_CreateTables(); + + // Update database version + m_database.Exec(Utility::Sprintf("UPDATE Database SET `version`=%d WHERE `rowid`=1", m_version)); + } + else if (update) + { + ProfilerScope $(Utility::Sprintf("Upgrading db (%d -> %d)", gotVersion, m_version)); + + // back up old db file + Path::Copy(Path::Absolute("maps.db"), Path::Absolute("maps.db_" + Shared::Time::Now().ToString() + ".bak")); + + m_outer.OnDatabaseUpdateStarted.Call(1); + + /// TODO: Make loop for doing iterative upgrades + if (gotVersion == 8) // upgrade from 8 to 9 + { + m_database.Exec("ALTER TABLE Scores ADD COLUMN hitstats BLOB"); + gotVersion = 9; + } + if (gotVersion == 9) // upgrade from 9 to 10 + { + m_database.Exec("ALTER TABLE Scores ADD COLUMN timestamp INTEGER"); + gotVersion = 10; + } + if (gotVersion == 10) // upgrade from 10 to 11 + { + m_database.Exec("ALTER TABLE Difficulties ADD COLUMN hash TEXT"); + gotVersion = 11; + } + if (gotVersion == 11) // upgrade from 11 to 12 + { + m_database.Exec("CREATE TABLE Collections" + "(collection TEXT, mapid INTEGER, " + "UNIQUE(collection,mapid), " + "FOREIGN KEY(mapid) REFERENCES Maps(rowid))"); + gotVersion = 12; + } + if (gotVersion == 12) // upgrade from 12 to 13 + { + + int diffCount = 1; + { + // Do in its own scope so that it will destruct before we modify anything else + DBStatement diffCountStmt = m_database.Query("SELECT COUNT(rowid) FROM Difficulties"); + if (diffCountStmt.StepRow()) + { + diffCount = diffCountStmt.IntColumn(0); + } + } + m_outer.OnDatabaseUpdateProgress.Call(0, diffCount); + + int progress = 0; + int totalScoreCount = 0; + + DBStatement diffScan = m_database.Query("SELECT rowid,path FROM Difficulties"); + + Vector scoresToAdd; + while (diffScan.StepRow()) + { + if (progress % 16 == 0) + m_outer.OnDatabaseUpdateProgress.Call(progress, diffCount); + progress++; + + bool noScores = true; + int diffid = diffScan.IntColumn(0); + String diffpath = diffScan.StringColumn(1); + DBStatement scoreCount = m_database.Query("SELECT COUNT(*) FROM scores WHERE diffid=?"); + scoreCount.BindInt(1, diffid); + if (scoreCount.StepRow()) + { + noScores = scoreCount.IntColumn(0) == 0; + } + + if (noScores) + { + continue; + } + + String hash; + File diffFile; + if (diffFile.OpenRead(diffpath)) + { + char data_buffer[0x80]; + uint32_t digest[5]; + sha1::SHA1 s; + + size_t amount_read = 0; + size_t read_size; + do + { + read_size = diffFile.Read(data_buffer, sizeof(data_buffer)); + amount_read += read_size; + s.processBytes(data_buffer, read_size); + } while (read_size != 0); + + s.getDigest(digest); + hash = Utility::Sprintf("%08x%08x%08x%08x%08x", digest[0], digest[1], digest[2], digest[3], digest[4]); + } + else + { + Logf("Could not open chart file at \"%s\" scores will be lost.", Logger::Severity::Warning, diffpath); + continue; + } + + DBStatement scoreScan = m_database.Query("SELECT rowid,score,crit,near,miss,gauge,gameflags,hitstats,timestamp,diffid FROM Scores WHERE diffid=?"); + scoreScan.BindInt(1, diffid); + while (scoreScan.StepRow()) + { + ScoreIndex score; + score.id = scoreScan.IntColumn(0); + score.score = scoreScan.IntColumn(1); + score.crit = scoreScan.IntColumn(2); + score.almost = scoreScan.IntColumn(3); + score.miss = scoreScan.IntColumn(4); + score.gauge = (float)scoreScan.DoubleColumn(5); + score.gaugeOption = scoreScan.IntColumn(6); + Buffer hitstats = scoreScan.BlobColumn(7); + score.timestamp = scoreScan.Int64Column(8); + auto timestamp = Shared::Time(score.timestamp); + score.chartHash = hash; + score.replayPath = Path::Normalize(Path::Absolute("replays/" + hash + "/" + timestamp.ToString() + ".urf")); + Path::CreateDir(Path::Absolute("replays/" + hash)); + File replayFile; + if (replayFile.OpenWrite(score.replayPath)) + { + replayFile.Write(hitstats.data(), hitstats.size()); + } + else + { + Logf("Could not open replay file at \"%s\" replay data will be lost.", Logger::Severity::Warning, score.replayPath); + } + scoresToAdd.Add(score); + totalScoreCount++; + } + } + + progress = 0; + + m_database.Exec("DROP TABLE IF EXISTS Maps"); + m_database.Exec("DROP TABLE IF EXISTS Difficulties"); + m_CreateTables(); + + DBStatement addScore = m_database.Query("INSERT INTO Scores(score,crit,near,miss,gauge,gameflags,replay,timestamp,chart_hash) VALUES(?,?,?,?,?,?,?,?,?)"); + + m_database.Exec("BEGIN"); + for (ScoreIndex& score : scoresToAdd) + { + addScore.BindInt(1, score.score); + addScore.BindInt(2, score.crit); + addScore.BindInt(3, score.almost); + addScore.BindInt(4, score.miss); + addScore.BindDouble(5, score.gauge); + addScore.BindInt(6, score.gaugeOption); + addScore.BindString(7, score.replayPath); + addScore.BindInt64(8, score.timestamp); + addScore.BindString(9, score.chartHash); + + addScore.Step(); + addScore.Rewind(); + + if (progress % 16 == 0) + { + m_outer.OnDatabaseUpdateProgress.Call(progress, totalScoreCount); + } + progress++; + } + m_database.Exec("END"); + m_database.Exec("VACUUM"); + gotVersion = 13; + } + if (gotVersion == 13) // from 13 to 14 + { + m_database.Exec("ALTER TABLE Charts ADD COLUMN custom_offset INTEGER"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN user_name TEXT"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN user_id TEXT"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN local_score INTEGER"); + m_database.Exec("UPDATE Charts SET custom_offset=0"); + m_database.Exec("UPDATE Scores SET local_score=1"); + m_database.Exec("UPDATE Scores SET local_score=1"); + m_database.Exec("UPDATE Scores SET user_name=\"\""); + m_database.Exec("UPDATE Scores SET user_id=\"\""); + gotVersion = 14; + } + if (gotVersion == 14) + { + m_database.Exec("CREATE TABLE PracticeSetups (" + "chart_id INTEGER," + "setup_title TEXT," + "loop_success INTEGER," + "loop_fail INTEGER," + "range_begin INTEGER," + "range_end INTEGER," + "fail_cond_type INTEGER," + "fail_cond_value INTEGER," + "playback_speed REAL," + "inc_speed_on_success INTEGER," + "inc_speed REAL," + "inc_streak INTEGER," + "dec_speed_on_fail INTEGER," + "dec_speed REAL," + "min_playback_speed REAL," + "max_rewind INTEGER," + "max_rewind_measure INTEGER," + "FOREIGN KEY(chart_id) REFERENCES Charts(rowid)" + ")"); + gotVersion = 15; + } + if (gotVersion == 15) + { + m_database.Exec("ALTER TABLE Scores ADD COLUMN window_perfect INTEGER"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN window_good INTEGER"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN window_hold INTEGER"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN window_miss INTEGER"); + m_database.Exec("UPDATE Scores SET window_perfect=46"); + m_database.Exec("UPDATE Scores SET window_good=150"); + m_database.Exec("UPDATE Scores SET window_hold=150"); + m_database.Exec("UPDATE Scores SET window_miss=300"); + gotVersion = 16; + } + if (gotVersion == 16) + { + m_database.Exec("CREATE TABLE Challenges" + "(" + "title TEXT," + "charts TEXT," + "chart_meta TEXT," // used for search + "clear_mark INTEGER," + "best_score INTEGER," + "req_text TEXT," + "path TEXT," + "hash TEXT," + "level INTEGER," + "lwt INTEGER" + ")"); + gotVersion = 17; + } + if (gotVersion == 17) + { + Map optionMap; + int totalScoreCount = 0; + DBStatement scoreScan = m_database.Query("SELECT rowid,gameflags FROM Scores"); + while (scoreScan.StepRow()) + { + optionMap.Add(scoreScan.IntColumn(0), PlaybackOptions::FromFlags(scoreScan.IntColumn(1))); + totalScoreCount++; + } + + m_outer.OnDatabaseUpdateProgress.Call(0, totalScoreCount); + int progress = 0; + + // alter table. + // if we were on a newer sqlite version the gameflags column could easily be renamed but it will + // instead still exist in the db after this update but will be unused. + m_database.Exec("BEGIN"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN gauge_type INTEGER"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN auto_flags INTEGER"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN gauge_opt INTEGER"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN mirror INTEGER"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN random INTEGER"); + + DBStatement setScoreOpt = m_database.Query("UPDATE Scores set gauge_type=?, gauge_opt=?, mirror=?, random=?, auto_flags=? WHERE rowid=?"); + for (auto& o : optionMap) + { + setScoreOpt.BindInt(1, (int32)o.second.gaugeType); + setScoreOpt.BindInt(2, o.second.gaugeOption); + setScoreOpt.BindInt(3, o.second.mirror ? 1 : 0); + setScoreOpt.BindInt(4, o.second.random ? 1 : 0); + setScoreOpt.BindInt(5, (int32)o.second.autoFlags); + setScoreOpt.BindInt(6, o.first); + + setScoreOpt.StepRow(); + setScoreOpt.Rewind(); + + progress++; + m_outer.OnDatabaseUpdateProgress.Call(progress, totalScoreCount); + } + m_database.Exec("END"); + gotVersion = 18; + } + if (gotVersion == 18) + { + m_database.Exec("ALTER TABLE Scores ADD COLUMN window_slam INTEGER"); + m_database.Exec("UPDATE Scores SET window_slam=84"); + gotVersion = 19; + } + if (gotVersion == 19) + { + m_database.Exec("ALTER TABLE Scores ADD COLUMN early INTEGER"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN late INTEGER"); + m_database.Exec("ALTER TABLE Scores ADD COLUMN combo INTEGER"); + m_database.Exec("UPDATE Scores SET early=?"); + m_database.Exec("UPDATE Scores SET late=?"); + m_database.Exec("UPDATE Scores SET combo=?"); + gotVersion = 20; + } + m_database.Exec(Utility::Sprintf("UPDATE Database SET `version`=%d WHERE `rowid`=1", m_version)); + + m_outer.OnDatabaseUpdateDone.Call(); + } + else + { + // NOTE: before we loaded the database here. This was redundant since we always do StartSearching + // Plus we don't do this when doing an update so if it was a problem to not do it here we would + // have already noticed it being broken in a launch where the db is updated + // Also with challenges we can't do this until the constructor is done so we can access the + // MapDatabase wrapper while loading challenges + // m_LoadInitialData(); + } + } + ~MapDatabase_Impl() + { + StopSearching(); + m_CleanupMapIndex(); + + // discard pending changes, probably should apply them(?) + auto changes = FlushChanges(); + for (auto& c : changes) + { + if (c.mapData) + delete c.mapData; + } + } + void LoadDatabaseWithoutSearching() + { + // Apply previous diff to prevent duplicated entry + Update(); + // Create initial data set to compare to when evaluating if a file is added/removed/updated + m_LoadInitialData(); + } + void StartSearching() + { + if (m_searching) + return; + + if (m_thread.joinable()) + m_thread.join(); + // Apply previous diff to prevent duplicated entry + Update(); + // Create initial data set to compare to when evaluating if a file is added/removed/updated + m_LoadInitialData(); + ResumeSearching(); + m_interruptSearch = false; + m_searching = true; + m_thread = thread(&MapDatabase_Impl::m_SearchThread, this); + } + void StopSearching() + { + ResumeSearching(); + m_interruptSearch = true; + m_searching = false; + if (m_thread.joinable()) + { + m_thread.join(); + } + } + void AddSearchPath(const String& path) + { + String normalizedPath = Path::Normalize(Path::Absolute(path)); + if (m_searchPaths.Contains(normalizedPath)) + return; + + m_searchPaths.Add(normalizedPath); + } + void RemoveSearchPath(const String& path) + { + String normalizedPath = Path::Normalize(Path::Absolute(path)); + if (!m_searchPaths.Contains(normalizedPath)) + return; + + m_searchPaths.erase(normalizedPath); + } + + /* Thread safe event queue functions */ + // Add a new change to the change queue + void AddChange(Event change) + { + m_pendingChangesLock.lock(); + m_pendingChanges.emplace_back(change); + m_pendingChangesLock.unlock(); + } + // Removes changes from the queue and returns them + // additionally you can specify the maximum amount of changes to remove from the queue + List FlushChanges(size_t maxChanges = -1) + { + List changes; + m_pendingChangesLock.lock(); + if (maxChanges == -1) + { + changes = std::move(m_pendingChanges); // All changes + } + else + { + for (size_t i = 0; i < maxChanges && !m_pendingChanges.empty(); i++) + { + changes.AddFront(m_pendingChanges.front()); + m_pendingChanges.pop_front(); + } + } + m_pendingChangesLock.unlock(); + return std::move(changes); + } + + // TODO(itszn) make sure this is not case sensitive + ChartIndex* FindFirstChartByPath(const String& searchString) + { + String stmt = "SELECT DISTINCT rowid FROM Charts WHERE path LIKE ? LIMIT 1"; + + DBStatement search = m_database.Query(stmt); + search.BindString(1, "%" + searchString + "%"); + while (search.StepRow()) + { + int32 id = search.IntColumn(0); + ChartIndex** chart = m_charts.Find(id); + if (!chart) + return nullptr; + return *chart; + } + + return nullptr; + } + + ChartIndex* FindFirstChartByNameAndLevel(const String& name, uint32 level, bool exact = true) + { + String stmt = "SELECT DISTINCT rowid FROM Charts WHERE title LIKE ? and level=? LIMIT 1"; + + DBStatement search = m_database.Query(stmt); + if (exact) + search.BindString(1, name); + else + search.BindString(1, "%" + name + "%"); + search.BindInt(2, level); + while (search.StepRow()) + { + int32 id = search.IntColumn(0); + ChartIndex** chart = m_charts.Find(id); + if (!chart) + return nullptr; + return *chart; + } + + // Try non exact now + if (exact) + return FindFirstChartByNameAndLevel(name, level, false); + + return nullptr; + } + + ChartIndex* FindFirstChartByHash(const String& hash) + { + ChartIndex** chart = m_chartsByHash.Find(hash); + if (!chart) + return nullptr; + return *chart; + } + + Map FindFoldersByHash(const String& hash) + { + String stmt = "SELECT DISTINCT folderId FROM Charts WHERE hash = ?"; + DBStatement search = m_database.Query(stmt); + search.BindString(1, hash); + + Map res; + while (search.StepRow()) + { + int32 id = search.IntColumn(0); + FolderIndex** folder = m_folders.Find(id); + if (folder) + { + res.Add(id, *folder); + } + } + + return res; + } + + // TODO(itszn) make this not case sensitive + Map FindFoldersByPath(const String& searchString) + { + String stmt = "SELECT DISTINCT folderId FROM Charts WHERE path LIKE ?"; + DBStatement search = m_database.Query(stmt); + search.BindString(1, "%" + searchString + "%"); + + Map res; + while (search.StepRow()) + { + int32 id = search.IntColumn(0); + FolderIndex** folder = m_folders.Find(id); + if (folder) + { + res.Add(id, *folder); + } + } + + return res; + } + + Map FindChallenges(const String& searchString) + { + WString test = Utility::ConvertToWString(searchString); + String stmt = "SELECT DISTINCT rowid FROM Challenges WHERE"; + + Vector terms = searchString.Explode(" "); + int32 i = 0; + for (auto term : terms) + { + if (i > 0) + stmt += " AND"; + stmt += String(" (title LIKE ?") + + " OR chart_meta LIKE ?" + " OR path LIKE ?)"; + i++; + } + DBStatement search = m_database.Query(stmt); + + i = 1; + for (auto term : terms) + { + // Bind all the terms + for (int j = 0; j < 3; j++) + { + search.BindString(i + j, "%" + term + "%"); + } + i += 6; + } + + Map res; + while (search.StepRow()) + { + int32 id = search.IntColumn(0); + ChallengeIndex** challenge = m_challenges.Find(id); + if (challenge) + { + res.Add(id, *challenge); + } + } + + return res; + } + + Map FindFoldersWithFilter(const String& searchString, const Vector> filters) + { + WString test = Utility::ConvertToWString(searchString); + String stmt = "SELECT DISTINCT folderId FROM Charts"; + + String conds = ""; + + Vector binds; + + if (!searchString.empty()) + { + Vector terms = searchString.Explode(" "); + for (const auto& term : terms) + { + if (term.empty()) + continue; + + if (!conds.empty()) + conds += " AND"; + conds += String(" (artist LIKE ?") + + " OR title LIKE ?" + + " OR path LIKE ?" + + " OR effector LIKE ?" + + " OR artist_translit LIKE ?" + + " OR title_translit LIKE ?)"; + + for (int j = 0; j < 6; j++) + binds.push_back("%" + term + "%"); + } + } + + for (const auto& filter : filters) + { + if (filter.second.empty()) + continue; + + if (!conds.empty()) + conds += " AND"; + conds += " (" + filter.first + " == ?)"; + binds.push_back(filter.second); + } + + if (!conds.empty()) + stmt += " WHERE" + conds; + + DBStatement search = m_database.Query(stmt); + + int32 num = 1; + for (const auto& bind : binds) + { + search.BindString(num, bind); + num++; + } + + Map res; + while (search.StepRow()) + { + int32 id = search.IntColumn(0); + FolderIndex** folder = m_folders.Find(id); + if (folder) + { + res.Add(id, *folder); + } + } + + return res; + } + + Map FindFolders(const String& searchString) + { + return FindFoldersWithFilter(searchString, {}); + } + + Vector GetCollections() + { + Vector res; + DBStatement search = m_database.Query("SELECT DISTINCT collection FROM collections"); + while (search.StepRow()) + { + res.Add(search.StringColumn(0)); + } + return res; + } + + Vector GetCollectionsForMap(int32 mapid) + { + Vector res; + DBStatement search = m_database.Query(Utility::Sprintf("SELECT DISTINCT collection FROM collections WHERE folderid==%d", mapid)); + while (search.StepRow()) + { + res.Add(search.StringColumn(0)); + } + return res; + } + + Vector GetOrAddPracticeSetups(int32 chartId, const PracticeSetupIndex& defaultOptions) + { + Vector res; + + auto it = m_practiceSetupsByChartId.equal_range(chartId); + for (auto it1 = it.first; it1 != it.second; ++it1) + { + res.Add(it1->second); + } + + if (res.empty()) + { + PracticeSetupIndex* practiceSetup = new PracticeSetupIndex(defaultOptions); + practiceSetup->id = -1; + practiceSetup->chartId = chartId; + + practiceSetup->setupTitle = ""; + + UpdateOrAddPracticeSetup(practiceSetup); + res.emplace_back(practiceSetup); + } + + return res; + } + + Map FindFoldersByCollection(const String& collection) + { + String stmt = "SELECT folderid FROM Collections WHERE collection==?"; + DBStatement search = m_database.Query(stmt); + search.BindString(1, collection); + + Map res; + while (search.StepRow()) + { + int32 id = search.IntColumn(0); + FolderIndex** folder = m_folders.Find(id); + if (folder) + { + res.Add(id, *folder); + } + } + + return res; + } + + Map FindFoldersByFolder(const String& folder) + { + char csep[2]; + csep[0] = Path::sep; + csep[1] = 0; + String sep(csep); + String stmt = "SELECT rowid FROM folders WHERE path LIKE ?"; + DBStatement search = m_database.Query(stmt); + search.BindString(1, "%" + sep + folder + sep + "%"); + + Map res; + while (search.StepRow()) + { + int32 id = search.IntColumn(0); + FolderIndex** folder = m_folders.Find(id); + if (folder) + { + res.Add(id, *folder); + } + } + + return res; + } + // Processes pending database changes + void Update() + { + List changes = FlushChanges(); + if (changes.empty()) + return; + + DBStatement addChart = m_database.Query("INSERT INTO Charts(" + "folderId,path,title,artist,title_translit,artist_translit,jacket_path,effector,illustrator," + "diff_name,diff_shortname,bpm,diff_index,level,hash,preview_file,preview_offset,preview_length,lwt) " + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + DBStatement addFolder = m_database.Query("INSERT INTO Folders(path,rowid) VALUES(?,?)"); + DBStatement addChallenge = m_database.Query("INSERT INTO Challenges(" + "title,charts,chart_meta,clear_mark,best_score,req_text,path,hash,level,lwt) " + "VALUES(?,?,?,?,?,?,?,?,?,?)"); + DBStatement update = m_database.Query("UPDATE Charts SET path=?,title=?,artist=?,title_translit=?,artist_translit=?,jacket_path=?,effector=?,illustrator=?," + "diff_name=?,diff_shortname=?,bpm=?,diff_index=?,level=?,hash=?,preview_file=?,preview_offset=?,preview_length=?,lwt=? WHERE rowid=?"); // TODO: update + DBStatement updateChallenge = m_database.Query("UPDATE Challenges SET title=?,charts=?,chart_meta=?,clear_mark=?,best_score=?,req_text=?,path=?,hash=?,level=?,lwt=? WHERE rowid=?"); + DBStatement removeChart = m_database.Query("DELETE FROM Charts WHERE rowid=?"); + DBStatement removeChallenge = m_database.Query("DELETE FROM Challenges WHERE rowid=?"); + DBStatement removeFolder = m_database.Query("DELETE FROM Folders WHERE rowid=?"); + DBStatement scoreScan = m_database.Query("SELECT " + "rowid,score,crit,near,early,late,combo,miss,gauge,auto_flags,replay,timestamp,chart_hash,user_name,user_id,local_score,window_perfect,window_good,window_hold,window_miss,window_slam,gauge_type,gauge_opt,mirror,random " + "FROM Scores WHERE chart_hash=?"); + DBStatement moveScores = m_database.Query("UPDATE Scores set chart_hash=? where chart_hash=?"); + + Set addedChartEvents; + Set removeChartEvents; + Set updatedChartEvents; + + Set addedChalEvents; + Set removeChalEvents; + Set updatedChalEvents; + + const String diffShortNames[4] = { "NOV", "ADV", "EXH", "INF" }; + const String diffNames[4] = { "Novice", "Advanced", "Exhaust", "Infinite" }; + + m_database.Exec("BEGIN"); + for (Event& e : changes) + { + if (e.type == Event::Challenge && (e.action == Event::Added || e.action == Event::Updated)) + { + ChallengeIndex* chal; + if (e.action == Event::Added) + { + chal = new ChallengeIndex(); + chal->id = m_nextChalId++; + } + else + { + auto itChal = m_challenges.find(e.id); + assert(itChal != m_challenges.end()); + chal = itChal->second; + } + + if (e.json.is_discarded() || e.json.is_null()) + { + Log("Tried to process invalid json in Challenge Add event", Logger::Severity::Warning); + continue; + } + chal->settings = e.json; + chal->settings["title"].get_to(chal->title); + chal->path = e.path; + chal->settings["level"].get_to(chal->level); + if (e.action == Event::Added) + { + chal->clearMark = 0; + chal->bestScore = 0; + } + chal->hash = e.hash; + chal->missingChart = false; + chal->lwt = e.lwt; + chal->charts.clear(); + + String chartMeta = ""; + // Grab the charts + chal->FindCharts(&m_outer, chal->settings["charts"]); + chal->GenerateDescription(); + + String chartString = chal->settings["charts"].dump(); + + if (e.action == Event::Added) + { + m_challenges.Add(chal->id, chal); + + // Add Chart + // ("title,charts,chart_meta,clear_mark,best_score,req_text,path,hash,level,lwt) " + addChallenge.BindString(1, chal->title); + addChallenge.BindString(2, chartString); + addChallenge.BindString(3, chartMeta); + addChallenge.BindInt(4, chal->clearMark); + addChallenge.BindInt(5, chal->bestScore); + addChallenge.BindString(6, chal->reqText); + addChallenge.BindString(7, chal->path); + addChallenge.BindString(8, chal->hash); + addChallenge.BindInt(9, chal->level); + addChallenge.BindInt64(10, chal->lwt); + + addChallenge.Step(); + addChallenge.Rewind(); + + addedChalEvents.Add(chal); + } + else if (e.action == Event::Updated) + { + updateChallenge.BindString(1, chal->title); + updateChallenge.BindString(2, chartString); + updateChallenge.BindString(3, chartMeta); + updateChallenge.BindInt(4, chal->clearMark); + updateChallenge.BindInt(5, chal->bestScore); + updateChallenge.BindString(6, chal->reqText); + updateChallenge.BindString(7, chal->path); + updateChallenge.BindString(8, chal->hash); + updateChallenge.BindInt(9, chal->level); + updateChallenge.BindInt64(10, chal->lwt); + updateChallenge.BindInt(11, e.id); + + updateChallenge.Step(); + updateChallenge.Rewind(); + + updatedChalEvents.Add(chal); + } + } + else if (e.type == Event::Challenge && e.action == Event::Removed) + { + auto itChal = m_challenges.find(e.id); + assert(itChal != m_challenges.end()); + + delete itChal->second; + m_challenges.erase(e.id); + + // Remove diff in db + removeChallenge.BindInt(1, e.id); + removeChallenge.Step(); + removeChallenge.Rewind(); + } + if (e.type == Event::Chart && e.action == Event::Added) + { + String folderPath = Path::RemoveLast(e.path, nullptr); + bool existingUpdated; + FolderIndex* folder; + + // Add or get folder + auto folderIt = m_foldersByPath.find(folderPath); + if (folderIt == m_foldersByPath.end()) + { + // Add folder + folder = new FolderIndex(); + folder->id = m_nextFolderId++; + folder->path = folderPath; + folder->selectId = (int32)m_folders.size(); + + m_folders.Add(folder->id, folder); + m_foldersByPath.Add(folder->path, folder); + + addFolder.BindString(1, folder->path); + addFolder.BindInt(2, folder->id); + addFolder.Step(); + addFolder.Rewind(); + + existingUpdated = false; // New folder + } + else + { + folder = folderIt->second; + existingUpdated = true; // Existing folder + } + + ChartIndex* chart = new ChartIndex(); + chart->id = m_nextChartId++; + chart->lwt = e.lwt; + chart->folderId = folder->id; + chart->path = e.path; + chart->title = e.mapData->title; + chart->artist = e.mapData->artist; + chart->level = e.mapData->level; + chart->effector = e.mapData->effector; + chart->preview_file = e.mapData->audioNoFX; + chart->preview_offset = e.mapData->previewOffset; + chart->preview_length = e.mapData->previewDuration; + chart->diff_index = e.mapData->difficulty; + chart->diff_name = diffNames[e.mapData->difficulty]; + chart->diff_shortname = diffShortNames[e.mapData->difficulty]; + chart->bpm = e.mapData->bpm; + chart->illustrator = e.mapData->illustrator; + chart->jacket_path = e.mapData->jacketPath; + chart->hash = e.hash; + + // Check for existing scores for this chart + scoreScan.BindString(1, chart->hash); + while (scoreScan.StepRow()) + { + ScoreIndex* score = new ScoreIndex(); + score->id = scoreScan.IntColumn(0); + score->score = scoreScan.IntColumn(1); + score->crit = scoreScan.IntColumn(2); + score->almost = scoreScan.IntColumn(3); + score->early = scoreScan.IntColumn(4); + score->late = scoreScan.IntColumn(5); + score->combo = scoreScan.IntColumn(6); + score->miss = scoreScan.IntColumn(7); + score->gauge = (float)scoreScan.DoubleColumn(8); + score->autoFlags = (AutoFlags)scoreScan.IntColumn(9); + score->replayPath = scoreScan.StringColumn(10); + + score->timestamp = scoreScan.Int64Column(11); + score->chartHash = scoreScan.StringColumn(12); + score->userName = scoreScan.StringColumn(13); + score->userId = scoreScan.StringColumn(14); + score->localScore = scoreScan.IntColumn(15); + + score->hitWindowPerfect = scoreScan.IntColumn(16); + score->hitWindowGood = scoreScan.IntColumn(17); + score->hitWindowHold = scoreScan.IntColumn(18); + score->hitWindowMiss = scoreScan.IntColumn(19); + score->hitWindowSlam = scoreScan.IntColumn(20); + + score->gaugeType = (GaugeType)scoreScan.IntColumn(21); + score->gaugeOption = scoreScan.IntColumn(22); + score->mirror = scoreScan.IntColumn(23) == 1; + score->random = scoreScan.IntColumn(24) == 1; + chart->scores.Add(score); + } + scoreScan.Rewind(); + + m_SortScores(chart); + + m_charts.Add(chart->id, chart); + m_chartsByHash.Add(chart->hash, chart); + // Add diff to map and resort + folder->charts.Add(chart); + m_SortCharts(folder); + + // Add Chart + addChart.BindInt(1, chart->folderId); + addChart.BindString(2, chart->path); + addChart.BindString(3, chart->title); + addChart.BindString(4, chart->artist); + addChart.BindString(5, chart->title_translit); + addChart.BindString(6, chart->artist_translit); + addChart.BindString(7, chart->jacket_path); + addChart.BindString(8, chart->effector); + addChart.BindString(9, chart->illustrator); + addChart.BindString(10, chart->diff_name); + addChart.BindString(11, chart->diff_shortname); + addChart.BindString(12, chart->bpm); + addChart.BindInt(13, chart->diff_index); + addChart.BindInt(14, chart->level); + addChart.BindString(15, chart->hash); + addChart.BindString(16, chart->preview_file); + addChart.BindInt(17, chart->preview_offset); + addChart.BindInt(18, chart->preview_length); + addChart.BindInt64(19, chart->lwt); + + addChart.Step(); + addChart.Rewind(); + + // Send appropriate notification + if (existingUpdated) + { + updatedChartEvents.Add(folder); + } + else + { + addedChartEvents.Add(folder); + } + } + else if (e.type == Event::Chart && e.action == Event::Updated) + { + update.BindString(1, e.path); + update.BindString(2, e.mapData->title); + update.BindString(3, e.mapData->artist); + update.BindString(4, ""); + update.BindString(5, ""); + update.BindString(6, e.mapData->jacketPath); + update.BindString(7, e.mapData->effector); + update.BindString(8, e.mapData->illustrator); + update.BindString(9, diffNames[e.mapData->difficulty]); + update.BindString(10, diffShortNames[e.mapData->difficulty]); + update.BindString(11, e.mapData->bpm); + update.BindInt(12, e.mapData->difficulty); + update.BindInt(13, e.mapData->level); + update.BindString(14, e.hash); + update.BindString(15, e.mapData->audioNoFX); + update.BindInt(16, e.mapData->previewOffset); + update.BindInt(17, e.mapData->previewDuration); + update.BindInt64(18, e.lwt); + update.BindInt(19, e.id); + + update.Step(); + update.Rewind(); + + auto itChart = m_charts.find(e.id); + assert(itChart != m_charts.end()); + + ChartIndex* chart = itChart->second; + chart->lwt = e.lwt; + chart->path = e.path; + chart->title = e.mapData->title; + chart->artist = e.mapData->artist; + chart->level = e.mapData->level; + chart->effector = e.mapData->effector; + chart->preview_file = e.mapData->audioNoFX; + chart->preview_offset = e.mapData->previewOffset; + chart->preview_length = e.mapData->previewDuration; + chart->diff_index = e.mapData->difficulty; + chart->diff_name = diffNames[e.mapData->difficulty]; + chart->diff_shortname = diffShortNames[e.mapData->difficulty]; + chart->bpm = e.mapData->bpm; + chart->illustrator = e.mapData->illustrator; + chart->jacket_path = e.mapData->jacketPath; + + // Check if the hash has changed... + if (chart->hash != e.hash && m_transferScores) + { + moveScores.BindString(1, e.hash); + moveScores.BindString(2, chart->hash); + moveScores.Step(); + moveScores.Rewind(); + } + chart->hash = e.hash; + + auto itFolder = m_folders.find(chart->folderId); + assert(itFolder != m_folders.end()); + + // Send notification + updatedChartEvents.Add(itFolder->second); + } + else if (e.type == Event::Chart && e.action == Event::Removed) + { + auto itChart = m_charts.find(e.id); + assert(itChart != m_charts.end()); + + auto itFolder = m_folders.find(itChart->second->folderId); + assert(itFolder != m_folders.end()); + + itFolder->second->charts.Remove(itChart->second); + + for (auto s : itChart->second->scores) + { + delete s; + } + itChart->second->scores.clear(); + delete itChart->second; + m_charts.erase(e.id); + + // Remove diff in db + removeChart.BindInt(1, e.id); + removeChart.Step(); + removeChart.Rewind(); + + if (itFolder->second->charts.empty()) // Remove map as well + { + removeChartEvents.Add(itFolder->second); + + removeFolder.BindInt(1, itFolder->first); + removeFolder.Step(); + removeFolder.Rewind(); + + m_foldersByPath.erase(itFolder->second->path); + m_folders.erase(itFolder); + } + else + { + updatedChartEvents.Add(itFolder->second); + } + } + if (e.mapData) + delete e.mapData; + } + m_database.Exec("END"); + + // Fire events + if (!removeChartEvents.empty()) + { + Vector eventsArray; + for (auto i : removeChartEvents) + { + // Don't send 'updated' or 'added' events for removed maps + addedChartEvents.erase(i); + updatedChartEvents.erase(i); + eventsArray.Add(i); + } + + m_outer.OnFoldersRemoved.Call(eventsArray); + for (auto e : eventsArray) + { + delete e; + } + } + if (!addedChartEvents.empty()) + { + Vector eventsArray; + for (auto i : addedChartEvents) + { + // Don't send 'updated' events for added maps + updatedChartEvents.erase(i); + eventsArray.Add(i); + } + + m_outer.OnFoldersAdded.Call(eventsArray); + } + if (!addedChalEvents.empty()) + { + Vector eventsArray; + for (auto i : addedChalEvents) + { + // Don't send 'updated' events for added maps + updatedChalEvents.erase(i); + eventsArray.Add(i); + } + + m_outer.OnChallengesAdded.Call(eventsArray); + } + if (!updatedChartEvents.empty()) + { + Vector eventsArray; + for (auto i : updatedChartEvents) + { + eventsArray.Add(i); + } + + m_outer.OnFoldersUpdated.Call(eventsArray); + } + if (!updatedChalEvents.empty()) + { + Vector eventsArray; + for (auto i : updatedChalEvents) + { + eventsArray.Add(i); + } + + m_outer.OnChallengesUpdated.Call(eventsArray); + } + } + + void AddScore(ScoreIndex* score) + { + DBStatement addScore = m_database.Query("INSERT INTO " + "Scores(score,crit,near,early,late,combo,miss,gauge,auto_flags,replay,timestamp,chart_hash,user_name,user_id,local_score,window_perfect,window_good,window_hold,window_miss,window_slam,gauge_type,gauge_opt,mirror,random) " + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + + m_database.Exec("BEGIN"); + addScore.BindInt(1, score->score); + addScore.BindInt(2, score->crit); + addScore.BindInt(3, score->almost); + addScore.BindInt(4, score->early); + addScore.BindInt(5, score->late); + addScore.BindInt(6, score->combo); + addScore.BindInt(7, score->miss); + addScore.BindDouble(8, score->gauge); + addScore.BindInt(9, (int32)score->autoFlags); + addScore.BindString(10, score->replayPath); + addScore.BindInt64(11, score->timestamp); + addScore.BindString(12, score->chartHash); + addScore.BindString(13, score->userName); + addScore.BindString(14, score->userId); + addScore.BindInt(15, score->localScore); + addScore.BindInt(16, score->hitWindowPerfect); + addScore.BindInt(17, score->hitWindowGood); + addScore.BindInt(18, score->hitWindowHold); + addScore.BindInt(19, score->hitWindowMiss); + addScore.BindInt(20, score->hitWindowSlam); + addScore.BindInt(21, (int32)score->gaugeType); + addScore.BindInt(22, score->gaugeOption); + addScore.BindInt(23, score->mirror ? 1 : 0); + addScore.BindInt(24, score->random ? 1 : 0); + + addScore.Step(); + addScore.Rewind(); + + m_database.Exec("END"); + } + + void UpdateChallengeResult(ChallengeIndex* chal, uint32 clearMark, uint32 bestScore) + { + assert(chal != nullptr); + assert(m_challenges.Contains(chal->id)); + + chal->clearMark = clearMark; + chal->bestScore = bestScore; + + const constexpr char* updateQuery = "UPDATE Challenges SET " + "clear_mark=?, best_score=?" + " WHERE rowid=?"; + + DBStatement statement = m_database.Query(updateQuery); + + m_database.Exec("BEGIN"); + + statement.BindInt(1, clearMark); + statement.BindInt(2, bestScore); + statement.BindInt(3, chal->id); + if (!statement.Step()) + { + Log("Failed to execute query for UpdateChallengeResult", Logger::Severity::Warning); + } + + statement.Rewind(); + + m_database.Exec("END"); + } + + void UpdateOrAddPracticeSetup(PracticeSetupIndex* practiceSetup) + { + if (!m_charts.Contains(practiceSetup->chartId)) + { + Logf("UpdateOrAddPracticeSetup called for invalid chart %d", Logger::Severity::Warning, practiceSetup->chartId); + return; + } + + const bool isUpdate = practiceSetup->id >= 0; + + if (isUpdate && !m_practiceSetups.Contains(practiceSetup->id)) + { + Logf("UpdateOrAddPracticeSetup called for invalid index %d", Logger::Severity::Warning, practiceSetup->id); + return; + } + + const constexpr char* addQuery = "INSERT INTO PracticeSetups(" + "chart_id, setup_title, loop_success, loop_fail, range_begin, range_end, fail_cond_type, fail_cond_value, " + "playback_speed, inc_speed_on_success, inc_speed, inc_streak, dec_speed_on_fail, dec_speed, min_playback_speed, max_rewind, max_rewind_measure" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + const constexpr char* updateQuery = "UPDATE PracticeSetups SET " + "chart_id=?, setup_title=?, loop_success=?, loop_fail=?, range_begin=?, range_end=?, fail_cond_type=?, fail_cond_value=?, " + "playback_speed=?, inc_speed_on_success=?, inc_speed=?, inc_streak=?, dec_speed_on_fail=?, dec_speed=?, min_playback_speed=?, max_rewind=?, max_rewind_measure=?" + " WHERE rowid=?"; + + DBStatement statement = m_database.Query(isUpdate ? updateQuery : addQuery); + + m_database.Exec("BEGIN"); + + statement.BindInt(1, practiceSetup->chartId); + statement.BindString(2, practiceSetup->setupTitle); + statement.BindInt(3, practiceSetup->loopSuccess); + statement.BindInt(4, practiceSetup->loopFail); + statement.BindInt(5, practiceSetup->rangeBegin); + statement.BindInt(6, practiceSetup->rangeEnd); + statement.BindInt(7, practiceSetup->failCondType); + statement.BindInt(8, practiceSetup->failCondValue); + statement.BindDouble(9, practiceSetup->playbackSpeed); + statement.BindInt(10, practiceSetup->incSpeedOnSuccess); + statement.BindDouble(11, practiceSetup->incSpeed); + statement.BindInt(12, practiceSetup->incStreak); + statement.BindInt(13, practiceSetup->decSpeedOnFail); + statement.BindDouble(14, practiceSetup->decSpeed); + statement.BindDouble(15, practiceSetup->minPlaybackSpeed); + statement.BindInt(16, practiceSetup->maxRewind); + statement.BindInt(17, practiceSetup->maxRewindMeasure); + + if (isUpdate) + statement.BindInt(18, practiceSetup->id); + + if (statement.Step()) + { + if (!isUpdate) + { + DBStatement rowidStatement = m_database.Query("SELECT last_insert_rowid()"); + if (rowidStatement.StepRow()) + { + practiceSetup->id = rowidStatement.IntColumn(0); + assert(!m_practiceSetups.Contains(practiceSetup->id)); + + m_practiceSetups.Add(practiceSetup->id, practiceSetup); + m_practiceSetupsByChartId.Add(practiceSetup->chartId, practiceSetup); + } + else + { + Log("Failed to retrieve rowid for UpdateOrAddPracticeSetup", Logger::Severity::Warning); + } + + rowidStatement.Rewind(); + } + } + else + { + Log("Failed to execute query for UpdateOrAddPracticeSetup", Logger::Severity::Warning); + } + + statement.Rewind(); + + m_database.Exec("END"); + } + + void UpdateChartOffset(const ChartIndex* chart) + { + // Safe from sqli bc hash will be alphanum + m_database.Exec(Utility::Sprintf("UPDATE Charts SET custom_offset=%d WHERE hash LIKE '%s'", chart->custom_offset, *chart->hash)); + } + + void AddOrRemoveToCollection(const String& name, int32 mapid) + { + DBStatement addColl = m_database.Query("INSERT INTO Collections(folderid,collection) VALUES(?,?)"); + m_database.Exec("BEGIN"); + + addColl.BindInt(1, mapid); + addColl.BindString(2, name); + + bool result = addColl.Step(); + addColl.Rewind(); + + m_database.Exec("END"); + + if (!result) // Failed to add, try to remove + { + DBStatement remColl = m_database.Query("DELETE FROM collections WHERE folderid==? AND collection==?"); + remColl.BindInt(1, mapid); + remColl.BindString(2, name); + remColl.Step(); + remColl.Rewind(); + } + } + + ChartIndex* GetRandomChart() + { + auto it = m_charts.begin(); + uint32 selection = Random::IntRange(0, (int32)m_charts.size() - 1); + std::advance(it, selection); + return it->second; + } + const auto& GetFolderMap() + { + assert(!m_searching); // Unsafe when searching + return m_folders; + } + const auto& GetChartMap() + { + assert(!m_searching); // Unsafe when searching + return m_charts; + } + const auto& GetChallengeMap() + { + assert(!m_searching); // Unsafe when searching + return m_challenges; + } + + // TODO: Research thread pausing more + // ugly but should work + void PauseSearching() + { + if (m_paused.load()) + return; + + m_paused.store(true); + } + + void ResumeSearching() + { + if (!m_paused.load()) + return; + + m_paused.store(false); + m_cvPause.notify_all(); + } + + void SetChartUpdateBehavior(bool transferScores) + { + m_transferScores = transferScores; + } private: - void m_CleanupMapIndex() - { - for(auto m : m_folders) - { - delete m.second; - } - for(auto m : m_charts) - { - for (auto s : m.second->scores) - { - delete s; - } - m.second->scores.clear(); - delete m.second; - } - for (auto m : m_practiceSetups) - { - delete m.second; - } - m_folders.clear(); - m_charts.clear(); - m_practiceSetups.clear(); - m_practiceSetupsByChartId.clear(); - } - void m_CreateTables() - { - m_database.Exec("DROP TABLE IF EXISTS Folders"); - m_database.Exec("DROP TABLE IF EXISTS Charts"); - m_database.Exec("DROP TABLE IF EXISTS Scores"); - m_database.Exec("DROP TABLE IF EXISTS Collections"); - - m_database.Exec("CREATE TABLE Folders" - "(path TEXT)"); - - m_database.Exec("CREATE TABLE Charts" - "(folderid INTEGER," - "title TEXT," - "artist TEXT," - "title_translit TEXT," - "artist_translit TEXT," - "jacket_path TEXT," - "effector TEXT," - "illustrator TEXT," - "diff_name TEXT," - "diff_shortname TEXT," - "path TEXT," - "bpm TEXT," - "diff_index INTEGER," - "level INTEGER," - "preview_offset INTEGER," - "preview_length INTEGER," - "lwt INTEGER," - "hash TEXT," - "preview_file TEXT," - "custom_offset INTEGER, " - "FOREIGN KEY(folderid) REFERENCES folders(rowid))"); - - m_database.Exec("CREATE TABLE Scores" - "(score INTEGER," - "crit INTEGER," - "near INTEGER," - "early INTEGER," - "late INTEGER," - "combo INTEGER," - "miss INTEGER," - "gauge REAL," - "gauge_type INTEGER," - "gauge_opt INTEGER," - "auto_flags INTEGER," - "mirror INTEGER," - "random INTEGER," - "timestamp INTEGER," - "replay TEXT," - "user_name TEXT," - "user_id TEXT," - "local_score INTEGER," - "window_perfect INTEGER," - "window_good INTEGER," - "window_hold INTEGER," - "window_miss INTEGER," - "window_slam INTEGER," - "chart_hash TEXT)"); - - m_database.Exec("CREATE TABLE Collections" - "(collection TEXT, folderid INTEGER, " - "UNIQUE(collection,folderid), " - "FOREIGN KEY(folderid) REFERENCES Folders(rowid))"); - - m_database.Exec("CREATE TABLE PracticeSetups (" - "chart_id INTEGER," - "setup_title TEXT," - "loop_success INTEGER," - "loop_fail INTEGER," - "range_begin INTEGER," - "range_end INTEGER," - "fail_cond_type INTEGER," - "fail_cond_value INTEGER," - "playback_speed REAL," - "inc_speed_on_success INTEGER," - "inc_speed REAL," - "inc_streak INTEGER," - "dec_speed_on_fail INTEGER," - "dec_speed REAL," - "min_playback_speed REAL," - "max_rewind INTEGER," - "max_rewind_measure INTEGER," - "FOREIGN KEY(chart_id) REFERENCES Charts(rowid)" - ")"); - - m_database.Exec("CREATE TABLE Challenges" - "(" - "title TEXT," - "charts TEXT," - "chart_meta TEXT," // used for search - "clear_mark INTEGER," - "best_score INTEGER," - "req_text TEXT," - "path TEXT," - "hash TEXT," - "level INTEGER," - "lwt INTEGER" - ")"); - } - void m_LoadInitialData() - { - assert(!m_searching); - - // Clear search state - m_searchState.difficulties.clear(); - - // Scan original maps - m_CleanupMapIndex(); - - // Select Maps - DBStatement mapScan = m_database.Query("SELECT rowid, path FROM Folders"); - while(mapScan.StepRow()) - { - FolderIndex* folder = new FolderIndex(); - folder->id = mapScan.IntColumn(0); - folder->path = mapScan.StringColumn(1); - folder->selectId = (int32) m_folders.size(); - m_folders.Add(folder->id, folder); - m_foldersByPath.Add(folder->path, folder); - } - m_nextFolderId = m_folders.empty() ? 1 : (m_folders.rbegin()->first + 1); - - // Select Difficulties - DBStatement chartScan = m_database.Query("SELECT rowid" - ",folderId" - ",path" - ",title" - ",artist" - ",title_translit" - ",artist_translit" - ",jacket_path" - ",effector" - ",illustrator" - ",diff_name" - ",diff_shortname" - ",bpm" - ",diff_index" - ",level" - ",hash" - ",preview_file" - ",preview_offset" - ",preview_length" - ",lwt" - ",custom_offset " - "FROM Charts"); - while(chartScan.StepRow()) - { - ChartIndex* chart = new ChartIndex(); - chart->id = chartScan.IntColumn(0); - chart->folderId = chartScan.IntColumn(1); - chart->path = chartScan.StringColumn(2); - chart->title = chartScan.StringColumn(3); - chart->artist = chartScan.StringColumn(4); - chart->title_translit = chartScan.StringColumn(5); - chart->artist_translit = chartScan.StringColumn(6); - chart->jacket_path = chartScan.StringColumn(7); - chart->effector = chartScan.StringColumn(8); - chart->illustrator = chartScan.StringColumn(9); - chart->diff_name = chartScan.StringColumn(10); - chart->diff_shortname = chartScan.StringColumn(11); - chart->bpm = chartScan.StringColumn(12); - chart->diff_index = chartScan.IntColumn(13); - chart->level = chartScan.IntColumn(14); - chart->hash = chartScan.StringColumn(15); - chart->preview_file = chartScan.StringColumn(16); - chart->preview_offset = chartScan.IntColumn(17); - chart->preview_length = chartScan.IntColumn(18); - chart->lwt = chartScan.Int64Column(19); - chart->custom_offset = chartScan.IntColumn(20); - - // Add existing diff - m_charts.Add(chart->id, chart); - m_chartsByHash.Add(chart->hash, chart); - - // Add difficulty to map and resort difficulties - auto folderIt = m_folders.find(chart->folderId); - assert(folderIt != m_folders.end()); - folderIt->second->charts.Add(chart); - m_SortCharts(folderIt->second); - - // Add to search state - SearchState::ExistingFileEntry ed; - ed.id = chart->id; - if (chart->hash.length() == 0) - { - ed.lwt = 0; - } - else { - ed.lwt = chart->lwt; - - } - m_searchState.difficulties.Add(chart->path, ed); - } - - // Select Scores - DBStatement scoreScan = m_database.Query("SELECT " - "rowid,score,crit,near,early,late,combo,miss,gauge,auto_flags,replay,timestamp,chart_hash,user_name,user_id,local_score,window_perfect,window_good,window_hold,window_miss,window_slam,gauge_type,gauge_opt,mirror,random " - "FROM Scores"); - - while (scoreScan.StepRow()) - { - ScoreIndex* score = new ScoreIndex(); - score->id = scoreScan.IntColumn(0); - score->score = scoreScan.IntColumn(1); - score->crit = scoreScan.IntColumn(2); - score->almost = scoreScan.IntColumn(3); - score->early = scoreScan.IntColumn(4); - score->late = scoreScan.IntColumn(5); - score->combo = scoreScan.IntColumn(6); - score->miss = scoreScan.IntColumn(7); - score->gauge = (float) scoreScan.DoubleColumn(8); - score->autoFlags = (AutoFlags)scoreScan.IntColumn(9); - score->replayPath = scoreScan.StringColumn(10); - - score->timestamp = scoreScan.Int64Column(11); - score->chartHash = scoreScan.StringColumn(12); - score->userName = scoreScan.StringColumn(13); - score->userId = scoreScan.StringColumn(14); - score->localScore = scoreScan.IntColumn(15); - - score->hitWindowPerfect = scoreScan.IntColumn(16); - score->hitWindowGood = scoreScan.IntColumn(17); - score->hitWindowHold = scoreScan.IntColumn(18); - score->hitWindowMiss = scoreScan.IntColumn(19); - score->hitWindowSlam = scoreScan.IntColumn(20); - - score->gaugeType = (GaugeType)scoreScan.IntColumn(21); - score->gaugeOption = scoreScan.IntColumn(22); - score->mirror = scoreScan.IntColumn(23) == 1; - score->random = scoreScan.IntColumn(24) == 1; - - // Add difficulty to map and resort difficulties - auto diffIt = m_chartsByHash.find(score->chartHash); - if (diffIt == m_chartsByHash.end()) // If for whatever reason the diff that the score is attatched to is not in the db, ignore the score. - { - delete score; - continue; - } - - diffIt->second->scores.Add(score); - m_SortScores(diffIt->second); - } - - // Select Practice setups - DBStatement practiceSetupScan = m_database.Query("SELECT rowid, chart_id, setup_title, loop_success, loop_fail, range_begin, range_end, fail_cond_type, fail_cond_value," - "playback_speed, inc_speed_on_success, inc_speed, inc_streak, dec_speed_on_fail, dec_speed, min_playback_speed, max_rewind, max_rewind_measure FROM PracticeSetups"); - - while (practiceSetupScan.StepRow()) - { - PracticeSetupIndex* practiceSetup = new PracticeSetupIndex(); - practiceSetup->id = practiceSetupScan.IntColumn(0); - practiceSetup->chartId = practiceSetupScan.IntColumn(1); - practiceSetup->setupTitle = practiceSetupScan.StringColumn(2); - practiceSetup->loopSuccess = practiceSetupScan.IntColumn(3); - practiceSetup->loopFail = practiceSetupScan.IntColumn(4); - practiceSetup->rangeBegin = practiceSetupScan.IntColumn(5); - practiceSetup->rangeEnd = practiceSetupScan.IntColumn(6); - practiceSetup->failCondType = practiceSetupScan.IntColumn(7); - practiceSetup->failCondValue = practiceSetupScan.IntColumn(8); - - practiceSetup->playbackSpeed = practiceSetupScan.DoubleColumn(9); - practiceSetup->incSpeedOnSuccess = practiceSetupScan.IntColumn(10); - practiceSetup->incSpeed = practiceSetupScan.DoubleColumn(11); - practiceSetup->incStreak = practiceSetupScan.IntColumn(12); - practiceSetup->decSpeedOnFail = practiceSetupScan.IntColumn(13); - practiceSetup->decSpeed = practiceSetupScan.DoubleColumn(14); - practiceSetup->minPlaybackSpeed = practiceSetupScan.DoubleColumn(15); - practiceSetup->maxRewind = practiceSetupScan.IntColumn(16); - practiceSetup->maxRewindMeasure = practiceSetupScan.IntColumn(17); - - if (!m_charts.Contains(practiceSetup->chartId)) - { - delete practiceSetup; - continue; - } - - m_practiceSetups.Add(practiceSetup->id, practiceSetup); - m_practiceSetupsByChartId.Add(practiceSetup->chartId, practiceSetup); - } - - m_outer.OnFoldersCleared.Call(m_folders); - - DBStatement chalScan = m_database.Query("SELECT rowid" - ",title" - ",charts" - ",clear_mark" - ",best_score" - ",req_text" - ",path" - ",hash" - ",level" - ",lwt" - " FROM Challenges"); - while (chalScan.StepRow()) - { - ChallengeIndex* chal = new ChallengeIndex(); - chal->id = chalScan.IntColumn(0); - chal->title = chalScan.StringColumn(1); - String chartsString = chalScan.StringColumn(2); - chal->clearMark = chalScan.IntColumn(3); - chal->bestScore = chalScan.IntColumn(4); - chal->reqText = chalScan.StringColumnEmptyOnNull(5); - chal->path = chalScan.StringColumn(6); - chal->hash = chalScan.StringColumn(7); - chal->level = chalScan.IntColumn(8); - chal->lwt = chalScan.Int64Column(9); - chal->missingChart = false; - chal->charts.clear(); - - nlohmann::json charts = nlohmann::json::parse(chartsString, nullptr, false); - chal->FindCharts(&m_outer, charts); - - if (chal->charts.size() == 0) - { - Logf("Unable to parse charts for challenge %s in database", Logger::Severity::Warning, chal->path); - // Try to reload from the file - chal->lwt = 0; - } - - // If we don't have req text, update - if (chal->reqText == "") - chal->lwt = 0; - - m_challenges.Add(chal->id, chal); - - // Add to search state - SearchState::ExistingFileEntry ed; - ed.id = chal->id; - if (chal->hash.length() == 0) - { - ed.lwt = 0; - } - else { - ed.lwt = chal->lwt; - - } - m_searchState.challenges.Add(chal->path, ed); - } - m_nextChalId = m_challenges.empty() ? 1 : (m_challenges.rbegin()->first + 1); - - m_outer.OnChallengesCleared.Call(m_challenges); - } - void m_SortCharts(FolderIndex* folderIndex) - { - folderIndex->charts.Sort([](ChartIndex* a, ChartIndex* b) - { - if (a->diff_index == b->diff_index) - return a->level < b->level; - return a->diff_index < b->diff_index; - }); - } - - void m_SortScores(ChartIndex* diffIndex) - { - diffIndex->scores.Sort([](ScoreIndex* a, ScoreIndex* b) - { - return a->score > b->score; - }); - } - - // Main search thread - void m_SearchThread() - { - Map fileList; - Map challengeFileList; - Map legacyChallengeFileList; - { - ProfilerScope $("Chart Database - Enumerate Files and Charts"); - m_outer.OnSearchStatusUpdated.Call("[START] Chart Database - Enumerate Files and Folders"); - for(String rootSearchPath : m_searchPaths) - { - Vector exts(3); - exts[0] = "ksh"; - exts[1] = "chal"; - exts[2] = "kco"; - Map> files = Files::ScanFilesRecursive(rootSearchPath, exts, &m_interruptSearch); - if(m_interruptSearch) - return; - for(FileInfo& fi : files["ksh"]) - { - fileList.Add(fi.fullPath, fi); - } - for(FileInfo& fi : files["chal"]) - { - challengeFileList.Add(fi.fullPath, fi); - } - for(FileInfo& fi : files["kco"]) - { - legacyChallengeFileList.Add(fi.fullPath, fi); - } - } - m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Enumerate Files and Folders"); - } - - { - ProfilerScope $("Chart Database - Process Removed Charts"); - m_outer.OnSearchStatusUpdated.Call("[START] Chart Database - Process Removed Charts"); - // Process scanned files - for(auto f : m_searchState.difficulties) - { - if(!fileList.Contains(f.first)) - { - Event evt; - evt.type = Event::Chart; - evt.action = Event::Removed; - evt.path = f.first; - evt.id = f.second.id; - AddChange(evt); - } - } - m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Process Removed Charts"); - } - - { - ProfilerScope $("Chart Database - Process New Charts"); - m_outer.OnSearchStatusUpdated.Call("[START] Chart Database - Process New Charts"); - // Process scanned files - for(auto f : fileList) - { - if (m_paused.load()) - { - unique_lock lock(m_pauseMutex); - m_cvPause.wait(lock); - } - - if(!m_searching) - break; - - uint64 mylwt = f.second.lastWriteTime; - Event evt; - evt.type = Event::Chart; - evt.lwt = mylwt; - - SearchState::ExistingFileEntry* existing = m_searchState.difficulties.Find(f.first); - if(existing) - { - evt.id = existing->id; - if(existing->lwt != mylwt) - { - // Map Updated - evt.action = Event::Updated; - } - else - { - // Skip, not changed - continue; - } - } - else - { - // Map added - evt.action = Event::Added; - } - - Logf("Discovered Chart [%s]", Logger::Severity::Info, f.first); - m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Discovered Chart [%s]", f.first)); - // Try to read map metadata - bool mapValid = false; - File fileStream; - Beatmap map; - if(fileStream.OpenRead(f.first)) - { - FileReader reader(fileStream); - - if(map.Load(reader, true)) - { - mapValid = true; - } - } - - if(mapValid) - { - fileStream.Seek(0); - evt.mapData = new BeatmapSettings(map.GetMapSettings()); - - ProfilerScope $("Chart Database - Hash Chart"); - char data_buffer[0x80]; - uint32_t digest[5]; - sha1::SHA1 s; - - size_t amount_read = 0; - size_t read_size; - do - { - read_size = fileStream.Read(data_buffer, sizeof(data_buffer)); - amount_read += read_size; - s.processBytes(data_buffer, read_size); - } - while (read_size != 0); - - s.getDigest(digest); - - evt.hash = Utility::Sprintf("%08x%08x%08x%08x%08x", digest[0], digest[1], digest[2], digest[3], digest[4]); - } - - if (!mapValid) - { - if(!existing) // Never added - { - Logf("Skipping corrupted chart [%s]", Logger::Severity::Warning, f.first); - m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Skipping corrupted chart [%s]", f.first)); - if(evt.mapData) - delete evt.mapData; - evt.mapData = nullptr; - continue; - } - // XXX does remove actually use / free mapData - // Invalid maps get removed from the database - evt.action = Event::Removed; - } - evt.path = f.first; - AddChange(evt); - continue; - } - m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Process New Charts"); - } - m_outer.OnSearchStatusUpdated.Call(""); - - { - ProfilerScope $("Chart Database - Process Removed Challenges"); - m_outer.OnSearchStatusUpdated.Call("[START] Chart Database - Process Removed Challenges"); - // Process scanned files - for(auto f : m_searchState.challenges) - { - if(!challengeFileList.Contains(f.first)) - { - Event evt; - evt.type = Event::Challenge; - evt.action = Event::Removed; - evt.path = f.first; - evt.id = f.second.id; - AddChange(evt); - } - } - m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Process Removed Challenges"); - } - - if (legacyChallengeFileList.size() > 0) - { - bool addedNewJson = false; - ProfilerScope $("Chart Database - Converting Legacy Challenges"); - for (auto f : legacyChallengeFileList) - { - if (m_paused.load()) - { - unique_lock lock(m_pauseMutex); - m_cvPause.wait(lock); - } - - if (!m_searching) - break; - - String newName = f.first + ".chal"; - // XXX if the kco was modified then after converting, it will not be updated - if (Path::FileExists(newName)) - { - // If we already did a convert, check if the kco has been updated - uint64 mylwt = f.second.lastWriteTime; - FileInfo* conv = challengeFileList.Find(newName); - if (conv != nullptr && conv->lastWriteTime >= mylwt) - { - // No update - continue; - } - } - Logf("Converting legacy KShoot course %s", Logger::Severity::Info, f.first); - - File legacyFile; - if (!legacyFile.OpenRead(f.first)) - { - m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Unable to open KShoot course [%s]", f.first)); - continue; - } - - FileReader legacyReader(legacyFile); - Map courseSettings; - Vector courseCharts; - if (!ParseKShootCourse(legacyReader, courseSettings, courseCharts) - || !courseSettings.Contains("title") - || courseCharts.size() == 0) - { - m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Skipping corrupted KShoot course [%s]", f.first)); - continue; - } - - nlohmann::json newJson = "{\"charts\":[], \"level\":0, \"global\":{\"clear\":true}}"_json; - newJson["title"] = courseSettings["title"]; - for (const String& chart : courseCharts) - newJson["charts"].push_back(chart); - - File newJsonFile; - if (!newJsonFile.OpenWrite(newName)) - { - m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Unable to open KShoot course json file [%s]", newName)); - continue; - } - String jsonData = newJson.dump(4); - newJsonFile.Write(*jsonData, jsonData.length()); - addedNewJson = true; - } - - // If we added a json file we have to rescan for chals, this only happens when converting legacy courses - if (addedNewJson) - { - for (String rootSearchPath : m_searchPaths) - { - challengeFileList.clear(); - Vector files = Files::ScanFilesRecursive(rootSearchPath, "chal", &m_interruptSearch); - if (m_interruptSearch) - return; - for (FileInfo& fi : files) - { - challengeFileList.Add(fi.fullPath, fi); - } - } - } - m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Converting Legacy Challenges"); - } - - if (challengeFileList.size() > 0) - { - ProfilerScope $("Chart Database - Process New Challenges"); - m_outer.OnSearchStatusUpdated.Call("[START] Chart Database - Process New Challenges"); - // Process scanned files - for (auto f : challengeFileList) - { - if (m_paused.load()) - { - unique_lock lock(m_pauseMutex); - m_cvPause.wait(lock); - } - - if (!m_searching) - break; - - uint64 mylwt = f.second.lastWriteTime; - Event evt; - evt.type = Event::Challenge; - evt.lwt = mylwt; - - SearchState::ExistingFileEntry* existing = m_searchState.challenges.Find(f.first); - if (existing) - { - evt.id = existing->id; - if (existing->lwt != mylwt) - { - // Challenge Updated - evt.action = Event::Updated; - } - else - { - // Skip, not changed - continue; - } - } - else - { - // Challenge added - evt.action = Event::Added; - } - - if (existing) - Logf("Discovered Updated Challenge [%s]", Logger::Severity::Info, f.first); - else - Logf("Discovered Challenge [%s]", Logger::Severity::Info, f.first); - m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Discovered Challenge [%s]", f.first)); - - bool chalValid = true; - // TODO support old style courses - nlohmann::json settings; - - File chalFile; - if (!chalFile.OpenRead(f.first)) - chalValid = false; - - if (chalValid) - { - // TODO support old style courses - Buffer jsonBuf; - jsonBuf.resize(chalFile.GetSize()); - chalFile.Read(jsonBuf.data(), jsonBuf.size()); - settings = ChallengeIndex::LoadJson(jsonBuf, f.first); - chalValid = ChallengeIndex::BasicValidate(settings, f.first); - - if (chalValid) - { - sha1::SHA1 s; - - s.processBytes(jsonBuf.data(), jsonBuf.size()); - - uint32_t digest[5]; - s.getDigest(digest); - - evt.hash = Utility::Sprintf("%08x%08x%08x%08x%08x", digest[0], digest[1], digest[2], digest[3], digest[4]); - - } - } - - if (!chalValid) - { - // Reset json entry - evt.json = nlohmann::json(); - - if(!existing) // Never added - { - Logf("Skipping corrupted challenge [%s]", Logger::Severity::Warning, f.first); - m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Skipping corrupted challenge [%s]", f.first)); - continue; - } - // Invalid chals get removed from the database - evt.action = Event::Removed; - } - else - { - evt.json = settings; - } - evt.path = f.first; - AddChange(evt); - continue; - } - m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Process New Challenges"); - } - m_outer.OnSearchStatusUpdated.Call(""); - - m_searching = false; - } - + void m_CleanupMapIndex() + { + for (auto m : m_folders) + { + delete m.second; + } + for (auto m : m_charts) + { + for (auto s : m.second->scores) + { + delete s; + } + m.second->scores.clear(); + delete m.second; + } + for (auto m : m_practiceSetups) + { + delete m.second; + } + m_folders.clear(); + m_charts.clear(); + m_practiceSetups.clear(); + m_practiceSetupsByChartId.clear(); + } + void m_CreateTables() + { + m_database.Exec("DROP TABLE IF EXISTS Folders"); + m_database.Exec("DROP TABLE IF EXISTS Charts"); + m_database.Exec("DROP TABLE IF EXISTS Scores"); + m_database.Exec("DROP TABLE IF EXISTS Collections"); + + m_database.Exec("CREATE TABLE Folders" + "(path TEXT)"); + + m_database.Exec("CREATE TABLE Charts" + "(folderid INTEGER," + "title TEXT," + "artist TEXT," + "title_translit TEXT," + "artist_translit TEXT," + "jacket_path TEXT," + "effector TEXT," + "illustrator TEXT," + "diff_name TEXT," + "diff_shortname TEXT," + "path TEXT," + "bpm TEXT," + "diff_index INTEGER," + "level INTEGER," + "preview_offset INTEGER," + "preview_length INTEGER," + "lwt INTEGER," + "hash TEXT," + "preview_file TEXT," + "custom_offset INTEGER, " + "FOREIGN KEY(folderid) REFERENCES folders(rowid))"); + + m_database.Exec("CREATE TABLE Scores" + "(score INTEGER," + "crit INTEGER," + "near INTEGER," + "early INTEGER," + "late INTEGER," + "combo INTEGER," + "miss INTEGER," + "gauge REAL," + "gauge_type INTEGER," + "gauge_opt INTEGER," + "auto_flags INTEGER," + "mirror INTEGER," + "random INTEGER," + "timestamp INTEGER," + "replay TEXT," + "user_name TEXT," + "user_id TEXT," + "local_score INTEGER," + "window_perfect INTEGER," + "window_good INTEGER," + "window_hold INTEGER," + "window_miss INTEGER," + "window_slam INTEGER," + "chart_hash TEXT)"); + + m_database.Exec("CREATE TABLE Collections" + "(collection TEXT, folderid INTEGER, " + "UNIQUE(collection,folderid), " + "FOREIGN KEY(folderid) REFERENCES Folders(rowid))"); + + m_database.Exec("CREATE TABLE PracticeSetups (" + "chart_id INTEGER," + "setup_title TEXT," + "loop_success INTEGER," + "loop_fail INTEGER," + "range_begin INTEGER," + "range_end INTEGER," + "fail_cond_type INTEGER," + "fail_cond_value INTEGER," + "playback_speed REAL," + "inc_speed_on_success INTEGER," + "inc_speed REAL," + "inc_streak INTEGER," + "dec_speed_on_fail INTEGER," + "dec_speed REAL," + "min_playback_speed REAL," + "max_rewind INTEGER," + "max_rewind_measure INTEGER," + "FOREIGN KEY(chart_id) REFERENCES Charts(rowid)" + ")"); + + m_database.Exec("CREATE TABLE Challenges" + "(" + "title TEXT," + "charts TEXT," + "chart_meta TEXT," // used for search + "clear_mark INTEGER," + "best_score INTEGER," + "req_text TEXT," + "path TEXT," + "hash TEXT," + "level INTEGER," + "lwt INTEGER" + ")"); + } + void m_LoadInitialData() + { + assert(!m_searching); + + // Clear search state + m_searchState.difficulties.clear(); + + // Scan original maps + m_CleanupMapIndex(); + + // Select Maps + DBStatement mapScan = m_database.Query("SELECT rowid, path FROM Folders"); + while (mapScan.StepRow()) + { + FolderIndex* folder = new FolderIndex(); + folder->id = mapScan.IntColumn(0); + folder->path = mapScan.StringColumn(1); + folder->selectId = (int32)m_folders.size(); + m_folders.Add(folder->id, folder); + m_foldersByPath.Add(folder->path, folder); + } + m_nextFolderId = m_folders.empty() ? 1 : (m_folders.rbegin()->first + 1); + + // Select Difficulties + DBStatement chartScan = m_database.Query("SELECT rowid" + ",folderId" + ",path" + ",title" + ",artist" + ",title_translit" + ",artist_translit" + ",jacket_path" + ",effector" + ",illustrator" + ",diff_name" + ",diff_shortname" + ",bpm" + ",diff_index" + ",level" + ",hash" + ",preview_file" + ",preview_offset" + ",preview_length" + ",lwt" + ",custom_offset " + "FROM Charts"); + while (chartScan.StepRow()) + { + ChartIndex* chart = new ChartIndex(); + chart->id = chartScan.IntColumn(0); + chart->folderId = chartScan.IntColumn(1); + chart->path = chartScan.StringColumn(2); + chart->title = chartScan.StringColumn(3); + chart->artist = chartScan.StringColumn(4); + chart->title_translit = chartScan.StringColumn(5); + chart->artist_translit = chartScan.StringColumn(6); + chart->jacket_path = chartScan.StringColumn(7); + chart->effector = chartScan.StringColumn(8); + chart->illustrator = chartScan.StringColumn(9); + chart->diff_name = chartScan.StringColumn(10); + chart->diff_shortname = chartScan.StringColumn(11); + chart->bpm = chartScan.StringColumn(12); + chart->diff_index = chartScan.IntColumn(13); + chart->level = chartScan.IntColumn(14); + chart->hash = chartScan.StringColumn(15); + chart->preview_file = chartScan.StringColumn(16); + chart->preview_offset = chartScan.IntColumn(17); + chart->preview_length = chartScan.IntColumn(18); + chart->lwt = chartScan.Int64Column(19); + chart->custom_offset = chartScan.IntColumn(20); + + // Add existing diff + m_charts.Add(chart->id, chart); + m_chartsByHash.Add(chart->hash, chart); + + // Add difficulty to map and resort difficulties + auto folderIt = m_folders.find(chart->folderId); + assert(folderIt != m_folders.end()); + folderIt->second->charts.Add(chart); + m_SortCharts(folderIt->second); + + // Add to search state + SearchState::ExistingFileEntry ed; + ed.id = chart->id; + if (chart->hash.length() == 0) + { + ed.lwt = 0; + } + else + { + ed.lwt = chart->lwt; + } + m_searchState.difficulties.Add(chart->path, ed); + } + + // Select Scores + DBStatement scoreScan = m_database.Query("SELECT " + "rowid,score,crit,near,early,late,combo,miss,gauge,auto_flags,replay,timestamp,chart_hash,user_name,user_id,local_score,window_perfect,window_good,window_hold,window_miss,window_slam,gauge_type,gauge_opt,mirror,random " + "FROM Scores"); + + while (scoreScan.StepRow()) + { + ScoreIndex* score = new ScoreIndex(); + score->id = scoreScan.IntColumn(0); + score->score = scoreScan.IntColumn(1); + score->crit = scoreScan.IntColumn(2); + score->almost = scoreScan.IntColumn(3); + score->early = scoreScan.IntColumn(4); + score->late = scoreScan.IntColumn(5); + score->combo = scoreScan.IntColumn(6); + score->miss = scoreScan.IntColumn(7); + score->gauge = (float)scoreScan.DoubleColumn(8); + score->autoFlags = (AutoFlags)scoreScan.IntColumn(9); + score->replayPath = scoreScan.StringColumn(10); + + score->timestamp = scoreScan.Int64Column(11); + score->chartHash = scoreScan.StringColumn(12); + score->userName = scoreScan.StringColumn(13); + score->userId = scoreScan.StringColumn(14); + score->localScore = scoreScan.IntColumn(15); + + score->hitWindowPerfect = scoreScan.IntColumn(16); + score->hitWindowGood = scoreScan.IntColumn(17); + score->hitWindowHold = scoreScan.IntColumn(18); + score->hitWindowMiss = scoreScan.IntColumn(19); + score->hitWindowSlam = scoreScan.IntColumn(20); + + score->gaugeType = (GaugeType)scoreScan.IntColumn(21); + score->gaugeOption = scoreScan.IntColumn(22); + score->mirror = scoreScan.IntColumn(23) == 1; + score->random = scoreScan.IntColumn(24) == 1; + + // Add difficulty to map and resort difficulties + auto diffIt = m_chartsByHash.find(score->chartHash); + if (diffIt == m_chartsByHash.end()) // If for whatever reason the diff that the score is attatched to is not in the db, ignore the score. + { + delete score; + continue; + } + + diffIt->second->scores.Add(score); + m_SortScores(diffIt->second); + } + + // Select Practice setups + DBStatement practiceSetupScan = m_database.Query("SELECT rowid, chart_id, setup_title, loop_success, loop_fail, range_begin, range_end, fail_cond_type, fail_cond_value," + "playback_speed, inc_speed_on_success, inc_speed, inc_streak, dec_speed_on_fail, dec_speed, min_playback_speed, max_rewind, max_rewind_measure FROM PracticeSetups"); + + while (practiceSetupScan.StepRow()) + { + PracticeSetupIndex* practiceSetup = new PracticeSetupIndex(); + practiceSetup->id = practiceSetupScan.IntColumn(0); + practiceSetup->chartId = practiceSetupScan.IntColumn(1); + practiceSetup->setupTitle = practiceSetupScan.StringColumn(2); + practiceSetup->loopSuccess = practiceSetupScan.IntColumn(3); + practiceSetup->loopFail = practiceSetupScan.IntColumn(4); + practiceSetup->rangeBegin = practiceSetupScan.IntColumn(5); + practiceSetup->rangeEnd = practiceSetupScan.IntColumn(6); + practiceSetup->failCondType = practiceSetupScan.IntColumn(7); + practiceSetup->failCondValue = practiceSetupScan.IntColumn(8); + + practiceSetup->playbackSpeed = practiceSetupScan.DoubleColumn(9); + practiceSetup->incSpeedOnSuccess = practiceSetupScan.IntColumn(10); + practiceSetup->incSpeed = practiceSetupScan.DoubleColumn(11); + practiceSetup->incStreak = practiceSetupScan.IntColumn(12); + practiceSetup->decSpeedOnFail = practiceSetupScan.IntColumn(13); + practiceSetup->decSpeed = practiceSetupScan.DoubleColumn(14); + practiceSetup->minPlaybackSpeed = practiceSetupScan.DoubleColumn(15); + practiceSetup->maxRewind = practiceSetupScan.IntColumn(16); + practiceSetup->maxRewindMeasure = practiceSetupScan.IntColumn(17); + + if (!m_charts.Contains(practiceSetup->chartId)) + { + delete practiceSetup; + continue; + } + + m_practiceSetups.Add(practiceSetup->id, practiceSetup); + m_practiceSetupsByChartId.Add(practiceSetup->chartId, practiceSetup); + } + + m_outer.OnFoldersCleared.Call(m_folders); + + DBStatement chalScan = m_database.Query("SELECT rowid" + ",title" + ",charts" + ",clear_mark" + ",best_score" + ",req_text" + ",path" + ",hash" + ",level" + ",lwt" + " FROM Challenges"); + while (chalScan.StepRow()) + { + ChallengeIndex* chal = new ChallengeIndex(); + chal->id = chalScan.IntColumn(0); + chal->title = chalScan.StringColumn(1); + String chartsString = chalScan.StringColumn(2); + chal->clearMark = chalScan.IntColumn(3); + chal->bestScore = chalScan.IntColumn(4); + chal->reqText = chalScan.StringColumnEmptyOnNull(5); + chal->path = chalScan.StringColumn(6); + chal->hash = chalScan.StringColumn(7); + chal->level = chalScan.IntColumn(8); + chal->lwt = chalScan.Int64Column(9); + chal->missingChart = false; + chal->charts.clear(); + + nlohmann::json charts = nlohmann::json::parse(chartsString, nullptr, false); + chal->FindCharts(&m_outer, charts); + + if (chal->charts.size() == 0) + { + Logf("Unable to parse charts for challenge %s in database", Logger::Severity::Warning, chal->path); + // Try to reload from the file + chal->lwt = 0; + } + + // If we don't have req text, update + if (chal->reqText == "") + chal->lwt = 0; + + m_challenges.Add(chal->id, chal); + + // Add to search state + SearchState::ExistingFileEntry ed; + ed.id = chal->id; + if (chal->hash.length() == 0) + { + ed.lwt = 0; + } + else + { + ed.lwt = chal->lwt; + } + m_searchState.challenges.Add(chal->path, ed); + } + m_nextChalId = m_challenges.empty() ? 1 : (m_challenges.rbegin()->first + 1); + + m_outer.OnChallengesCleared.Call(m_challenges); + } + void m_SortCharts(FolderIndex* folderIndex) + { + folderIndex->charts.Sort([](ChartIndex* a, ChartIndex* b) + { + if (a->diff_index == b->diff_index) + return a->level < b->level; + return a->diff_index < b->diff_index; }); + } + + void m_SortScores(ChartIndex* diffIndex) + { + diffIndex->scores.Sort([](ScoreIndex* a, ScoreIndex* b) + { return a->score > b->score; }); + } + + // Main search thread + void m_SearchThread() + { + Map fileList; + Map challengeFileList; + Map legacyChallengeFileList; + { + ProfilerScope $("Chart Database - Enumerate Files and Charts"); + m_outer.OnSearchStatusUpdated.Call("[START] Chart Database - Enumerate Files and Folders"); + for (String rootSearchPath : m_searchPaths) + { + Vector exts(3); + exts[0] = "ksh"; + exts[1] = "chal"; + exts[2] = "kco"; + Map> files = Files::ScanFilesRecursive(rootSearchPath, exts, &m_interruptSearch); + if (m_interruptSearch) + return; + for (FileInfo& fi : files["ksh"]) + { + fileList.Add(fi.fullPath, fi); + } + for (FileInfo& fi : files["chal"]) + { + challengeFileList.Add(fi.fullPath, fi); + } + for (FileInfo& fi : files["kco"]) + { + legacyChallengeFileList.Add(fi.fullPath, fi); + } + } + m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Enumerate Files and Folders"); + } + + { + ProfilerScope $("Chart Database - Process Removed Charts"); + m_outer.OnSearchStatusUpdated.Call("[START] Chart Database - Process Removed Charts"); + // Process scanned files + for (auto f : m_searchState.difficulties) + { + if (!fileList.Contains(f.first)) + { + Event evt; + evt.type = Event::Chart; + evt.action = Event::Removed; + evt.path = f.first; + evt.id = f.second.id; + AddChange(evt); + } + } + m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Process Removed Charts"); + } + + { + ProfilerScope $("Chart Database - Process New Charts"); + m_outer.OnSearchStatusUpdated.Call("[START] Chart Database - Process New Charts"); + // Process scanned files + for (auto f : fileList) + { + if (m_paused.load()) + { + unique_lock lock(m_pauseMutex); + m_cvPause.wait(lock); + } + + if (!m_searching) + break; + + uint64 mylwt = f.second.lastWriteTime; + Event evt; + evt.type = Event::Chart; + evt.lwt = mylwt; + + SearchState::ExistingFileEntry* existing = m_searchState.difficulties.Find(f.first); + if (existing) + { + evt.id = existing->id; + if (existing->lwt != mylwt) + { + // Map Updated + evt.action = Event::Updated; + } + else + { + // Skip, not changed + continue; + } + } + else + { + // Map added + evt.action = Event::Added; + } + + Logf("Discovered Chart [%s]", Logger::Severity::Info, f.first); + m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Discovered Chart [%s]", f.first)); + // Try to read map metadata + bool mapValid = false; + File fileStream; + Beatmap map; + if (fileStream.OpenRead(f.first)) + { + FileReader reader(fileStream); + + if (map.Load(reader, true)) + { + mapValid = true; + } + } + + if (mapValid) + { + fileStream.Seek(0); + evt.mapData = new BeatmapSettings(map.GetMapSettings()); + + ProfilerScope $("Chart Database - Hash Chart"); + char data_buffer[0x80]; + uint32_t digest[5]; + sha1::SHA1 s; + + size_t amount_read = 0; + size_t read_size; + do + { + read_size = fileStream.Read(data_buffer, sizeof(data_buffer)); + amount_read += read_size; + s.processBytes(data_buffer, read_size); + } while (read_size != 0); + + s.getDigest(digest); + + evt.hash = Utility::Sprintf("%08x%08x%08x%08x%08x", digest[0], digest[1], digest[2], digest[3], digest[4]); + } + + if (!mapValid) + { + if (!existing) // Never added + { + Logf("Skipping corrupted chart [%s]", Logger::Severity::Warning, f.first); + m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Skipping corrupted chart [%s]", f.first)); + if (evt.mapData) + delete evt.mapData; + evt.mapData = nullptr; + continue; + } + // XXX does remove actually use / free mapData + // Invalid maps get removed from the database + evt.action = Event::Removed; + } + evt.path = f.first; + AddChange(evt); + continue; + } + m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Process New Charts"); + } + m_outer.OnSearchStatusUpdated.Call(""); + + { + ProfilerScope $("Chart Database - Process Removed Challenges"); + m_outer.OnSearchStatusUpdated.Call("[START] Chart Database - Process Removed Challenges"); + // Process scanned files + for (auto f : m_searchState.challenges) + { + if (!challengeFileList.Contains(f.first)) + { + Event evt; + evt.type = Event::Challenge; + evt.action = Event::Removed; + evt.path = f.first; + evt.id = f.second.id; + AddChange(evt); + } + } + m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Process Removed Challenges"); + } + + if (legacyChallengeFileList.size() > 0) + { + bool addedNewJson = false; + ProfilerScope $("Chart Database - Converting Legacy Challenges"); + for (auto f : legacyChallengeFileList) + { + if (m_paused.load()) + { + unique_lock lock(m_pauseMutex); + m_cvPause.wait(lock); + } + + if (!m_searching) + break; + + String newName = f.first + ".chal"; + // XXX if the kco was modified then after converting, it will not be updated + if (Path::FileExists(newName)) + { + // If we already did a convert, check if the kco has been updated + uint64 mylwt = f.second.lastWriteTime; + FileInfo* conv = challengeFileList.Find(newName); + if (conv != nullptr && conv->lastWriteTime >= mylwt) + { + // No update + continue; + } + } + Logf("Converting legacy KShoot course %s", Logger::Severity::Info, f.first); + + File legacyFile; + if (!legacyFile.OpenRead(f.first)) + { + m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Unable to open KShoot course [%s]", f.first)); + continue; + } + + FileReader legacyReader(legacyFile); + Map courseSettings; + Vector courseCharts; + if (!ParseKShootCourse(legacyReader, courseSettings, courseCharts) || !courseSettings.Contains("title") || courseCharts.size() == 0) + { + m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Skipping corrupted KShoot course [%s]", f.first)); + continue; + } + + nlohmann::json newJson = "{\"charts\":[], \"level\":0, \"global\":{\"clear\":true}}"_json; + newJson["title"] = courseSettings["title"]; + for (const String& chart : courseCharts) + newJson["charts"].push_back(chart); + + File newJsonFile; + if (!newJsonFile.OpenWrite(newName)) + { + m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Unable to open KShoot course json file [%s]", newName)); + continue; + } + String jsonData = newJson.dump(4); + newJsonFile.Write(*jsonData, jsonData.length()); + addedNewJson = true; + } + + // If we added a json file we have to rescan for chals, this only happens when converting legacy courses + if (addedNewJson) + { + for (String rootSearchPath : m_searchPaths) + { + challengeFileList.clear(); + Vector files = Files::ScanFilesRecursive(rootSearchPath, "chal", &m_interruptSearch); + if (m_interruptSearch) + return; + for (FileInfo& fi : files) + { + challengeFileList.Add(fi.fullPath, fi); + } + } + } + m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Converting Legacy Challenges"); + } + + if (challengeFileList.size() > 0) + { + ProfilerScope $("Chart Database - Process New Challenges"); + m_outer.OnSearchStatusUpdated.Call("[START] Chart Database - Process New Challenges"); + // Process scanned files + for (auto f : challengeFileList) + { + if (m_paused.load()) + { + unique_lock lock(m_pauseMutex); + m_cvPause.wait(lock); + } + + if (!m_searching) + break; + + uint64 mylwt = f.second.lastWriteTime; + Event evt; + evt.type = Event::Challenge; + evt.lwt = mylwt; + + SearchState::ExistingFileEntry* existing = m_searchState.challenges.Find(f.first); + if (existing) + { + evt.id = existing->id; + if (existing->lwt != mylwt) + { + // Challenge Updated + evt.action = Event::Updated; + } + else + { + // Skip, not changed + continue; + } + } + else + { + // Challenge added + evt.action = Event::Added; + } + + if (existing) + Logf("Discovered Updated Challenge [%s]", Logger::Severity::Info, f.first); + else + Logf("Discovered Challenge [%s]", Logger::Severity::Info, f.first); + m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Discovered Challenge [%s]", f.first)); + + bool chalValid = true; + // TODO support old style courses + nlohmann::json settings; + + File chalFile; + if (!chalFile.OpenRead(f.first)) + chalValid = false; + + if (chalValid) + { + // TODO support old style courses + Buffer jsonBuf; + jsonBuf.resize(chalFile.GetSize()); + chalFile.Read(jsonBuf.data(), jsonBuf.size()); + settings = ChallengeIndex::LoadJson(jsonBuf, f.first); + chalValid = ChallengeIndex::BasicValidate(settings, f.first); + + if (chalValid) + { + sha1::SHA1 s; + + s.processBytes(jsonBuf.data(), jsonBuf.size()); + + uint32_t digest[5]; + s.getDigest(digest); + + evt.hash = Utility::Sprintf("%08x%08x%08x%08x%08x", digest[0], digest[1], digest[2], digest[3], digest[4]); + } + } + + if (!chalValid) + { + // Reset json entry + evt.json = nlohmann::json(); + + if (!existing) // Never added + { + Logf("Skipping corrupted challenge [%s]", Logger::Severity::Warning, f.first); + m_outer.OnSearchStatusUpdated.Call(Utility::Sprintf("Skipping corrupted challenge [%s]", f.first)); + continue; + } + // Invalid chals get removed from the database + evt.action = Event::Removed; + } + else + { + evt.json = settings; + } + evt.path = f.first; + AddChange(evt); + continue; + } + m_outer.OnSearchStatusUpdated.Call("[END] Chart Database - Process New Challenges"); + } + m_outer.OnSearchStatusUpdated.Call(""); + + m_searching = false; + } }; void MapDatabase::FinishInit() { - assert(!m_impl); - m_impl = new MapDatabase_Impl(*this, m_transferScores); + assert(!m_impl); + m_impl = new MapDatabase_Impl(*this, m_transferScores); } MapDatabase::MapDatabase(bool postponeInit) { - if (!postponeInit) - FinishInit(); - else - m_impl = NULL; + if (!postponeInit) + FinishInit(); + else + m_impl = NULL; } MapDatabase::MapDatabase() { - m_impl = new MapDatabase_Impl(*this, true); + m_impl = new MapDatabase_Impl(*this, true); } MapDatabase::~MapDatabase() { - delete m_impl; + delete m_impl; } void MapDatabase::Update() { - m_impl->Update(); + m_impl->Update(); } bool MapDatabase::IsSearching() const { - return m_impl->m_searching; + return m_impl->m_searching; } void MapDatabase::StartSearching() { - assert(m_impl); - m_impl->StartSearching(); + assert(m_impl); + m_impl->StartSearching(); } void MapDatabase::PauseSearching() { - m_impl->PauseSearching(); + m_impl->PauseSearching(); } void MapDatabase::ResumeSearching() { - m_impl->ResumeSearching(); + m_impl->ResumeSearching(); } void MapDatabase::StopSearching() { - m_impl->StopSearching(); + m_impl->StopSearching(); } Map MapDatabase::FindFoldersByPath(const String& search) { - return m_impl->FindFoldersByPath(search); + return m_impl->FindFoldersByPath(search); } Map MapDatabase::FindFoldersWithFilter(const String& search, const Vector> filter) { - return m_impl->FindFoldersWithFilter(search, filter); + return m_impl->FindFoldersWithFilter(search, filter); } Map MapDatabase::FindChallenges(const String& search) { - return m_impl->FindChallenges(search); + return m_impl->FindChallenges(search); } Map MapDatabase::FindFolders(const String& search) { - return m_impl->FindFolders(search); + return m_impl->FindFolders(search); } Map MapDatabase::FindFoldersByHash(const String& hash) { - return m_impl->FindFoldersByHash(hash); + return m_impl->FindFoldersByHash(hash); } -Map MapDatabase::FindFoldersByFolder(const String & folder) +Map MapDatabase::FindFoldersByFolder(const String& folder) { - return m_impl->FindFoldersByFolder(folder); + return m_impl->FindFoldersByFolder(folder); } Map MapDatabase::FindFoldersByCollection(const String& category) { - return m_impl->FindFoldersByCollection(category); + return m_impl->FindFoldersByCollection(category); } FolderIndex* MapDatabase::GetFolder(int32 idx) { - FolderIndex** folderIdx = m_impl->m_folders.Find(idx); - return folderIdx ? *folderIdx : nullptr; + FolderIndex** folderIdx = m_impl->m_folders.Find(idx); + return folderIdx ? *folderIdx : nullptr; } Vector MapDatabase::GetCollections() { - return m_impl->GetCollections(); + return m_impl->GetCollections(); } Vector MapDatabase::GetCollectionsForMap(int32 mapid) { - return m_impl->GetCollectionsForMap(mapid); + return m_impl->GetCollectionsForMap(mapid); } Vector MapDatabase::GetOrAddPracticeSetups(int32 chartId, const PracticeSetupIndex& defaultOptions) { - return m_impl->GetOrAddPracticeSetups(chartId, defaultOptions); + return m_impl->GetOrAddPracticeSetups(chartId, defaultOptions); } void MapDatabase::AddOrRemoveToCollection(const String& name, int32 mapid) { - m_impl->AddOrRemoveToCollection(name, mapid); + m_impl->AddOrRemoveToCollection(name, mapid); } void MapDatabase::AddSearchPath(const String& path) { - m_impl->AddSearchPath(path); + m_impl->AddSearchPath(path); } void MapDatabase::RemoveSearchPath(const String& path) { - m_impl->RemoveSearchPath(path); + m_impl->RemoveSearchPath(path); } void MapDatabase::LoadDatabaseWithoutSearching() { - m_impl->LoadDatabaseWithoutSearching(); + m_impl->LoadDatabaseWithoutSearching(); } void MapDatabase::UpdateChartOffset(const ChartIndex* chart) { - m_impl->UpdateChartOffset(chart); + m_impl->UpdateChartOffset(chart); } void MapDatabase::AddScore(ScoreIndex* score) { - m_impl->AddScore(score); + m_impl->AddScore(score); } void MapDatabase::UpdatePracticeSetup(PracticeSetupIndex* practiceSetup) { - m_impl->UpdateOrAddPracticeSetup(practiceSetup); + m_impl->UpdateOrAddPracticeSetup(practiceSetup); } void MapDatabase::UpdateChallengeResult(ChallengeIndex* chal, uint32 clearMark, uint32 bestScore) { - m_impl->UpdateChallengeResult(chal, clearMark, bestScore); + m_impl->UpdateChallengeResult(chal, clearMark, bestScore); } ChartIndex* MapDatabase::GetRandomChart() { - return m_impl->GetRandomChart(); + return m_impl->GetRandomChart(); } -const std::map& MapDatabase::GetFolderMap() +const std::map& MapDatabase::GetFolderMap() { - return m_impl->GetFolderMap(); + return m_impl->GetFolderMap(); } -const std::map& MapDatabase::GetChartMap() +const std::map& MapDatabase::GetChartMap() { - return m_impl->GetChartMap(); + return m_impl->GetChartMap(); } -const std::map& MapDatabase::GetChallengeMap() +const std::map& MapDatabase::GetChallengeMap() { - return m_impl->GetChallengeMap(); + return m_impl->GetChallengeMap(); } -void MapDatabase::SetChartUpdateBehavior(bool transferScores) { - m_transferScores = transferScores; - if (m_impl != NULL) - m_impl->SetChartUpdateBehavior(transferScores); +void MapDatabase::SetChartUpdateBehavior(bool transferScores) +{ + m_transferScores = transferScores; + if (m_impl != NULL) + m_impl->SetChartUpdateBehavior(transferScores); } ChartIndex* MapDatabase::FindFirstChartByPath(const String& s) { - return m_impl->FindFirstChartByPath(s); + return m_impl->FindFirstChartByPath(s); } ChartIndex* MapDatabase::FindFirstChartByHash(const String& s) { - return m_impl->FindFirstChartByHash(s); + return m_impl->FindFirstChartByHash(s); } ChartIndex* MapDatabase::FindFirstChartByNameAndLevel(const String& s, int32 level) { - return m_impl->FindFirstChartByNameAndLevel(s, level); + return m_impl->FindFirstChartByNameAndLevel(s, level); } diff --git a/Beatmap/src/PlaybackOptions.cpp b/Beatmap/src/PlaybackOptions.cpp index adc6f28e8..72a7b1cc7 100644 --- a/Beatmap/src/PlaybackOptions.cpp +++ b/Beatmap/src/PlaybackOptions.cpp @@ -3,43 +3,43 @@ enum class LegacyGameFlags : uint32 { - None = 0, + None = 0, - Hard = 0b1, + Hard = 0b1, - Mirror = 0b10, + Mirror = 0b10, - Random = 0b100, + Random = 0b100, - AutoBT = 0b1000, + AutoBT = 0b1000, - AutoFX = 0b10000, + AutoFX = 0b10000, - AutoLaser = 0b100000, - End + AutoLaser = 0b100000, + End }; - PlaybackOptions PlaybackOptions::FromFlags(uint32 flags) { PlaybackOptions res; - if ((flags & (uint32)LegacyGameFlags::Hard) != 0) { - res.gaugeType = GaugeType::Hard; - } + if ((flags & (uint32)LegacyGameFlags::Hard) != 0) + { + res.gaugeType = GaugeType::Hard; + } - res.random = (flags & (uint32)LegacyGameFlags::Random); - res.mirror = (flags & (uint32)LegacyGameFlags::Mirror); - res.autoFlags = (AutoFlags)((flags >> 3) & 0b111); + res.random = (flags & (uint32)LegacyGameFlags::Random); + res.mirror = (flags & (uint32)LegacyGameFlags::Mirror); + res.autoFlags = (AutoFlags)((flags >> 3) & 0b111); return res; } uint32 PlaybackOptions::ToLegacyFlags(const PlaybackOptions& options) { - uint32 flags = 0; - flags |= options.gaugeType != GaugeType::Normal ? (uint32)LegacyGameFlags::Hard : 0; - flags |= options.mirror ? (uint32)LegacyGameFlags::Mirror : 0; - flags |= options.random ? (uint32)LegacyGameFlags::Random : 0; - flags |= (uint32)options.autoFlags << 3; - return flags; + uint32 flags = 0; + flags |= options.gaugeType != GaugeType::Normal ? (uint32)LegacyGameFlags::Hard : 0; + flags |= options.mirror ? (uint32)LegacyGameFlags::Mirror : 0; + flags |= options.random ? (uint32)LegacyGameFlags::Random : 0; + flags |= (uint32)options.autoFlags << 3; + return flags; } diff --git a/Beatmap/stdafx.cpp b/Beatmap/stdafx.cpp index 1577c4e3b..fd4f341c7 100644 --- a/Beatmap/stdafx.cpp +++ b/Beatmap/stdafx.cpp @@ -1 +1 @@ -#include "stdafx.h" \ No newline at end of file +#include "stdafx.h" diff --git a/Beatmap/stdafx.h b/Beatmap/stdafx.h index 1df97f24a..a87675eb6 100644 --- a/Beatmap/stdafx.h +++ b/Beatmap/stdafx.h @@ -7,4 +7,4 @@ #include #include #include -#include \ No newline at end of file +#include diff --git a/CMakeLists.txt b/CMakeLists.txt index f4275e4a2..bf5600c6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,10 +23,7 @@ endif() set(CMAKE_CONFIGURATION_TYPES Debug Release) set(CMAKE_DEBUG_POSTFIX _Debug) set(CMAKE_RELEASE_POSTFIX _Release) -execute_process(COMMAND git log -1 --date=short --format="%cd_%h" - OUTPUT_VARIABLE GIT_DATE_HASH - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) + # Set output folders @@ -55,7 +52,16 @@ find_package(Vorbis REQUIRED) find_package(OGG REQUIRED) find_package(LibArchive REQUIRED) find_package(Iconv REQUIRED) +find_package(Git) + +if(GIT_FOUND) +message(${GIT_EXECUTABLE}) + execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --date=short --format="%cd_%h" + OUTPUT_VARIABLE GIT_DATE_HASH + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() # All projects use unicode define # this is mainly for windows functions either being defined to call A or W prefixed functions add_definitions(-DUNICODE -D_UNICODE) diff --git a/GUI/include/GUI/nanovg_lua.h b/GUI/include/GUI/nanovg_lua.h index 8abcf365b..7d2a39797 100644 --- a/GUI/include/GUI/nanovg_lua.h +++ b/GUI/include/GUI/nanovg_lua.h @@ -13,63 +13,63 @@ struct Label { - Text text; - int size; - float scale; - FontRes::TextOptions opt; - Graphics::Font font; - String content; + Text text; + int size; + float scale; + FontRes::TextOptions opt; + Graphics::Font font; + String content; }; struct ImageAnimation { - int FrameCount; - std::atomic CurrentFrame; - int TimesToLoop; - int LoopCounter; - int w; - int h; - float SecondsPerFrame; - float Timer; - std::atomic Compressed; - std::atomic LoadComplete; - std::atomic Cancelled; - std::mutex LoadMutex; - Vector Frames; - Vector FrameData; //for storing the file contents of the compressed frames - Image CurrentImage; //the current uncompressed frame in use - Image NextImage; - Thread* JobThread; - lua_State* State; + int FrameCount; + std::atomic CurrentFrame; + int TimesToLoop; + int LoopCounter; + int w; + int h; + float SecondsPerFrame; + float Timer; + std::atomic Compressed; + std::atomic LoadComplete; + std::atomic Cancelled; + std::mutex LoadMutex; + Vector Frames; + Vector FrameData; //for storing the file contents of the compressed frames + Image CurrentImage; //the current uncompressed frame in use + Image NextImage; + Thread* JobThread; + lua_State* State; }; struct GUIState { - NVGcontext* vg; - RenderQueue* rq; - Transform t; - Map> textCache; - Map> paintCache; - Map nextTextId; - Map nextPaintId; - Map fontCahce; - Map> vgImages; - Graphics::Font currentFont; - Vector4 fillColor; - int textAlign; - int fontSize; - Material* fontMaterial; - Material* fillMaterial; - NVGcolor otrColor; //outer color - NVGcolor inrColor; //inner color - NVGcolor imageTint; - Rect scissor; - Vector2i resolution; - Map> animations; - int scissorOffset; - Vector transformStack; - Vector nvgFonts; + NVGcontext* vg; + RenderQueue* rq; + Transform t; + Map> textCache; + Map> paintCache; + Map nextTextId; + Map nextPaintId; + Map fontCahce; + Map> vgImages; + Graphics::Font currentFont; + Vector4 fillColor; + int textAlign; + int fontSize; + Material* fontMaterial; + Material* fillMaterial; + NVGcolor otrColor; //outer color + NVGcolor inrColor; //inner color + NVGcolor imageTint; + Rect scissor; + Vector2i resolution; + Map> animations; + int scissorOffset; + Vector transformStack; + Vector nvgFonts; }; @@ -79,1041 +79,1041 @@ GUIState g_guiState; static int LoadFont(const char* name, const char* filename, lua_State* L) { - { - Graphics::Font* cached = g_guiState.fontCahce.Find(name); - if (cached) - { - g_guiState.currentFont = *cached; - } - else - { - String path = filename; - Graphics::Font newFont = FontRes::Create(g_gl, path); - if (!newFont) - { - lua_Debug ar; - lua_getstack(L, 1, &ar); - lua_getinfo(L, "Snl", &ar); - String luaFilename; - String fontFilename; - Path::RemoveLast(filename, &fontFilename); - Path::RemoveLast(ar.source, &luaFilename); - lua_pushstring(L, *Utility::Sprintf("Failed to load font \"%s\" at line %d in \"%s\"", fontFilename, ar.currentline, luaFilename)); - lua_error(L); - return 0; - } - g_guiState.fontCahce.Add(name, newFont); - g_guiState.currentFont = *g_guiState.fontCahce.Find(name); - } - } - - { //nanovg - if (nvgFindFont(g_guiState.vg, name) != -1) - { - nvgFontFace(g_guiState.vg, name); - return 0; - } - int fontId = nvgCreateFont(g_guiState.vg, name, filename); - nvgFontFaceId(g_guiState.vg, fontId); - nvgAddFallbackFont(g_guiState.vg, name, "fallback"); - } - return 0; + { + Graphics::Font* cached = g_guiState.fontCahce.Find(name); + if (cached) + { + g_guiState.currentFont = *cached; + } + else + { + String path = filename; + Graphics::Font newFont = FontRes::Create(g_gl, path); + if (!newFont) + { + lua_Debug ar; + lua_getstack(L, 1, &ar); + lua_getinfo(L, "Snl", &ar); + String luaFilename; + String fontFilename; + Path::RemoveLast(filename, &fontFilename); + Path::RemoveLast(ar.source, &luaFilename); + lua_pushstring(L, *Utility::Sprintf("Failed to load font \"%s\" at line %d in \"%s\"", fontFilename, ar.currentline, luaFilename)); + lua_error(L); + return 0; + } + g_guiState.fontCahce.Add(name, newFont); + g_guiState.currentFont = *g_guiState.fontCahce.Find(name); + } + } + + { //nanovg + if (nvgFindFont(g_guiState.vg, name) != -1) + { + nvgFontFace(g_guiState.vg, name); + return 0; + } + int fontId = nvgCreateFont(g_guiState.vg, name, filename); + nvgFontFaceId(g_guiState.vg, fontId); + nvgAddFallbackFont(g_guiState.vg, name, "fallback"); + } + return 0; } static int lBeginPath(lua_State* L) { - g_guiState.fillColor = Vector4(1.0); - nvgBeginPath(g_guiState.vg); - return 0; + g_guiState.fillColor = Vector4(1.0); + nvgBeginPath(g_guiState.vg); + return 0; } static void AnimationLoader(Vector files, Ref ia) { - ia->FrameCount = files.size(); - files.Sort([](FileInfo& a, FileInfo& b) { - String af, bf; - Path::RemoveLast(a.fullPath, &af); - Path::RemoveLast(b.fullPath, &bf); - return af.compare(bf) < 0; - }); - ia->Timer = 0; - - if (ia->Compressed.load()) - { - for (int i = 0; i < ia->FrameCount; i++) - { - if (ia->Cancelled.load()) - break; - File newImage; - if (newImage.OpenRead(files[i].fullPath)) { - Buffer newData; - newData.resize(newImage.GetSize()); - newImage.Read(newData.data(), newImage.GetSize()); - ia->FrameData.push_back(std::move(newData)); - } - } - ia->NextImage = ImageRes::Create(ia->FrameData[0]); - } - else { - for (int i = 0; i < ia->FrameCount; i++) - { - if (ia->Cancelled.load()) - break; - ia->Frames.Add(Graphics::ImageRes::Create(files[i].fullPath)); - } - } - ia->LoadComplete.store(true); - - - if (ia->Compressed.load()) - { - std::chrono::microseconds sleepDuration((uint32)(250000.f * ia->SecondsPerFrame)); - int currentFrame = -1; - while (!ia->Cancelled.load()) - { - if (ia->CurrentFrame != currentFrame) - { - currentFrame = ia->CurrentFrame; - int nextFrame = (currentFrame + 1) % ia->FrameCount; - Image nextImage = ImageRes::Create(ia->FrameData[nextFrame]); - ia->LoadMutex.lock(); - ia->NextImage = nextImage; - ia->LoadMutex.unlock(); - } - else - { - std::this_thread::sleep_for(sleepDuration); - } - } - } + ia->FrameCount = files.size(); + files.Sort([](FileInfo& a, FileInfo& b) { + String af, bf; + Path::RemoveLast(a.fullPath, &af); + Path::RemoveLast(b.fullPath, &bf); + return af.compare(bf) < 0; + }); + ia->Timer = 0; + + if (ia->Compressed.load()) + { + for (int i = 0; i < ia->FrameCount; i++) + { + if (ia->Cancelled.load()) + break; + File newImage; + if (newImage.OpenRead(files[i].fullPath)) { + Buffer newData; + newData.resize(newImage.GetSize()); + newImage.Read(newData.data(), newImage.GetSize()); + ia->FrameData.push_back(std::move(newData)); + } + } + ia->NextImage = ImageRes::Create(ia->FrameData[0]); + } + else { + for (int i = 0; i < ia->FrameCount; i++) + { + if (ia->Cancelled.load()) + break; + ia->Frames.Add(Graphics::ImageRes::Create(files[i].fullPath)); + } + } + ia->LoadComplete.store(true); + + + if (ia->Compressed.load()) + { + std::chrono::microseconds sleepDuration((uint32)(250000.f * ia->SecondsPerFrame)); + int currentFrame = -1; + while (!ia->Cancelled.load()) + { + if (ia->CurrentFrame != currentFrame) + { + currentFrame = ia->CurrentFrame; + int nextFrame = (currentFrame + 1) % ia->FrameCount; + Image nextImage = ImageRes::Create(ia->FrameData[nextFrame]); + ia->LoadMutex.lock(); + ia->NextImage = nextImage; + ia->LoadMutex.unlock(); + } + else + { + std::this_thread::sleep_for(sleepDuration); + } + } + } } static int lTickAnimation(lua_State* L) { - int key; - float deltatime; - key = luaL_checkinteger(L, 1); - deltatime = luaL_checknumber(L, 2); - - if (!g_guiState.animations.Contains(key)) { - lua_pushinteger(L, -1); - return 1; - } - - Ref ia = g_guiState.animations.at(key); - if (!ia->LoadComplete.load()) { - lua_pushinteger(L, 0); - return 1; - } - - if (ia->Cancelled.load()) { - lua_pushinteger(L, -1); - return 1; - } - - ia->Timer += deltatime; - if (ia->Timer >= ia->SecondsPerFrame) - { - if (ia->LoopCounter < ia->TimesToLoop || ia->TimesToLoop == 0) - { - ia->Timer = fmodf(ia->Timer, ia->SecondsPerFrame); - - if (ia->CurrentFrame == ia->FrameCount - 1) - ++ia->LoopCounter; - - if (ia->LoopCounter == ia->TimesToLoop && ia->LoopCounter != 0) - return 0; - - ia->CurrentFrame = (ia->CurrentFrame + 1) % ia->FrameCount; - if (ia->Compressed.load()) - { - ia->LoadMutex.lock(); - ia->CurrentImage = ia->NextImage; - ia->LoadMutex.unlock(); - nvgUpdateImage(g_guiState.vg, key, (unsigned char*)ia->CurrentImage->GetBits()); - } - else - { - nvgUpdateImage(g_guiState.vg, key, (unsigned char*)ia->Frames[ia->CurrentFrame]->GetBits()); - } - } - } - lua_pushinteger(L, 1); - return 1; + int key; + float deltatime; + key = luaL_checkinteger(L, 1); + deltatime = luaL_checknumber(L, 2); + + if (!g_guiState.animations.Contains(key)) { + lua_pushinteger(L, -1); + return 1; + } + + Ref ia = g_guiState.animations.at(key); + if (!ia->LoadComplete.load()) { + lua_pushinteger(L, 0); + return 1; + } + + if (ia->Cancelled.load()) { + lua_pushinteger(L, -1); + return 1; + } + + ia->Timer += deltatime; + if (ia->Timer >= ia->SecondsPerFrame) + { + if (ia->LoopCounter < ia->TimesToLoop || ia->TimesToLoop == 0) + { + ia->Timer = fmodf(ia->Timer, ia->SecondsPerFrame); + + if (ia->CurrentFrame == ia->FrameCount - 1) + ++ia->LoopCounter; + + if (ia->LoopCounter == ia->TimesToLoop && ia->LoopCounter != 0) + return 0; + + ia->CurrentFrame = (ia->CurrentFrame + 1) % ia->FrameCount; + if (ia->Compressed.load()) + { + ia->LoadMutex.lock(); + ia->CurrentImage = ia->NextImage; + ia->LoadMutex.unlock(); + nvgUpdateImage(g_guiState.vg, key, (unsigned char*)ia->CurrentImage->GetBits()); + } + else + { + nvgUpdateImage(g_guiState.vg, key, (unsigned char*)ia->Frames[ia->CurrentFrame]->GetBits()); + } + } + } + lua_pushinteger(L, 1); + return 1; } static int LoadAnimation(lua_State* L, const char* path, float frametime, int loopcount, bool compressed) { - Vector files = Files::ScanFiles(path); - if (files.empty()) - return -1; + Vector files = Files::ScanFiles(path); + if (files.empty()) + return -1; - int key = nvgCreateImage(g_guiState.vg, *files[0].fullPath, 0); - Ref ia = std::make_shared(); - ia->Compressed = compressed; - ia->TimesToLoop = loopcount; - ia->LoopCounter = 0; - ia->SecondsPerFrame = frametime; - ia->LoadComplete.store(false); - ia->Cancelled.store(false); - ia->State = L; - ia->JobThread = new Thread(AnimationLoader, files, ia); - g_guiState.animations.insert(std::make_pair(key, ia)); + int key = nvgCreateImage(g_guiState.vg, *files[0].fullPath, 0); + Ref ia = std::make_shared(); + ia->Compressed = compressed; + ia->TimesToLoop = loopcount; + ia->LoopCounter = 0; + ia->SecondsPerFrame = frametime; + ia->LoadComplete.store(false); + ia->Cancelled.store(false); + ia->State = L; + ia->JobThread = new Thread(AnimationLoader, files, ia); + g_guiState.animations.insert(std::make_pair(key, ia)); - return key; + return key; } static int lResetAnimation(lua_State* L) { - int key; - key = luaL_checkinteger(L, 1); - Ref ia = g_guiState.animations.at(key); - ia->CurrentFrame = 0; - ia->Timer = 0; - ia->LoopCounter = 0; - return 0; + int key; + key = luaL_checkinteger(L, 1); + Ref ia = g_guiState.animations.at(key); + ia->CurrentFrame = 0; + ia->Timer = 0; + ia->LoopCounter = 0; + return 0; } static int lLoadAnimation(lua_State* L) { - const char* path; - float frametime; - int loopcount = 0; - bool compressed = false; + const char* path; + float frametime; + int loopcount = 0; + bool compressed = false; - path = luaL_checkstring(L, 1); - frametime = luaL_checknumber(L, 2); - if (lua_gettop(L) == 3) - { - loopcount = luaL_checkinteger(L, 3); - } - else if (lua_gettop(L) == 4) - { - loopcount = luaL_checkinteger(L, 3); - compressed = lua_toboolean(L, 4) == 1; - } - + path = luaL_checkstring(L, 1); + frametime = luaL_checknumber(L, 2); + if (lua_gettop(L) == 3) + { + loopcount = luaL_checkinteger(L, 3); + } + else if (lua_gettop(L) == 4) + { + loopcount = luaL_checkinteger(L, 3); + compressed = lua_toboolean(L, 4) == 1; + } - int result = LoadAnimation(L, path, frametime, loopcount, compressed); - if (result == -1) - return 0; - lua_pushnumber(L, result); - return 1; + int result = LoadAnimation(L, path, frametime, loopcount, compressed); + if (result == -1) + return 0; + + lua_pushnumber(L, result); + return 1; } static int lText(lua_State* L /*const char* s, float x, float y*/) { - const char* s; - float x, y; - s = luaL_checkstring(L, 1); - x = luaL_checknumber(L, 2); - y = luaL_checknumber(L, 3); - nvgText(g_guiState.vg, x, y, s, NULL); - - //{ //Fast text - // WString text = Utility::Convert ToWString(s); - // Text te = (*g_guiState.currentFont)->CreateText(text, g_guiState.fontSize); - // Transform textTransform = g_guiState.t; - // textTransform *= Transform::Translation(Vector2(x, y)); - - // //vertical alignment - // if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_BOTTOM) != 0) - // { - // textTransform *= Transform::Translation(Vector2(0, -te->size.y)); - // } - // else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_MIDDLE) != 0) - // { - // textTransform *= Transform::Translation(Vector2(0, -te->size.y / 2)); - // } - - // //horizontal alignment - // if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_CENTER) != 0) - // { - // textTransform *= Transform::Translation(Vector2(-te->size.x / 2, 0)); - // } - // else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_RIGHT) != 0) - // { - // textTransform *= Transform::Translation(Vector2(-te->size.x, 0)); - // } - // MaterialParameterSet params; - // params.SetParameter("color", g_guiState.fillColor); - // g_guiState.rq->Draw(textTransform, te, g_application->GetFontMaterial(), params); - //} - return 0; + const char* s; + float x, y; + s = luaL_checkstring(L, 1); + x = luaL_checknumber(L, 2); + y = luaL_checknumber(L, 3); + nvgText(g_guiState.vg, x, y, s, NULL); + + //{ //Fast text + // WString text = Utility::Convert ToWString(s); + // Text te = (*g_guiState.currentFont)->CreateText(text, g_guiState.fontSize); + // Transform textTransform = g_guiState.t; + // textTransform *= Transform::Translation(Vector2(x, y)); + + // //vertical alignment + // if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_BOTTOM) != 0) + // { + // textTransform *= Transform::Translation(Vector2(0, -te->size.y)); + // } + // else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_MIDDLE) != 0) + // { + // textTransform *= Transform::Translation(Vector2(0, -te->size.y / 2)); + // } + + // //horizontal alignment + // if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_CENTER) != 0) + // { + // textTransform *= Transform::Translation(Vector2(-te->size.x / 2, 0)); + // } + // else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_RIGHT) != 0) + // { + // textTransform *= Transform::Translation(Vector2(-te->size.x, 0)); + // } + // MaterialParameterSet params; + // params.SetParameter("color", g_guiState.fillColor); + // g_guiState.rq->Draw(textTransform, te, g_application->GetFontMaterial(), params); + //} + return 0; } static int lFontFace(lua_State* L /*const char* s*/) { - const char* s; - s = luaL_checkstring(L, 1); - nvgFontFace(g_guiState.vg, s); - return 0; + const char* s; + s = luaL_checkstring(L, 1); + nvgFontFace(g_guiState.vg, s); + return 0; } static int lFontSize(lua_State* L /*float size*/) { - float size = luaL_checknumber(L, 1); - nvgFontSize(g_guiState.vg, size); - g_guiState.fontSize = size; - return 0; + float size = luaL_checknumber(L, 1); + nvgFontSize(g_guiState.vg, size); + g_guiState.fontSize = size; + return 0; } static int lFillColor(lua_State* L /*int r, int g, int b, int a = 255*/) { - int r, g, b, a; - r = luaL_checkinteger(L, 1); - g = luaL_checkinteger(L, 2); - b = luaL_checkinteger(L, 3); - if (lua_gettop(L) == 4) - { - a = luaL_checkinteger(L, 4); - } - else - { - a = 255; - } - nvgFillColor(g_guiState.vg, nvgRGBA(r, g, b, a)); - g_guiState.fillColor = Vector4(r / 255.0, g / 255.0, b / 255.0, a / 255.0); - return 0; + int r, g, b, a; + r = luaL_checkinteger(L, 1); + g = luaL_checkinteger(L, 2); + b = luaL_checkinteger(L, 3); + if (lua_gettop(L) == 4) + { + a = luaL_checkinteger(L, 4); + } + else + { + a = 255; + } + nvgFillColor(g_guiState.vg, nvgRGBA(r, g, b, a)); + g_guiState.fillColor = Vector4(r / 255.0, g / 255.0, b / 255.0, a / 255.0); + return 0; } static int lRect(lua_State* L /*float x, float y, float w, float h*/) { - float x, y, w, h; - x = luaL_checknumber(L, 1); - y = luaL_checknumber(L, 2); - w = luaL_checknumber(L, 3); - h = luaL_checknumber(L, 4); - nvgRect(g_guiState.vg, x, y, w, h); - return 0; + float x, y, w, h; + x = luaL_checknumber(L, 1); + y = luaL_checknumber(L, 2); + w = luaL_checknumber(L, 3); + h = luaL_checknumber(L, 4); + nvgRect(g_guiState.vg, x, y, w, h); + return 0; } static int lFill(lua_State* L) { - nvgFill(g_guiState.vg); - return 0; + nvgFill(g_guiState.vg); + return 0; } static int lTextAlign(lua_State* L /*int align*/) { - nvgTextAlign(g_guiState.vg, luaL_checkinteger(L, 1)); - g_guiState.textAlign = luaL_checkinteger(L, 1); - return 0; + nvgTextAlign(g_guiState.vg, luaL_checkinteger(L, 1)); + g_guiState.textAlign = luaL_checkinteger(L, 1); + return 0; } static int lCreateImage(lua_State* L /*const char* filename, int imageflags */) { - const char* filename = luaL_checkstring(L, 1); - int imageflags = luaL_checkinteger(L, 2); - int handle = nvgCreateImage(g_guiState.vg, filename, imageflags); - if (handle != 0) - { - g_guiState.vgImages[L].Add(handle); - lua_pushnumber(L, handle); - return 1; - } - return 0; + const char* filename = luaL_checkstring(L, 1); + int imageflags = luaL_checkinteger(L, 2); + int handle = nvgCreateImage(g_guiState.vg, filename, imageflags); + if (handle != 0) + { + g_guiState.vgImages[L].Add(handle); + lua_pushnumber(L, handle); + return 1; + } + return 0; } static int lImagePatternFill(lua_State* L /*int image, float alpha*/) { - int image = luaL_checkinteger(L, 1); - float alpha = luaL_checknumber(L, 2); - int w, h; - nvgImageSize(g_guiState.vg, image, &w, &h); - nvgFillPaint(g_guiState.vg, nvgImagePattern(g_guiState.vg, 0, 0, w, h, 0, image, alpha)); - return 0; + int image = luaL_checkinteger(L, 1); + float alpha = luaL_checknumber(L, 2); + int w, h; + nvgImageSize(g_guiState.vg, image, &w, &h); + nvgFillPaint(g_guiState.vg, nvgImagePattern(g_guiState.vg, 0, 0, w, h, 0, image, alpha)); + return 0; } static int lSetImageTint(lua_State* L /*int r, int g, int b*/) { - int r, g, b; - r = luaL_checkinteger(L, 1); - g = luaL_checkinteger(L, 2); - b = luaL_checkinteger(L, 3); - g_guiState.imageTint = nvgRGB(r, g, b); - return 0; + int r, g, b; + r = luaL_checkinteger(L, 1); + g = luaL_checkinteger(L, 2); + b = luaL_checkinteger(L, 3); + g_guiState.imageTint = nvgRGB(r, g, b); + return 0; } static int lImageRect(lua_State* L /*float x, float y, float w, float h, int image, float alpha, float angle*/) { - float x = 0.f; - float y = 0.f; - float w = 0.f; - float h = 0.f; - float alpha = 1.f; - float angle = 0.f; - int image = -1; - x = luaL_checknumber(L, 1); - y = luaL_checknumber(L, 2); - w = luaL_checknumber(L, 3); - h = luaL_checknumber(L, 4); - image = luaL_checkinteger(L, 5); - alpha = luaL_checknumber(L, 6); - angle = luaL_checknumber(L, 7); - - int imgH = -1, imgW = -1; - nvgImageSize(g_guiState.vg, image, &imgW, &imgH); - float scaleX = 1.f, scaleY = 1.f; - float tr[6] = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f}; - nvgCurrentTransform(g_guiState.vg, tr); - scaleX = w / imgW; - scaleY = h / imgH; - nvgTranslate(g_guiState.vg, x, y); - nvgRotate(g_guiState.vg, angle); - nvgScale(g_guiState.vg, scaleX, scaleY); - NVGpaint paint = nvgImagePattern(g_guiState.vg, 0, 0, imgW, imgH, 0, image, alpha); - paint.innerColor = g_guiState.imageTint; - paint.innerColor.a = alpha; - nvgFillPaint(g_guiState.vg, paint); - nvgRect(g_guiState.vg, 0, 0, imgW, imgH); - nvgFill(g_guiState.vg); - nvgResetTransform(g_guiState.vg); - nvgTransform(g_guiState.vg, tr[0], tr[1], tr[2], tr[3], tr[4], tr[5]); - return 0; + float x = 0.f; + float y = 0.f; + float w = 0.f; + float h = 0.f; + float alpha = 1.f; + float angle = 0.f; + int image = -1; + x = luaL_checknumber(L, 1); + y = luaL_checknumber(L, 2); + w = luaL_checknumber(L, 3); + h = luaL_checknumber(L, 4); + image = luaL_checkinteger(L, 5); + alpha = luaL_checknumber(L, 6); + angle = luaL_checknumber(L, 7); + + int imgH = -1, imgW = -1; + nvgImageSize(g_guiState.vg, image, &imgW, &imgH); + float scaleX = 1.f, scaleY = 1.f; + float tr[6] = { 0.f, 0.f, 0.f, 0.f, 0.f, 0.f }; + nvgCurrentTransform(g_guiState.vg, tr); + scaleX = w / imgW; + scaleY = h / imgH; + nvgTranslate(g_guiState.vg, x, y); + nvgRotate(g_guiState.vg, angle); + nvgScale(g_guiState.vg, scaleX, scaleY); + NVGpaint paint = nvgImagePattern(g_guiState.vg, 0, 0, imgW, imgH, 0, image, alpha); + paint.innerColor = g_guiState.imageTint; + paint.innerColor.a = alpha; + nvgFillPaint(g_guiState.vg, paint); + nvgRect(g_guiState.vg, 0, 0, imgW, imgH); + nvgFill(g_guiState.vg); + nvgResetTransform(g_guiState.vg); + nvgTransform(g_guiState.vg, tr[0], tr[1], tr[2], tr[3], tr[4], tr[5]); + return 0; } static int lScale(lua_State* L /*float x, float y*/) { - float x, y; - x = luaL_checknumber(L, 1); - y = luaL_checknumber(L, 2); - nvgScale(g_guiState.vg, x, y); - g_guiState.t *= Transform::Scale({ x, y, 0 }); - return 0; + float x, y; + x = luaL_checknumber(L, 1); + y = luaL_checknumber(L, 2); + nvgScale(g_guiState.vg, x, y); + g_guiState.t *= Transform::Scale({ x, y, 0 }); + return 0; } static int lTranslate(lua_State* L /*float x, float y*/) { - float x, y; - x = luaL_checknumber(L, 1); - y = luaL_checknumber(L, 2); - g_guiState.t *= Transform::Translation({ x, y, 0 }); - nvgTranslate(g_guiState.vg, x, y); - return 0; + float x, y; + x = luaL_checknumber(L, 1); + y = luaL_checknumber(L, 2); + g_guiState.t *= Transform::Translation({ x, y, 0 }); + nvgTranslate(g_guiState.vg, x, y); + return 0; } static int lRotate(lua_State* L /*float angle*/) { - float angle = luaL_checknumber(L, 1); - nvgRotate(g_guiState.vg, angle); - g_guiState.t *= Transform::Rotation({ 0, 0, angle }); - return 0; + float angle = luaL_checknumber(L, 1); + nvgRotate(g_guiState.vg, angle); + g_guiState.t *= Transform::Rotation({ 0, 0, angle }); + return 0; } static int lResetTransform(lua_State* L) { - nvgResetTransform(g_guiState.vg); - g_guiState.t = Transform(); - return 0; + nvgResetTransform(g_guiState.vg); + g_guiState.t = Transform(); + return 0; } static int lLoadFont(lua_State* L /*const char* name, const char* filename*/) { - const char* name = luaL_checkstring(L, 1); - const char* filename = luaL_checkstring(L, 2); - return LoadFont(name, filename, L); + const char* name = luaL_checkstring(L, 1); + const char* filename = luaL_checkstring(L, 2); + return LoadFont(name, filename, L); } static int lCreateLabel(lua_State* L /*const char* text, int size, bool monospace*/) { - const char* text = luaL_checkstring(L, 1); - int size = luaL_checkinteger(L, 2); - int monospace = luaL_checkinteger(L, 3); - - Label newLabel; - newLabel.text = g_guiState.currentFont->CreateText(Utility::ConvertToWString(text), - size * g_guiState.t.GetScale().y, - (FontRes::TextOptions)monospace); - newLabel.scale = g_guiState.t.GetScale().y; - newLabel.size = size; - newLabel.opt = (FontRes::TextOptions)monospace; - newLabel.font = g_guiState.currentFont; - newLabel.content = text; - g_guiState.textCache.FindOrAdd(L).Add(g_guiState.nextTextId[L], newLabel); - lua_pushnumber(L, g_guiState.nextTextId[L]); - g_guiState.nextTextId[L]++; - return 1; + const char* text = luaL_checkstring(L, 1); + int size = luaL_checkinteger(L, 2); + int monospace = luaL_checkinteger(L, 3); + + Label newLabel; + newLabel.text = g_guiState.currentFont->CreateText(Utility::ConvertToWString(text), + size * g_guiState.t.GetScale().y, + (FontRes::TextOptions)monospace); + newLabel.scale = g_guiState.t.GetScale().y; + newLabel.size = size; + newLabel.opt = (FontRes::TextOptions)monospace; + newLabel.font = g_guiState.currentFont; + newLabel.content = text; + g_guiState.textCache.FindOrAdd(L).Add(g_guiState.nextTextId[L], newLabel); + lua_pushnumber(L, g_guiState.nextTextId[L]); + g_guiState.nextTextId[L]++; + return 1; } static int lUpdateLabel(lua_State* L /*int labelId, const char* text, int size*/) { - int labelId = luaL_checkinteger(L, 1); - const char* text = luaL_checkstring(L, 2); - int size = luaL_checkinteger(L, 3); - Label updated; - updated.text = g_guiState.currentFont->CreateText(Utility::ConvertToWString(text), size * g_guiState.t.GetScale().y); - updated.size = size; - updated.scale = g_guiState.t.GetScale().y; - updated.content = text; - g_guiState.textCache[L][labelId] = updated; - return 0; + int labelId = luaL_checkinteger(L, 1); + const char* text = luaL_checkstring(L, 2); + int size = luaL_checkinteger(L, 3); + Label updated; + updated.text = g_guiState.currentFont->CreateText(Utility::ConvertToWString(text), size * g_guiState.t.GetScale().y); + updated.size = size; + updated.scale = g_guiState.t.GetScale().y; + updated.content = text; + g_guiState.textCache[L][labelId] = updated; + return 0; } static int lDrawLabel(lua_State* L /*int labelId, float x, float y, float maxWidth = -1*/) { - int labelId = luaL_checkinteger(L, 1); - float x = luaL_checknumber(L, 2); - float y = luaL_checknumber(L, 3); - float maxWidth = -1; - if (lua_gettop(L) == 4) - { - maxWidth = luaL_checknumber(L, 4); - } - Vector2 scale = g_guiState.t.GetScale().xy(); - if (scale.x == 0 || scale.y == 0) - return 0; - - Transform textTransform = g_guiState.t; - textTransform *= Transform::Translation(Vector2(x, y)); - textTransform *= Transform::Scale(Vector2(1.0) / scale); - Label te = g_guiState.textCache[L][labelId]; - if (fabsf(te.scale - g_guiState.t.GetScale().y) > 0.001) - { - te.scale = g_guiState.t.GetScale().y; - te.text = te.font->CreateText(Utility::ConvertToWString(te.content), Math::Round((float)te.size * te.scale)); - g_guiState.textCache[L][labelId] = te; - } - float mwScale = 1.0f; - if (maxWidth > 0) - { - mwScale = Math::Min(1.0f, maxWidth / ((float)te.text->size.x / te.scale)); - textTransform *= Transform::Scale(Vector2(mwScale)); - } - - //vertical alignment - if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_BOTTOM) != 0) - { - textTransform *= Transform::Translation(Vector2(0, -te.text->size.y)); - } - else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_MIDDLE) != 0) - { - textTransform *= Transform::Translation(Vector2(0, -te.text->size.y / 2)); - } - - //horizontal alignment - if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_CENTER) != 0) - { - textTransform *= Transform::Translation(Vector2(-te.text->size.x / 2, 0)); - } - else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_RIGHT) != 0) - { - textTransform *= Transform::Translation(Vector2(-te.text->size.x, 0)); - } - - - MaterialParameterSet params; - params.SetParameter("color", g_guiState.fillColor); - g_guiState.rq->DrawScissored(g_guiState.scissor ,textTransform, te.text, *g_guiState.fontMaterial, params); - return 0; + int labelId = luaL_checkinteger(L, 1); + float x = luaL_checknumber(L, 2); + float y = luaL_checknumber(L, 3); + float maxWidth = -1; + if (lua_gettop(L) == 4) + { + maxWidth = luaL_checknumber(L, 4); + } + Vector2 scale = g_guiState.t.GetScale().xy(); + if (scale.x == 0 || scale.y == 0) + return 0; + + Transform textTransform = g_guiState.t; + textTransform *= Transform::Translation(Vector2(x, y)); + textTransform *= Transform::Scale(Vector2(1.0) / scale); + Label te = g_guiState.textCache[L][labelId]; + if (fabsf(te.scale - g_guiState.t.GetScale().y) > 0.001) + { + te.scale = g_guiState.t.GetScale().y; + te.text = te.font->CreateText(Utility::ConvertToWString(te.content), Math::Round((float)te.size * te.scale)); + g_guiState.textCache[L][labelId] = te; + } + float mwScale = 1.0f; + if (maxWidth > 0) + { + mwScale = Math::Min(1.0f, maxWidth / ((float)te.text->size.x / te.scale)); + textTransform *= Transform::Scale(Vector2(mwScale)); + } + + //vertical alignment + if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_BOTTOM) != 0) + { + textTransform *= Transform::Translation(Vector2(0, -te.text->size.y)); + } + else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_MIDDLE) != 0) + { + textTransform *= Transform::Translation(Vector2(0, -te.text->size.y / 2)); + } + + //horizontal alignment + if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_CENTER) != 0) + { + textTransform *= Transform::Translation(Vector2(-te.text->size.x / 2, 0)); + } + else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_RIGHT) != 0) + { + textTransform *= Transform::Translation(Vector2(-te.text->size.x, 0)); + } + + + MaterialParameterSet params; + params.SetParameter("color", g_guiState.fillColor); + g_guiState.rq->DrawScissored(g_guiState.scissor, textTransform, te.text, *g_guiState.fontMaterial, params); + return 0; } static int lMoveTo(lua_State* L /* float x, float y */) { - float x = luaL_checknumber(L, 1); - float y = luaL_checknumber(L, 2); - nvgMoveTo(g_guiState.vg, x, y); - return 0; + float x = luaL_checknumber(L, 1); + float y = luaL_checknumber(L, 2); + nvgMoveTo(g_guiState.vg, x, y); + return 0; } static int lLineTo(lua_State* L /* float x, float y */) { - float x = luaL_checknumber(L, 1); - float y = luaL_checknumber(L, 2); - nvgLineTo(g_guiState.vg, x, y); - return 0; + float x = luaL_checknumber(L, 1); + float y = luaL_checknumber(L, 2); + nvgLineTo(g_guiState.vg, x, y); + return 0; } static int lBezierTo(lua_State* L /* float c1x, float c1y, float c2x, float c2y, float x, float y */) { - float c1x = luaL_checknumber(L, 1); - float c1y = luaL_checknumber(L, 2); - float c2x = luaL_checknumber(L, 3); - float c2y = luaL_checknumber(L, 4); - float x = luaL_checknumber(L, 5); - float y = luaL_checknumber(L, 6); - nvgBezierTo(g_guiState.vg, c1x, c1y, c2x, c2y, x, y); - return 0; + float c1x = luaL_checknumber(L, 1); + float c1y = luaL_checknumber(L, 2); + float c2x = luaL_checknumber(L, 3); + float c2y = luaL_checknumber(L, 4); + float x = luaL_checknumber(L, 5); + float y = luaL_checknumber(L, 6); + nvgBezierTo(g_guiState.vg, c1x, c1y, c2x, c2y, x, y); + return 0; } static int lQuadTo(lua_State* L /* float cx, float cy, float x, float y */) { - float cx = luaL_checknumber(L, 1); - float cy = luaL_checknumber(L, 2); - float x = luaL_checknumber(L, 3); - float y = luaL_checknumber(L, 4); - nvgQuadTo(g_guiState.vg, cx, cy, x, y); - return 0; + float cx = luaL_checknumber(L, 1); + float cy = luaL_checknumber(L, 2); + float x = luaL_checknumber(L, 3); + float y = luaL_checknumber(L, 4); + nvgQuadTo(g_guiState.vg, cx, cy, x, y); + return 0; } static int lArcTo(lua_State* L /* float x1, float y1, float x2, float y2, float radius */) { - float x1 = luaL_checknumber(L, 1); - float y1 = luaL_checknumber(L, 2); - float x2 = luaL_checknumber(L, 3); - float y2 = luaL_checknumber(L, 4); - float radius = luaL_checknumber(L, 5); - nvgArcTo(g_guiState.vg, x1, y1, x2, y2, radius); - return 0; + float x1 = luaL_checknumber(L, 1); + float y1 = luaL_checknumber(L, 2); + float x2 = luaL_checknumber(L, 3); + float y2 = luaL_checknumber(L, 4); + float radius = luaL_checknumber(L, 5); + nvgArcTo(g_guiState.vg, x1, y1, x2, y2, radius); + return 0; } static int lClosePath(lua_State* L) { - nvgClosePath(g_guiState.vg); - return 0; + nvgClosePath(g_guiState.vg); + return 0; } static int lStroke(lua_State* L) { - nvgStroke(g_guiState.vg); - return 0; + nvgStroke(g_guiState.vg); + return 0; } static int lMiterLimit(lua_State* L /* float limit */) { - float limit = luaL_checknumber(L, 1); - nvgMiterLimit(g_guiState.vg, limit); - return 0; + float limit = luaL_checknumber(L, 1); + nvgMiterLimit(g_guiState.vg, limit); + return 0; } static int lStrokeWidth(lua_State* L /* float size */) { - float size = luaL_checknumber(L, 1); - nvgStrokeWidth(g_guiState.vg, size); - return 0; + float size = luaL_checknumber(L, 1); + nvgStrokeWidth(g_guiState.vg, size); + return 0; } static int lLineCap(lua_State* L /* int cap */) { - int cap = luaL_checkinteger(L, 1); - nvgLineCap(g_guiState.vg, cap); - return 0; + int cap = luaL_checkinteger(L, 1); + nvgLineCap(g_guiState.vg, cap); + return 0; } static int lLineJoin(lua_State* L /* int join */) { - int join = luaL_checkinteger(L, 1); - nvgLineJoin(g_guiState.vg, join); - return 0; + int join = luaL_checkinteger(L, 1); + nvgLineJoin(g_guiState.vg, join); + return 0; } static int lStrokeColor(lua_State* L /*int r, int g, int b, int a = 255*/) { - int r, g, b, a; - r = luaL_checkinteger(L, 1); - g = luaL_checkinteger(L, 2); - b = luaL_checkinteger(L, 3); - if (lua_gettop(L) == 4) - { - a = luaL_checkinteger(L, 4); - } - else - { - a = 255; - } - nvgStrokeColor(g_guiState.vg, nvgRGBA(r, g, b, a)); - return 0; + int r, g, b, a; + r = luaL_checkinteger(L, 1); + g = luaL_checkinteger(L, 2); + b = luaL_checkinteger(L, 3); + if (lua_gettop(L) == 4) + { + a = luaL_checkinteger(L, 4); + } + else + { + a = 255; + } + nvgStrokeColor(g_guiState.vg, nvgRGBA(r, g, b, a)); + return 0; } static int lFastRect(lua_State* L /*float x, float y, float w, float h*/) { - float x, y, w, h; - x = luaL_checknumber(L, 1); - y = luaL_checknumber(L, 2); - w = luaL_checknumber(L, 3); - h = luaL_checknumber(L, 4); - Mesh quad = Graphics::MeshGenerators::Quad(g_gl, Vector2(x, y), Vector2(w, h)); - MaterialParameterSet params; - params.SetParameter("color", g_guiState.fillColor); - g_guiState.rq->DrawScissored(g_guiState.scissor, g_guiState.t, quad, *g_guiState.fillMaterial, params); - return 0; + float x, y, w, h; + x = luaL_checknumber(L, 1); + y = luaL_checknumber(L, 2); + w = luaL_checknumber(L, 3); + h = luaL_checknumber(L, 4); + Mesh quad = Graphics::MeshGenerators::Quad(g_gl, Vector2(x, y), Vector2(w, h)); + MaterialParameterSet params; + params.SetParameter("color", g_guiState.fillColor); + g_guiState.rq->DrawScissored(g_guiState.scissor, g_guiState.t, quad, *g_guiState.fillMaterial, params); + return 0; } static int lFastText(lua_State* L /* String utf8string, float x, float y */) { - const char* s; - float x, y; - s = luaL_checkstring(L, 1); - x = luaL_checknumber(L, 2); - y = luaL_checknumber(L, 3); - - WString text = Utility::ConvertToWString(s); - Text te = g_guiState.currentFont->CreateText(text, g_guiState.fontSize); - Transform textTransform = g_guiState.t; - textTransform *= Transform::Translation(Vector2(x, y)); - - //vertical alignment - if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_BOTTOM) != 0) - { - textTransform *= Transform::Translation(Vector2(0, -te->size.y)); - } - else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_MIDDLE) != 0) - { - textTransform *= Transform::Translation(Vector2(0, -te->size.y / 2)); - } - - //horizontal alignment - if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_CENTER) != 0) - { - textTransform *= Transform::Translation(Vector2(-te->size.x / 2, 0)); - } - else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_RIGHT) != 0) - { - textTransform *= Transform::Translation(Vector2(-te->size.x, 0)); - } - MaterialParameterSet params; - params.SetParameter("color", g_guiState.fillColor); - g_guiState.rq->DrawScissored(g_guiState.scissor, textTransform, te, *g_guiState.fontMaterial, params); - - return 0; + const char* s; + float x, y; + s = luaL_checkstring(L, 1); + x = luaL_checknumber(L, 2); + y = luaL_checknumber(L, 3); + + WString text = Utility::ConvertToWString(s); + Text te = g_guiState.currentFont->CreateText(text, g_guiState.fontSize); + Transform textTransform = g_guiState.t; + textTransform *= Transform::Translation(Vector2(x, y)); + + //vertical alignment + if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_BOTTOM) != 0) + { + textTransform *= Transform::Translation(Vector2(0, -te->size.y)); + } + else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_MIDDLE) != 0) + { + textTransform *= Transform::Translation(Vector2(0, -te->size.y / 2)); + } + + //horizontal alignment + if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_CENTER) != 0) + { + textTransform *= Transform::Translation(Vector2(-te->size.x / 2, 0)); + } + else if ((g_guiState.textAlign & (int)NVGalign::NVG_ALIGN_RIGHT) != 0) + { + textTransform *= Transform::Translation(Vector2(-te->size.x, 0)); + } + MaterialParameterSet params; + params.SetParameter("color", g_guiState.fillColor); + g_guiState.rq->DrawScissored(g_guiState.scissor, textTransform, te, *g_guiState.fontMaterial, params); + + return 0; } static int lRoundedRect(lua_State* L /* float x, float y, float w, float h, float r */) { - float x = luaL_checknumber(L, 1); - float y = luaL_checknumber(L, 2); - float w = luaL_checknumber(L, 3); - float h = luaL_checknumber(L, 4); - float r = luaL_checknumber(L, 5); - nvgRoundedRect(g_guiState.vg, x, y, w, h, r); - return 0; + float x = luaL_checknumber(L, 1); + float y = luaL_checknumber(L, 2); + float w = luaL_checknumber(L, 3); + float h = luaL_checknumber(L, 4); + float r = luaL_checknumber(L, 5); + nvgRoundedRect(g_guiState.vg, x, y, w, h, r); + return 0; } static int lRoundedRectVarying(lua_State* L /* float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft */) { - float x = luaL_checknumber(L, 1); - float y = luaL_checknumber(L, 2); - float w = luaL_checknumber(L, 3); - float h = luaL_checknumber(L, 4); - float radTopLeft = luaL_checknumber(L, 5); - float radTopRight = luaL_checknumber(L, 6); - float radBottomRight = luaL_checknumber(L, 7); - float radBottomLeft = luaL_checknumber(L, 8); - nvgRoundedRectVarying(g_guiState.vg, x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft); - return 0; + float x = luaL_checknumber(L, 1); + float y = luaL_checknumber(L, 2); + float w = luaL_checknumber(L, 3); + float h = luaL_checknumber(L, 4); + float radTopLeft = luaL_checknumber(L, 5); + float radTopRight = luaL_checknumber(L, 6); + float radBottomRight = luaL_checknumber(L, 7); + float radBottomLeft = luaL_checknumber(L, 8); + nvgRoundedRectVarying(g_guiState.vg, x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft); + return 0; } static int lEllipse(lua_State* L /* float cx, float cy, float rx, float ry */) { - float cx = luaL_checknumber(L, 1); - float cy = luaL_checknumber(L, 2); - float rx = luaL_checknumber(L, 3); - float ry = luaL_checknumber(L, 4); - nvgEllipse(g_guiState.vg, cx, cy, rx, ry); - return 0; + float cx = luaL_checknumber(L, 1); + float cy = luaL_checknumber(L, 2); + float rx = luaL_checknumber(L, 3); + float ry = luaL_checknumber(L, 4); + nvgEllipse(g_guiState.vg, cx, cy, rx, ry); + return 0; } static int lCircle(lua_State* L /* float cx, float cy, float r */) { - float cx = luaL_checknumber(L, 1); - float cy = luaL_checknumber(L, 2); - float r = luaL_checknumber(L, 3); - nvgCircle(g_guiState.vg, cx, cy, r); - return 0; + float cx = luaL_checknumber(L, 1); + float cy = luaL_checknumber(L, 2); + float r = luaL_checknumber(L, 3); + nvgCircle(g_guiState.vg, cx, cy, r); + return 0; } static int lSkewX(lua_State* L /* float angle */) { - float angle = luaL_checknumber(L, 1); - nvgSkewX(g_guiState.vg, angle); - return 0; + float angle = luaL_checknumber(L, 1); + nvgSkewX(g_guiState.vg, angle); + return 0; } static int lSkewY(lua_State* L /* float angle */) { - float angle = luaL_checknumber(L, 1); - nvgSkewY(g_guiState.vg, angle); - return 0; + float angle = luaL_checknumber(L, 1); + nvgSkewY(g_guiState.vg, angle); + return 0; } static int lLinearGradient(lua_State* L /* float sx, float sy, float ex, float ey */) { - float sx = luaL_checknumber(L, 1); - float sy = luaL_checknumber(L, 2); - float ex = luaL_checknumber(L, 3); - float ey = luaL_checknumber(L, 4); - NVGpaint paint = nvgLinearGradient(g_guiState.vg, sx, sy, ex, ey, g_guiState.inrColor, g_guiState.otrColor); - g_guiState.paintCache[L].Add(g_guiState.nextPaintId[L], paint); - lua_pushnumber(L, g_guiState.nextPaintId[L]); - g_guiState.nextPaintId[L]++; - return 1; + float sx = luaL_checknumber(L, 1); + float sy = luaL_checknumber(L, 2); + float ex = luaL_checknumber(L, 3); + float ey = luaL_checknumber(L, 4); + NVGpaint paint = nvgLinearGradient(g_guiState.vg, sx, sy, ex, ey, g_guiState.inrColor, g_guiState.otrColor); + g_guiState.paintCache[L].Add(g_guiState.nextPaintId[L], paint); + lua_pushnumber(L, g_guiState.nextPaintId[L]); + g_guiState.nextPaintId[L]++; + return 1; } static int lBoxGradient(lua_State* L /* float x, float y, float w, float h, float r, float f */) { - float x = luaL_checknumber(L, 1); - float y = luaL_checknumber(L, 2); - float w = luaL_checknumber(L, 3); - float h = luaL_checknumber(L, 4); - float r = luaL_checknumber(L, 5); - float f = luaL_checknumber(L, 6); - NVGpaint paint = nvgBoxGradient(g_guiState.vg, x, y, w, h, r, f, g_guiState.inrColor, g_guiState.otrColor); - g_guiState.paintCache[L].Add(g_guiState.nextPaintId[L], paint); - lua_pushnumber(L, g_guiState.nextPaintId[L]); - g_guiState.nextPaintId[L]++; - return 1; + float x = luaL_checknumber(L, 1); + float y = luaL_checknumber(L, 2); + float w = luaL_checknumber(L, 3); + float h = luaL_checknumber(L, 4); + float r = luaL_checknumber(L, 5); + float f = luaL_checknumber(L, 6); + NVGpaint paint = nvgBoxGradient(g_guiState.vg, x, y, w, h, r, f, g_guiState.inrColor, g_guiState.otrColor); + g_guiState.paintCache[L].Add(g_guiState.nextPaintId[L], paint); + lua_pushnumber(L, g_guiState.nextPaintId[L]); + g_guiState.nextPaintId[L]++; + return 1; } static int lRadialGradient(lua_State* L /* float cx, float cy, float inr, float outr */) { - float cx = luaL_checknumber(L, 1); - float cy = luaL_checknumber(L, 2); - float inr = luaL_checknumber(L, 3); - float outr = luaL_checknumber(L, 4); - NVGpaint paint = nvgRadialGradient(g_guiState.vg, cx, cy, inr, outr, g_guiState.inrColor, g_guiState.otrColor); - g_guiState.paintCache[L].Add(g_guiState.nextPaintId[L], paint); - lua_pushnumber(L, g_guiState.nextPaintId[L]); - g_guiState.nextPaintId[L]++; - return 1; + float cx = luaL_checknumber(L, 1); + float cy = luaL_checknumber(L, 2); + float inr = luaL_checknumber(L, 3); + float outr = luaL_checknumber(L, 4); + NVGpaint paint = nvgRadialGradient(g_guiState.vg, cx, cy, inr, outr, g_guiState.inrColor, g_guiState.otrColor); + g_guiState.paintCache[L].Add(g_guiState.nextPaintId[L], paint); + lua_pushnumber(L, g_guiState.nextPaintId[L]); + g_guiState.nextPaintId[L]++; + return 1; } static int lImagePattern(lua_State* L /* float ox, float oy, float ex, float ey, float angle, int image, float alpha */) { - float ox = luaL_checknumber(L, 1); - float oy = luaL_checknumber(L, 2); - float ex = luaL_checknumber(L, 3); - float ey = luaL_checknumber(L, 4); - float angle = luaL_checknumber(L, 5); - int image = luaL_checkinteger(L, 6); - float alpha = luaL_checknumber(L, 7); - NVGpaint paint = nvgImagePattern(g_guiState.vg, ox, oy, ex, ey, angle, image, alpha); - g_guiState.paintCache[L].Add(g_guiState.nextPaintId[L], paint); - lua_pushnumber(L, g_guiState.nextPaintId[L]); - g_guiState.nextPaintId[L]++; - return 1; + float ox = luaL_checknumber(L, 1); + float oy = luaL_checknumber(L, 2); + float ex = luaL_checknumber(L, 3); + float ey = luaL_checknumber(L, 4); + float angle = luaL_checknumber(L, 5); + int image = luaL_checkinteger(L, 6); + float alpha = luaL_checknumber(L, 7); + NVGpaint paint = nvgImagePattern(g_guiState.vg, ox, oy, ex, ey, angle, image, alpha); + g_guiState.paintCache[L].Add(g_guiState.nextPaintId[L], paint); + lua_pushnumber(L, g_guiState.nextPaintId[L]); + g_guiState.nextPaintId[L]++; + return 1; } static int lUpdateImagePattern(lua_State* L /*int paint, float ox, float oy, float ex, float ey, float angle, float alpha*/) { - int paint = luaL_checkinteger(L, 1); - if (!g_guiState.paintCache[L].Contains(paint)) - return 0; - NVGpaint& p = g_guiState.paintCache[L].at(paint); + int paint = luaL_checkinteger(L, 1); + if (!g_guiState.paintCache[L].Contains(paint)) + return 0; + NVGpaint& p = g_guiState.paintCache[L].at(paint); - float ox = luaL_checknumber(L, 2); - float oy = luaL_checknumber(L, 3); - float ex = luaL_checknumber(L, 4); - float ey = luaL_checknumber(L, 5); - float angle = luaL_checknumber(L, 6); - float alpha = luaL_checknumber(L, 7); + float ox = luaL_checknumber(L, 2); + float oy = luaL_checknumber(L, 3); + float ex = luaL_checknumber(L, 4); + float ey = luaL_checknumber(L, 5); + float angle = luaL_checknumber(L, 6); + float alpha = luaL_checknumber(L, 7); - nvgTransformIdentity(p.xform); - nvgTransformRotate(p.xform, angle); - p.xform[4] = ox; - p.xform[5] = oy; + nvgTransformIdentity(p.xform); + nvgTransformRotate(p.xform, angle); + p.xform[4] = ox; + p.xform[5] = oy; - p.extent[0] = ex; - p.extent[1] = ey; + p.extent[0] = ex; + p.extent[1] = ey; - p.innerColor.a = p.outerColor.a = alpha; - return 0; + p.innerColor.a = p.outerColor.a = alpha; + return 0; } static int lGradientColors(lua_State* L /*int ri, int gi, int bi, int ai, int ro, int go, int bo, int ao*/) { - int ri, gi, bi, ai, ro, go, bo, ao; - ri = luaL_checkinteger(L, 1); - gi = luaL_checkinteger(L, 2); - bi = luaL_checkinteger(L, 3); - ai = luaL_checkinteger(L, 4); - ro = luaL_checkinteger(L, 5); - go = luaL_checkinteger(L, 6); - bo = luaL_checkinteger(L, 7); - ao = luaL_checkinteger(L, 8); - g_guiState.inrColor = nvgRGBA(ri, gi, bi, ai); - g_guiState.otrColor = nvgRGBA(ro, go, bo, ao); - return 0; + int ri, gi, bi, ai, ro, go, bo, ao; + ri = luaL_checkinteger(L, 1); + gi = luaL_checkinteger(L, 2); + bi = luaL_checkinteger(L, 3); + ai = luaL_checkinteger(L, 4); + ro = luaL_checkinteger(L, 5); + go = luaL_checkinteger(L, 6); + bo = luaL_checkinteger(L, 7); + ao = luaL_checkinteger(L, 8); + g_guiState.inrColor = nvgRGBA(ri, gi, bi, ai); + g_guiState.otrColor = nvgRGBA(ro, go, bo, ao); + return 0; } static int lFillPaint(lua_State* L /* int paint */) { - int paint = luaL_checkinteger(L, 1); - nvgFillPaint(g_guiState.vg, g_guiState.paintCache[L][paint]); - return 0; + int paint = luaL_checkinteger(L, 1); + nvgFillPaint(g_guiState.vg, g_guiState.paintCache[L][paint]); + return 0; } static int lStrokePaint(lua_State* L /* int paint */) { - int paint = luaL_checkinteger(L, 1); - nvgStrokePaint(g_guiState.vg, g_guiState.paintCache[L][paint]); - return 0; + int paint = luaL_checkinteger(L, 1); + nvgStrokePaint(g_guiState.vg, g_guiState.paintCache[L][paint]); + return 0; } static int lSave(lua_State* L /* */) { - g_guiState.transformStack.push_back(g_guiState.t); - nvgSave(g_guiState.vg); - return 0; + g_guiState.transformStack.push_back(g_guiState.t); + nvgSave(g_guiState.vg); + return 0; } static int lRestore(lua_State* L /* */) { - g_guiState.t = g_guiState.transformStack.back(); - g_guiState.transformStack.pop_back(); - nvgRestore(g_guiState.vg); - return 0; + g_guiState.t = g_guiState.transformStack.back(); + g_guiState.transformStack.pop_back(); + nvgRestore(g_guiState.vg); + return 0; } static int lReset(lua_State* L /* */) { - g_guiState.transformStack.clear(); - nvgReset(g_guiState.vg); - return 0; + g_guiState.transformStack.clear(); + nvgReset(g_guiState.vg); + return 0; } static int lPathWinding(lua_State* L /* int dir */) { - int dir = luaL_checkinteger(L, 1); - nvgPathWinding(g_guiState.vg, dir); - return 0; + int dir = luaL_checkinteger(L, 1); + nvgPathWinding(g_guiState.vg, dir); + return 0; } static int lScissor(lua_State* L /* float x, float y, float w, float h */) { - float x = luaL_checknumber(L, 1); - float y = luaL_checknumber(L, 2); - float w = luaL_checknumber(L, 3); - float h = luaL_checknumber(L, 4); - - Vector3 scale = g_guiState.t.GetScale(); - Vector3 pos = g_guiState.t.GetPosition(); - Vector2 topLeft = pos.xy() + Vector2(x + g_guiState.scissorOffset, y); - Vector2 size = Vector2(w, h) * scale.xy(); + float x = luaL_checknumber(L, 1); + float y = luaL_checknumber(L, 2); + float w = luaL_checknumber(L, 3); + float h = luaL_checknumber(L, 4); + + Vector3 scale = g_guiState.t.GetScale(); + Vector3 pos = g_guiState.t.GetPosition(); + Vector2 topLeft = pos.xy() + Vector2(x + g_guiState.scissorOffset, y); + Vector2 size = Vector2(w, h) * scale.xy(); + + g_guiState.scissor = Rect(topLeft, size); - g_guiState.scissor = Rect(topLeft, size); - - nvgScissor(g_guiState.vg, x, y, w, h); - return 0; + nvgScissor(g_guiState.vg, x, y, w, h); + return 0; } static int lIntersectScissor(lua_State* L /* float x, float y, float w, float h */) { - float x = luaL_checknumber(L, 1); - float y = luaL_checknumber(L, 2); - float w = luaL_checknumber(L, 3); - float h = luaL_checknumber(L, 4); - nvgIntersectScissor(g_guiState.vg, x, y, w, h); - return 0; + float x = luaL_checknumber(L, 1); + float y = luaL_checknumber(L, 2); + float w = luaL_checknumber(L, 3); + float h = luaL_checknumber(L, 4); + nvgIntersectScissor(g_guiState.vg, x, y, w, h); + return 0; } static int lResetScissor(lua_State* L /* */) { - nvgResetScissor(g_guiState.vg); - g_guiState.scissor = Rect(0, 0, -1, -1); - return 0; + nvgResetScissor(g_guiState.vg); + g_guiState.scissor = Rect(0, 0, -1, -1); + return 0; } static int lTextBounds(lua_State* L /*float x, float y, char* s*/) { - float x = luaL_checknumber(L, 1); - float y = luaL_checknumber(L, 2); - const char* s = luaL_checkstring(L, 3); - float bounds[4] = { 0,0,0,0 }; + float x = luaL_checknumber(L, 1); + float y = luaL_checknumber(L, 2); + const char* s = luaL_checkstring(L, 3); + float bounds[4] = { 0,0,0,0 }; - nvgTextBounds(g_guiState.vg, x, y, s, 0, bounds); - for (size_t i = 0; i < 4; i++) - { - lua_pushnumber(L, bounds[i]); - } - return 4; + nvgTextBounds(g_guiState.vg, x, y, s, 0, bounds); + for (size_t i = 0; i < 4; i++) + { + lua_pushnumber(L, bounds[i]); + } + return 4; } static int lLabelSize(lua_State* L /*int label*/) { - int label = luaL_checkinteger(L, 1); - Label l = g_guiState.textCache[L][label]; - lua_pushnumber(L, l.text->size.x / l.scale); - lua_pushnumber(L, l.text->size.y / l.scale); - return 2; + int label = luaL_checkinteger(L, 1); + Label l = g_guiState.textCache[L][label]; + lua_pushnumber(L, l.text->size.x / l.scale); + lua_pushnumber(L, l.text->size.y / l.scale); + return 2; } static int lFastTextSize(lua_State* L /* char* text */) { - const char* s; - s = luaL_checkstring(L, 1); + const char* s; + s = luaL_checkstring(L, 1); - WString text = Utility::ConvertToWString(s); - Text l = g_guiState.currentFont->CreateText(text, g_guiState.fontSize); - lua_pushnumber(L, l->size.x); - lua_pushnumber(L, l->size.y); - return 2; + WString text = Utility::ConvertToWString(s); + Text l = g_guiState.currentFont->CreateText(text, g_guiState.fontSize); + lua_pushnumber(L, l->size.x); + lua_pushnumber(L, l->size.y); + return 2; } static int lImageSize(lua_State* L /*int image*/) { - int image = luaL_checkinteger(L, 1); - int w,h; - nvgImageSize(g_guiState.vg, image, &w, &h); - lua_pushnumber(L,w); - lua_pushnumber(L,h); - return 2; + int image = luaL_checkinteger(L, 1); + int w, h; + nvgImageSize(g_guiState.vg, image, &w, &h); + lua_pushnumber(L, w); + lua_pushnumber(L, h); + return 2; } static int DisposeGUI(lua_State* state) { - g_guiState.textCache[state].clear(); - g_guiState.textCache.erase(state); - g_guiState.paintCache[state].clear(); - g_guiState.paintCache.erase(state); + g_guiState.textCache[state].clear(); + g_guiState.textCache.erase(state); + g_guiState.paintCache[state].clear(); + g_guiState.paintCache.erase(state); - - for(auto&& i : g_guiState.vgImages[state]) - { - nvgDeleteImage(g_guiState.vg, i); - } + for (auto&& i : g_guiState.vgImages[state]) + { + nvgDeleteImage(g_guiState.vg, i); + } - Vector keysToDelete; - for (auto anim : g_guiState.animations) - { - if (anim.second->State != state) - continue; - anim.second->Cancelled.store(true); + Vector keysToDelete; + for (auto anim : g_guiState.animations) + { + if (anim.second->State != state) + continue; - if(anim.second->JobThread && anim.second->JobThread->joinable()) - anim.second->JobThread->join(); - anim.second->Frames.clear(); - anim.second->FrameData.clear(); - keysToDelete.Add(anim.first); - delete anim.second->JobThread; - nvgDeleteImage(g_guiState.vg, anim.first); - } - for (int k : keysToDelete) - { - g_guiState.animations.erase(k); - } + anim.second->Cancelled.store(true); - return 0; + if (anim.second->JobThread && anim.second->JobThread->joinable()) + anim.second->JobThread->join(); + anim.second->Frames.clear(); + anim.second->FrameData.clear(); + keysToDelete.Add(anim.first); + delete anim.second->JobThread; + nvgDeleteImage(g_guiState.vg, anim.first); + } + for (int k : keysToDelete) + { + g_guiState.animations.erase(k); + } + + return 0; } static int lArc(lua_State* L /* float cx, float cy, float r, float a0, float a1, int dir */) { - float cx = luaL_checknumber(L, 1); - float cy = luaL_checknumber(L, 2); - float r = luaL_checknumber(L, 3); - float a0 = luaL_checknumber(L, 4); - float a1 = luaL_checknumber(L, 5); - int dir = luaL_checkinteger(L, 6); - nvgArc(g_guiState.vg, cx, cy, r, a0, a1, dir); - return 0; + float cx = luaL_checknumber(L, 1); + float cy = luaL_checknumber(L, 2); + float r = luaL_checknumber(L, 3); + float a0 = luaL_checknumber(L, 4); + float a1 = luaL_checknumber(L, 5); + int dir = luaL_checkinteger(L, 6); + nvgArc(g_guiState.vg, cx, cy, r, a0, a1, dir); + return 0; } static int lGlobalCompositeOperation(lua_State* L /* int op */) { - int op = luaL_checkinteger(L, 1); - nvgGlobalCompositeOperation(g_guiState.vg, op); - return 0; + int op = luaL_checkinteger(L, 1); + nvgGlobalCompositeOperation(g_guiState.vg, op); + return 0; } static int lGlobalCompositeBlendFunc(lua_State* L /* int sfactor, int dfactor */) { - int sfactor = luaL_checkinteger(L, 1); - int dfactor = luaL_checkinteger(L, 2); - nvgGlobalCompositeBlendFunc(g_guiState.vg, sfactor, dfactor); - return 0; + int sfactor = luaL_checkinteger(L, 1); + int dfactor = luaL_checkinteger(L, 2); + nvgGlobalCompositeBlendFunc(g_guiState.vg, sfactor, dfactor); + return 0; } static int lGlobalCompositeBlendFuncSeparate(lua_State* L /* int srcRGB, int dstRGB, int srcAlpha, int dstAlpha */) { - int srcRGB = luaL_checkinteger(L, 1); - int dstRGB = luaL_checkinteger(L, 2); - int srcAlpha = luaL_checkinteger(L, 3); - int dstAlpha = luaL_checkinteger(L, 4); - nvgGlobalCompositeBlendFuncSeparate(g_guiState.vg, srcRGB, dstRGB, srcAlpha, dstAlpha); - return 0; + int srcRGB = luaL_checkinteger(L, 1); + int dstRGB = luaL_checkinteger(L, 2); + int srcAlpha = luaL_checkinteger(L, 3); + int dstAlpha = luaL_checkinteger(L, 4); + nvgGlobalCompositeBlendFuncSeparate(g_guiState.vg, srcRGB, dstRGB, srcAlpha, dstAlpha); + return 0; } static int lGlobalAlpha(lua_State* L /*float alpha*/) { - nvgGlobalAlpha(g_guiState.vg, luaL_checknumber(L, 1)); - return 0; -} \ No newline at end of file + nvgGlobalAlpha(g_guiState.vg, luaL_checknumber(L, 1)); + return 0; +} diff --git a/GUI/stdafx.cpp b/GUI/stdafx.cpp index 1577c4e3b..fd4f341c7 100644 --- a/GUI/stdafx.cpp +++ b/GUI/stdafx.cpp @@ -1 +1 @@ -#include "stdafx.h" \ No newline at end of file +#include "stdafx.h" diff --git a/GUI/stdafx.h b/GUI/stdafx.h index c9a1ccf34..b5a8cc28f 100644 --- a/GUI/stdafx.h +++ b/GUI/stdafx.h @@ -5,4 +5,4 @@ using namespace Graphics; // Asset loading macro -#define CheckedLoad(__stmt) if(!(__stmt)){Logf("Failed to load asset [%s]", Logger::Error, #__stmt); return false; } \ No newline at end of file +#define CheckedLoad(__stmt) if(!(__stmt)){Logf("Failed to load asset [%s]", Logger::Error, #__stmt); return false; } diff --git a/Graphics/include/Graphics/Font.hpp b/Graphics/include/Graphics/Font.hpp index cdf1a7830..f2209625d 100644 --- a/Graphics/include/Graphics/Font.hpp +++ b/Graphics/include/Graphics/Font.hpp @@ -7,50 +7,50 @@ namespace Graphics { - /* - A prerendered text object, contains all the vertices and texture sheets to draw itself - */ - class TextRes - { - friend class Font_Impl; - struct FontSize* fontSize; - Ref mesh; - public: - ~TextRes(); - Ref GetTexture(); - Ref GetMesh() { return mesh; } - void Draw(); - //width, line height, base height - Vector3 size; - }; + /* + A prerendered text object, contains all the vertices and texture sheets to draw itself + */ + class TextRes + { + friend class Font_Impl; + struct FontSize* fontSize; + Ref mesh; + public: + ~TextRes(); + Ref GetTexture(); + Ref GetMesh() { return mesh; } + void Draw(); + //width, line height, base height + Vector3 size; + }; - /* - Font class, can create Text objects - */ - class FontRes - { - public: - virtual ~FontRes() = default; - static Ref Create(class OpenGL* gl, const String& assetPath); - static bool InitLibrary(); - static void FreeLibrary(); - public: - // Text rendering options - enum TextOptions - { - None = 0, - Monospace = 0x1, - }; + /* + Font class, can create Text objects + */ + class FontRes + { + public: + virtual ~FontRes() = default; + static Ref Create(class OpenGL* gl, const String& assetPath); + static bool InitLibrary(); + static void FreeLibrary(); + public: + // Text rendering options + enum TextOptions + { + None = 0, + Monospace = 0x1, + }; - // Renders the input string into a drawable text object - virtual Ref CreateText(const WString& str, uint32 nFontSize, TextOptions options = TextOptions::None) = 0; + // Renders the input string into a drawable text object + virtual Ref CreateText(const WString& str, uint32 nFontSize, TextOptions options = TextOptions::None) = 0; - private: - static bool LoadFallbackFont(); - }; + private: + static bool LoadFallbackFont(); + }; - typedef Ref Font; - typedef Ref Text; + typedef Ref Font; + typedef Ref Text; - DEFINE_RESOURCE_TYPE(Font, FontRes) + DEFINE_RESOURCE_TYPE(Font, FontRes) } diff --git a/Graphics/include/Graphics/GL.hpp b/Graphics/include/Graphics/GL.hpp index 8b5fc12ec..fc1b1bddf 100644 --- a/Graphics/include/Graphics/GL.hpp +++ b/Graphics/include/Graphics/GL.hpp @@ -1,6 +1,6 @@ /* - OpenGL include file - This file includes the appropriate opengl headers for the platform + OpenGL include file + This file includes the appropriate opengl headers for the platform */ #pragma once diff --git a/Graphics/include/Graphics/Gamepad.hpp b/Graphics/include/Graphics/Gamepad.hpp index 1e44ed3e6..4eae5d041 100644 --- a/Graphics/include/Graphics/Gamepad.hpp +++ b/Graphics/include/Graphics/Gamepad.hpp @@ -1,21 +1,21 @@ #pragma once /* - Gamepad Abstraction + Gamepad Abstraction */ class Gamepad { public: - virtual ~Gamepad() = default; + virtual ~Gamepad() = default; - virtual bool GetButton(uint8 button) const = 0; - virtual float GetAxis(uint8 idx) const = 0; + virtual bool GetButton(uint8 button) const = 0; + virtual float GetAxis(uint8 idx) const = 0; - virtual uint32 NumButtons() const = 0; - virtual uint32 NumAxes() const = 0; + virtual uint32 NumButtons() const = 0; + virtual uint32 NumAxes() const = 0; - // Gamepad button event - Delegate OnButtonPressed; - // Gamepad button event - Delegate OnButtonReleased; -}; \ No newline at end of file + // Gamepad button event + Delegate OnButtonPressed; + // Gamepad button event + Delegate OnButtonReleased; +}; diff --git a/Graphics/include/Graphics/Image.hpp b/Graphics/include/Graphics/Image.hpp index d0e8c0ec9..a0c866319 100644 --- a/Graphics/include/Graphics/Image.hpp +++ b/Graphics/include/Graphics/Image.hpp @@ -3,51 +3,51 @@ namespace Graphics { - /* - RGBA8 image class - The bits have the same layout as the Colori class - */ - class ImageRes - { - public: - virtual ~ImageRes() = default; - static Ref Create(const String& assetPath); - static Ref Create(Vector2i size = Vector2i()); - static Ref Create(Buffer& b); - static Ref Screenshot(class OpenGL* gl, Vector2i size = Vector2i(), Vector2i pos = Vector2i()); - public: - virtual void SetSize(Vector2i size) = 0; - virtual void ReSize(Vector2i size) = 0; - virtual Vector2i GetSize() const = 0; - virtual Colori* GetBits() = 0; - virtual const Colori* GetBits() const = 0; - virtual void SavePNG(const String& file) = 0; - }; + /* + RGBA8 image class + The bits have the same layout as the Colori class + */ + class ImageRes + { + public: + virtual ~ImageRes() = default; + static Ref Create(const String& assetPath); + static Ref Create(Vector2i size = Vector2i()); + static Ref Create(Buffer& b); + static Ref Screenshot(class OpenGL* gl, Vector2i size = Vector2i(), Vector2i pos = Vector2i()); + public: + virtual void SetSize(Vector2i size) = 0; + virtual void ReSize(Vector2i size) = 0; + virtual Vector2i GetSize() const = 0; + virtual Colori* GetBits() = 0; + virtual const Colori* GetBits() const = 0; + virtual void SavePNG(const String& file) = 0; + }; - /* - Sprite map - Adding images to this will pack the image into a final image that contains all the added images - After this the UV coordinates of these images can be asked for given and image index + /* + Sprite map + Adding images to this will pack the image into a final image that contains all the added images + After this the UV coordinates of these images can be asked for given and image index - !! The packing is not optimal as the images are stacked for bottom to top and placed in columns based on their width - */ - class TextureRes; - class SpriteMapRes - { - public: - virtual ~SpriteMapRes() = default; - static Ref Create(); - public: - virtual uint32 AddSegment(Ref image) = 0; - virtual void Clear() = 0; - virtual Ref GetImage() = 0; - virtual Ref GenerateTexture(class OpenGL* gl) = 0; - virtual Shared::Recti GetCoords(uint32 nIndex) = 0; - }; + !! The packing is not optimal as the images are stacked for bottom to top and placed in columns based on their width + */ + class TextureRes; + class SpriteMapRes + { + public: + virtual ~SpriteMapRes() = default; + static Ref Create(); + public: + virtual uint32 AddSegment(Ref image) = 0; + virtual void Clear() = 0; + virtual Ref GetImage() = 0; + virtual Ref GenerateTexture(class OpenGL* gl) = 0; + virtual Shared::Recti GetCoords(uint32 nIndex) = 0; + }; - typedef Ref Image; - typedef Ref SpriteMap; + typedef Ref Image; + typedef Ref SpriteMap; - DEFINE_RESOURCE_TYPE(Image, ImageRes) - DEFINE_RESOURCE_TYPE(SpriteMap, SpriteMapRes) -} \ No newline at end of file + DEFINE_RESOURCE_TYPE(Image, ImageRes) + DEFINE_RESOURCE_TYPE(SpriteMap, SpriteMapRes) +} diff --git a/Graphics/include/Graphics/ImageLoader.hpp b/Graphics/include/Graphics/ImageLoader.hpp index b9a674505..87dd7ca9a 100644 --- a/Graphics/include/Graphics/ImageLoader.hpp +++ b/Graphics/include/Graphics/ImageLoader.hpp @@ -2,17 +2,17 @@ namespace Graphics { - /* - Static image loader - Supports the following formats: - - PNG (RGB8, RGBA8) - - JPEG - */ - class ImageRes; - class ImageLoader - { - public: - static bool Load(ImageRes* outPtr, const String& fullPath); - static bool Load(ImageRes* outPtr, Buffer& b); - }; -} \ No newline at end of file + /* + Static image loader + Supports the following formats: + - PNG (RGB8, RGBA8) + - JPEG + */ + class ImageRes; + class ImageLoader + { + public: + static bool Load(ImageRes* outPtr, const String& fullPath); + static bool Load(ImageRes* outPtr, Buffer& b); + }; +} diff --git a/Graphics/include/Graphics/Keys.hpp b/Graphics/include/Graphics/Keys.hpp index e19c950bb..d4bbabc2b 100644 --- a/Graphics/include/Graphics/Keys.hpp +++ b/Graphics/include/Graphics/Keys.hpp @@ -8,87 +8,87 @@ namespace Graphics { - DefineEnum(MouseButton, - Left = 0, - Middle, - Right) + DefineEnum(MouseButton, + Left = 0, + Middle, + Right) - DefineBitflagEnum(ModifierKeys, - None = 0, - Alt = 1, - Ctrl = 2, - Shift = 4) + DefineBitflagEnum(ModifierKeys, + None = 0, + Alt = 1, + Ctrl = 2, + Shift = 4) - DefineEnum(Key, - None = 0, - // Top Row Keys - Escape, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - PrntScr, - ScrollLock, - Pause, - // Alpha keys - A = 'A', - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - // Top row number keys - Tilde, - Top0, - Top1, - Top2, - Top3, - Top4, - Top5, - Top6, - Top7, - Top8, - Top9, - Minus, - Plus, - Backspace, - // Arrow keys - ArrowLeft, - ArrowUp, - ArrowRight, - ArrowDown, - // Other keys - Space, - Return, - PageUp, - PageDown, - Tab) -} \ No newline at end of file + DefineEnum(Key, + None = 0, + // Top Row Keys + Escape, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + PrntScr, + ScrollLock, + Pause, + // Alpha keys + A = 'A', + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + // Top row number keys + Tilde, + Top0, + Top1, + Top2, + Top3, + Top4, + Top5, + Top6, + Top7, + Top8, + Top9, + Minus, + Plus, + Backspace, + // Arrow keys + ArrowLeft, + ArrowUp, + ArrowRight, + ArrowDown, + // Other keys + Space, + Return, + PageUp, + PageDown, + Tab) +} diff --git a/Graphics/include/Graphics/Material.hpp b/Graphics/include/Graphics/Material.hpp index 9b05ba01b..ee67fa8a7 100644 --- a/Graphics/include/Graphics/Material.hpp +++ b/Graphics/include/Graphics/Material.hpp @@ -5,96 +5,96 @@ namespace Graphics { - /* A single parameter that is set for a material */ - struct MaterialParameter - { - CopyableBuffer parameterData; - uint32 parameterType; + /* A single parameter that is set for a material */ + struct MaterialParameter + { + CopyableBuffer parameterData; + uint32 parameterType; - template - static MaterialParameter Create(const T& obj, uint32 type) - { - MaterialParameter r; - r.Bind(obj); - r.parameterType = type; - return r; - } - template - void Bind(const T& obj) - { - parameterData.resize(sizeof(T)); - memcpy(parameterData.data(), &obj, sizeof(T)); - } - template - const T& Get() - { - assert(sizeof(T) == parameterData.size()); - return *(T*)parameterData.data(); - } + template + static MaterialParameter Create(const T& obj, uint32 type) + { + MaterialParameter r; + r.Bind(obj); + r.parameterType = type; + return r; + } + template + void Bind(const T& obj) + { + parameterData.resize(sizeof(T)); + memcpy(parameterData.data(), &obj, sizeof(T)); + } + template + const T& Get() + { + assert(sizeof(T) == parameterData.size()); + return *(T*)parameterData.data(); + } - bool operator==(const MaterialParameter& other) const - { - if(parameterType != other.parameterType) - return false; - if(parameterData.size() != other.parameterData.size()) - return false; - return memcmp(parameterData.data(), other.parameterData.data(), parameterData.size()) == 0; - } - }; + bool operator==(const MaterialParameter& other) const + { + if (parameterType != other.parameterType) + return false; + if (parameterData.size() != other.parameterData.size()) + return false; + return memcmp(parameterData.data(), other.parameterData.data(), parameterData.size()) == 0; + } + }; - /* - A list of parameters that is set for a material - use SetParameter(name, param) to set any parameter by name - */ - class MaterialParameterSet : public Map - { - public: - using Map::Map; - void SetParameter(const String& name, int sc); - void SetParameter(const String& name, float sc); - void SetParameter(const String& name, const Vector4& vec); - void SetParameter(const String& name, const Colori& color); - void SetParameter(const String& name, const Vector2& vec2); - void SetParameter(const String & name, const Vector3 & vec3); - void SetParameter(const String& name, const Vector2i& vec2); - void SetParameter(const String& name, const Transform& tf); - void SetParameter(const String& name, Ref tex); - }; + /* + A list of parameters that is set for a material + use SetParameter(name, param) to set any parameter by name + */ + class MaterialParameterSet : public Map + { + public: + using Map::Map; + void SetParameter(const String& name, int sc); + void SetParameter(const String& name, float sc); + void SetParameter(const String& name, const Vector4& vec); + void SetParameter(const String& name, const Colori& color); + void SetParameter(const String& name, const Vector2& vec2); + void SetParameter(const String& name, const Vector3& vec3); + void SetParameter(const String& name, const Vector2i& vec2); + void SetParameter(const String& name, const Transform& tf); + void SetParameter(const String& name, Ref tex); + }; - enum class MaterialBlendMode - { - Normal, - Additive, - Multiply, - }; + enum class MaterialBlendMode + { + Normal, + Additive, + Multiply, + }; - /* - Abstracts the use of shaders/uniforms/pipelines into a single interface class - */ - class MaterialRes - { - public: - virtual ~MaterialRes() = default; - // Create a default material - static Ref Create(class OpenGL* gl); - // Create a material that has both a vertex and fragment shader - static Ref Create(class OpenGL* gl, const String& vsPath, const String& fsPath); + /* + Abstracts the use of shaders/uniforms/pipelines into a single interface class + */ + class MaterialRes + { + public: + virtual ~MaterialRes() = default; + // Create a default material + static Ref Create(class OpenGL* gl); + // Create a material that has both a vertex and fragment shader + static Ref Create(class OpenGL* gl, const String& vsPath, const String& fsPath); - bool opaque = true; - MaterialBlendMode blendMode = MaterialBlendMode::Normal; + bool opaque = true; + MaterialBlendMode blendMode = MaterialBlendMode::Normal; - public: - virtual void AssignShader(ShaderType t, Shader shader) = 0; - virtual void Bind(const RenderState& rs, const MaterialParameterSet& params = MaterialParameterSet()) = 0; + public: + virtual void AssignShader(ShaderType t, Shader shader) = 0; + virtual void Bind(const RenderState& rs, const MaterialParameterSet& params = MaterialParameterSet()) = 0; - // Only binds parameters to the current shader - virtual void BindParameters(const MaterialParameterSet& params, const Transform& worldTransform) = 0; - virtual bool HasUniform(String name) = 0; - // Bind only shaders/pipeline to context - virtual void BindToContext() = 0; - }; + // Only binds parameters to the current shader + virtual void BindParameters(const MaterialParameterSet& params, const Transform& worldTransform) = 0; + virtual bool HasUniform(String name) = 0; + // Bind only shaders/pipeline to context + virtual void BindToContext() = 0; + }; - typedef Ref Material; + typedef Ref Material; - DEFINE_RESOURCE_TYPE(Material, MaterialRes) -} \ No newline at end of file + DEFINE_RESOURCE_TYPE(Material, MaterialRes) +} diff --git a/Graphics/include/Graphics/Mesh.hpp b/Graphics/include/Graphics/Mesh.hpp index 0c1246d52..6918774bb 100644 --- a/Graphics/include/Graphics/Mesh.hpp +++ b/Graphics/include/Graphics/Mesh.hpp @@ -4,49 +4,49 @@ namespace Graphics { - /* The type that tells how a mesh is drawn */ - enum class PrimitiveType - { - TriangleList = 0, - TriangleStrip, - TriangleFan, - LineList, - LineStrip, - PointList, - }; + /* The type that tells how a mesh is drawn */ + enum class PrimitiveType + { + TriangleList = 0, + TriangleStrip, + TriangleFan, + LineList, + LineStrip, + PointList, + }; - /* - Simple mesh object - */ - class MeshRes - { - public: - virtual ~MeshRes() = default; - static Ref Create(class OpenGL* gl); - public: - // Sets the vertex point data for this mesh - // must be set before drawing - // the vertex type must inherit from VertexFormat to automatically detect the correct format - template - void SetData(const Vector& verts) - { - SetData(verts.data(), verts.size(), T::GetDescriptors()); - } + /* + Simple mesh object + */ + class MeshRes + { + public: + virtual ~MeshRes() = default; + static Ref Create(class OpenGL* gl); + public: + // Sets the vertex point data for this mesh + // must be set before drawing + // the vertex type must inherit from VertexFormat to automatically detect the correct format + template + void SetData(const Vector& verts) + { + SetData(verts.data(), verts.size(), T::GetDescriptors()); + } - // Sets how the point data is interpreted and drawn - // must be set before drawing - virtual void SetPrimitiveType(PrimitiveType pt) = 0; - virtual PrimitiveType GetPrimitiveType() const = 0; - // Draws the mesh - virtual void Draw() = 0; - // Draws the mesh after if has already been drawn once, reuse of bound objects - virtual void Redraw() = 0; + // Sets how the point data is interpreted and drawn + // must be set before drawing + virtual void SetPrimitiveType(PrimitiveType pt) = 0; + virtual PrimitiveType GetPrimitiveType() const = 0; + // Draws the mesh + virtual void Draw() = 0; + // Draws the mesh after if has already been drawn once, reuse of bound objects + virtual void Redraw() = 0; - private: - virtual void SetData(const void* pData, size_t vertexCount, const VertexFormatList& desc) = 0; - }; + private: + virtual void SetData(const void* pData, size_t vertexCount, const VertexFormatList& desc) = 0; + }; - typedef Ref Mesh; + typedef Ref Mesh; - DEFINE_RESOURCE_TYPE(Mesh, MeshRes) -} \ No newline at end of file + DEFINE_RESOURCE_TYPE(Mesh, MeshRes) +} diff --git a/Graphics/include/Graphics/MeshGenerators.hpp b/Graphics/include/Graphics/MeshGenerators.hpp index e401afdbf..e10739c3c 100644 --- a/Graphics/include/Graphics/MeshGenerators.hpp +++ b/Graphics/include/Graphics/MeshGenerators.hpp @@ -3,33 +3,33 @@ namespace Graphics { - // Generates meshes based on certain parameters - // generated attributes are always in the following order: - // - Position - // - Texture Coordinates - // - Color - // - Normal - namespace MeshGenerators - { - using Shared::Rect3D; - using Shared::Rect; + // Generates meshes based on certain parameters + // generated attributes are always in the following order: + // - Position + // - Texture Coordinates + // - Color + // - Normal + namespace MeshGenerators + { + using Shared::Rect3D; + using Shared::Rect; - struct SimpleVertex : public VertexFormat - { - SimpleVertex() = default; - SimpleVertex(Vector3 pos, Vector2 tex) : pos(pos), tex(tex) {}; - Vector3 pos; - Vector2 tex; - }; + struct SimpleVertex : public VertexFormat + { + SimpleVertex() = default; + SimpleVertex(Vector3 pos, Vector2 tex) : pos(pos), tex(tex) {}; + Vector3 pos; + Vector2 tex; + }; - Mesh Quad(OpenGL* gl, Vector2 pos, Vector2 size = Vector2(1, 1)); + Mesh Quad(OpenGL* gl, Vector2 pos, Vector2 size = Vector2(1, 1)); - // Generates vertices for a quad from a given rectangle, with given uv coordinate rectangle - // the position top = +y - // the uv has bottom = +y - // Triangle List - void GenerateSimpleXYQuad(Rect3D r, Rect uv, Vector& out); + // Generates vertices for a quad from a given rectangle, with given uv coordinate rectangle + // the position top = +y + // the uv has bottom = +y + // Triangle List + void GenerateSimpleXYQuad(Rect3D r, Rect uv, Vector& out); - void GenerateSimpleXZQuad(Rect3D r, Rect uv, Vector& out); - } -} \ No newline at end of file + void GenerateSimpleXZQuad(Rect3D r, Rect uv, Vector& out); + } +} diff --git a/Graphics/include/Graphics/OpenGL.hpp b/Graphics/include/Graphics/OpenGL.hpp index e68b4bb2e..3d9f98c96 100644 --- a/Graphics/include/Graphics/OpenGL.hpp +++ b/Graphics/include/Graphics/OpenGL.hpp @@ -5,44 +5,44 @@ namespace Graphics { #ifdef _WIN32 - void __stdcall GLDebugProc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); + void __stdcall GLDebugProc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); #else - void GLDebugProc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); + void GLDebugProc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); #endif - /* - OpenGL context wrapper with common functionality - */ - using Shared::Recti; - class OpenGL - { - class ShaderRes* m_activeShaders[3] = { 0 }; - uint32 m_mainProgramPipeline; - class OpenGL_Impl* m_impl; - Window* m_window; + /* + OpenGL context wrapper with common functionality + */ + using Shared::Recti; + class OpenGL + { + class ShaderRes* m_activeShaders[3] = { 0 }; + uint32 m_mainProgramPipeline; + class OpenGL_Impl* m_impl; + Window* m_window; - friend class ShaderRes; - friend class TextureRes; - friend class MeshRes; - friend class Shader_Impl; - friend class RenderQueue; + friend class ShaderRes; + friend class TextureRes; + friend class MeshRes; + friend class Shader_Impl; + friend class RenderQueue; - public: - OpenGL(); - virtual ~OpenGL(); - void InitResourceManagers(); - bool Init(Window& window, uint32 antialiasing); + public: + OpenGL(); + virtual ~OpenGL(); + void InitResourceManagers(); + bool Init(Window& window, uint32 antialiasing); - Recti GetViewport() const; - uint32 GetFramebufferHandle(); - void SetViewport(Vector2i size); - void SetViewport(Recti vp); - void MakeCurrent(); - void ReleaseCurrent(); + Recti GetViewport() const; + uint32 GetFramebufferHandle(); + void SetViewport(Vector2i size); + void SetViewport(Recti vp); + void MakeCurrent(); + void ReleaseCurrent(); - // Check if the calling thread is the thread that runs this OpenGL context - bool IsOpenGLThread() const; + // Check if the calling thread is the thread that runs this OpenGL context + bool IsOpenGLThread() const; - virtual void SwapBuffers(); - }; -} \ No newline at end of file + virtual void SwapBuffers(); + }; +} diff --git a/Graphics/include/Graphics/ParticleEmitter.hpp b/Graphics/include/Graphics/ParticleEmitter.hpp index 9ff435b2b..c89594b02 100644 --- a/Graphics/include/Graphics/ParticleEmitter.hpp +++ b/Graphics/include/Graphics/ParticleEmitter.hpp @@ -6,34 +6,34 @@ namespace Graphics { - /* - Particle Emitter, which is a component of a particle system that handles the emission of particles together with the properties of the emitter particles - */ - class ParticleEmitter - { - public: - ParticleEmitter() = default; - ~ParticleEmitter(); + /* + Particle Emitter, which is a component of a particle system that handles the emission of particles together with the properties of the emitter particles + */ + class ParticleEmitter + { + public: + ParticleEmitter() = default; + ~ParticleEmitter(); - // Material used for the particle - Material material; + // Material used for the particle + Material material; - // Texture to use for the particle - Texture texture; + // Texture to use for the particle + Texture texture; - // Emitter location - Vector3 position; + // Emitter location + Vector3 position; - // Emitter duration - float duration = 5.0f; + // Emitter duration + float duration = 5.0f; - float scale = 1.0f; + float scale = 1.0f; - // Amount of loops to make - // 0 = forever - uint32 loops = 0; + // Amount of loops to make + // 0 = forever + uint32 loops = 0; - // Particle parameter accessors + // Particle parameter accessors #define PARTICLE_PARAMETER(__name, __type)\ void Set##__name(const IParticleParameter<__type>& param)\ {\ @@ -44,38 +44,38 @@ namespace Graphics } #include - // True after all loops are done playing - bool HasFinished() const { return m_finished; } + // True after all loops are done playing + bool HasFinished() const { return m_finished; } - // Restarts a particle emitter - void Reset(); + // Restarts a particle emitter + void Reset(); - // Stop spawning any particles - void Deactivate(); + // Stop spawning any particles + void Deactivate(); - private: - // Constructed by particle system - ParticleEmitter(class ParticleSystem_Impl* sys); - void Render(const class RenderState& rs, float deltaTime); - void m_ReallocatePool(uint32 newCapacity); + private: + // Constructed by particle system + ParticleEmitter(class ParticleSystem_Impl* sys); + void Render(const class RenderState& rs, float deltaTime); + void m_ReallocatePool(uint32 newCapacity); - float m_spawnCounter = 0; - float m_emitterTime = 0; - float m_emitterRate; - bool m_deactivated = false; - bool m_finished = false; - uint32 m_emitterLoopIndex = 0; - Mesh m_mesh; - friend class ParticleSystem_Impl; - friend class Particle; - ParticleSystem_Impl* m_system; + float m_spawnCounter = 0; + float m_emitterTime = 0; + float m_emitterRate; + bool m_deactivated = false; + bool m_finished = false; + uint32 m_emitterLoopIndex = 0; + Mesh m_mesh; + friend class ParticleSystem_Impl; + friend class Particle; + ParticleSystem_Impl* m_system; - class Particle* m_particles = nullptr; - uint32 m_poolSize = 0; + class Particle* m_particles = nullptr; + uint32 m_poolSize = 0; - // Particle parameters private + // Particle parameters private #define PARTICLE_PARAMETER(__name, __type)\ IParticleParameter<__type>* m_param_##__name = nullptr; #include - }; -} \ No newline at end of file + }; +} diff --git a/Graphics/include/Graphics/ParticleParameter.hpp b/Graphics/include/Graphics/ParticleParameter.hpp index 3d16bfdbe..005bcc495 100644 --- a/Graphics/include/Graphics/ParticleParameter.hpp +++ b/Graphics/include/Graphics/ParticleParameter.hpp @@ -9,214 +9,214 @@ these can then be set in Particle Systems for various properties namespace Graphics { - /* Base class for particle parameters */ - template - class IParticleParameter - { - public: - virtual ~IParticleParameter() = default; - // Used to initialize a starting attribute - virtual T Init(float systemTime) { return Sample(systemTime); } - // Used to process over lifetime events - virtual T Sample(float duration) = 0; - virtual T GetMax() = 0; - virtual IParticleParameter* Duplicate() const = 0; - }; + /* Base class for particle parameters */ + template + class IParticleParameter + { + public: + virtual ~IParticleParameter() = default; + // Used to initialize a starting attribute + virtual T Init(float systemTime) { return Sample(systemTime); } + // Used to process over lifetime events + virtual T Sample(float duration) = 0; + virtual T GetMax() = 0; + virtual IParticleParameter* Duplicate() const = 0; + }; - // Macro for implementing the Duplicate() function + // Macro for implementing the Duplicate() function #define IMPLEMENT_DUPLICATE(__type, __self) IParticleParameter<__type>* Duplicate() const override { return new __self(*this); } - /* A constant value at all times */ - template - class PPConstant : public IParticleParameter - { - public: - PPConstant(const T& val) : val(val) {}; - T Sample(float in) override - { - return val; - } - T GetMax() override - { - return val; - } - IMPLEMENT_DUPLICATE(T, PPConstant); - private: - T val; - }; + /* A constant value at all times */ + template + class PPConstant : public IParticleParameter + { + public: + PPConstant(const T& val) : val(val) {}; + T Sample(float in) override + { + return val; + } + T GetMax() override + { + return val; + } + IMPLEMENT_DUPLICATE(T, PPConstant); + private: + T val; + }; - /* A random value between A and B */ - template - class PPRandomRange : public IParticleParameter - { - public: - PPRandomRange(const T& min, const T& max) : min(min), max(max) { delta = max - min; }; - T Init(float systemTime) override - { - return Sample(Random::Float()); - } - T Sample(float in) override - { - return (max - min) * in + min; - } - T GetMax() override - { - return Math::Max(max, min); - } - IMPLEMENT_DUPLICATE(T, PPRandomRange); - private: - T delta; - T min, max; - }; + /* A random value between A and B */ + template + class PPRandomRange : public IParticleParameter + { + public: + PPRandomRange(const T& min, const T& max) : min(min), max(max) { delta = max - min; }; + T Init(float systemTime) override + { + return Sample(Random::Float()); + } + T Sample(float in) override + { + return (max - min) * in + min; + } + T GetMax() override + { + return Math::Max(max, min); + } + IMPLEMENT_DUPLICATE(T, PPRandomRange); + private: + T delta; + T min, max; + }; - /* A value that transitions from A to B over time */ - template - class PPRange : public IParticleParameter - { - public: - PPRange(const T& min, const T& max) : min(min), max(max) { delta = max - min; }; - T Sample(float in) override - { - return (max - min) * in + min; - } - T GetMax() override - { - return Math::Max(max, min); - } - IMPLEMENT_DUPLICATE(T, PPRange); - private: - T delta; - T min, max; - }; + /* A value that transitions from A to B over time */ + template + class PPRange : public IParticleParameter + { + public: + PPRange(const T& min, const T& max) : min(min), max(max) { delta = max - min; }; + T Sample(float in) override + { + return (max - min) * in + min; + } + T GetMax() override + { + return Math::Max(max, min); + } + IMPLEMENT_DUPLICATE(T, PPRange); + private: + T delta; + T min, max; + }; - /* A 2 point gradient with a fade in value */ - template - class PPRangeFadeIn : public IParticleParameter - { - public: - PPRangeFadeIn(const T& min, const T& max, const float fadeIn) : fadeIn(fadeIn), min(min), max(max) - { - delta = max - min; - rangeOut = 1.0f - fadeIn; - }; - T Sample(float in) override - { - if(in < fadeIn) - { - return min * (in / fadeIn); - } - else - { - return (in - fadeIn) / rangeOut * (max - min) + min; - } - } - T GetMax() override - { - return Math::Max(max, min); - } - IMPLEMENT_DUPLICATE(T, PPRangeFadeIn); - private: - float rangeOut; - float fadeIn; - T delta; - T min, max; - }; + /* A 2 point gradient with a fade in value */ + template + class PPRangeFadeIn : public IParticleParameter + { + public: + PPRangeFadeIn(const T& min, const T& max, const float fadeIn) : fadeIn(fadeIn), min(min), max(max) + { + delta = max - min; + rangeOut = 1.0f - fadeIn; + }; + T Sample(float in) override + { + if (in < fadeIn) + { + return min * (in / fadeIn); + } + else + { + return (in - fadeIn) / rangeOut * (max - min) + min; + } + } + T GetMax() override + { + return Math::Max(max, min); + } + IMPLEMENT_DUPLICATE(T, PPRangeFadeIn); + private: + float rangeOut; + float fadeIn; + T delta; + T min, max; + }; - /* A random sphere particle parameter */ - class PPSphere : public IParticleParameter - { - public: - PPSphere(float radius) : radius(radius) - { - } - Vector3 Sample(float in) override - { - return Vector3(Random::FloatRange(-1.0f, 1.0f), Random::FloatRange(-1.0f, 1.0f), Random::FloatRange(-1.0f, 1.0f)) * radius; - } - Vector3 GetMax() override - { - return Vector3(radius); - } - IMPLEMENT_DUPLICATE(Vector3, PPSphere); - private: - float radius; - }; + /* A random sphere particle parameter */ + class PPSphere : public IParticleParameter + { + public: + PPSphere(float radius) : radius(radius) + { + } + Vector3 Sample(float in) override + { + return Vector3(Random::FloatRange(-1.0f, 1.0f), Random::FloatRange(-1.0f, 1.0f), Random::FloatRange(-1.0f, 1.0f)) * radius; + } + Vector3 GetMax() override + { + return Vector3(radius); + } + IMPLEMENT_DUPLICATE(Vector3, PPSphere); + private: + float radius; + }; - /* A centered random box particle parameter with a size */ - class PPBox : public IParticleParameter - { - public: - PPBox(Vector3 size) : size(size) - { - } - Vector3 Sample(float in) override - { - Vector3 offset = -size * 0.5f; - offset.x += Random::Float() * size.x; - offset.y += Random::Float() * size.y; - offset.z += Random::Float() * size.z; - return offset; - } - Vector3 GetMax() override - { - return size; - } - IMPLEMENT_DUPLICATE(Vector3, PPBox); - private: - Vector3 size; - }; + /* A centered random box particle parameter with a size */ + class PPBox : public IParticleParameter + { + public: + PPBox(Vector3 size) : size(size) + { + } + Vector3 Sample(float in) override + { + Vector3 offset = -size * 0.5f; + offset.x += Random::Float() * size.x; + offset.y += Random::Float() * size.y; + offset.z += Random::Float() * size.z; + return offset; + } + Vector3 GetMax() override + { + return size; + } + IMPLEMENT_DUPLICATE(Vector3, PPBox); + private: + Vector3 size; + }; - /* - Cone directional particle parameter - Cone angle is in degrees - */ - class PPCone : public IParticleParameter - { - private: - float lengthMin, lengthMax; - float angle; - Transform mat; - public: - PPCone(Vector3 dir, float angle, float lengthMin, float lengthMax) - : lengthMin(lengthMin), lengthMax(lengthMax), angle(angle * Math::degToRad) - { - Vector3 normal = dir.Normalized(); - Vector3 tangent = Vector3(normal.y, -normal.x, normal.z); - if(normal.x == 0 && normal.y == 0) - tangent = Vector3(normal.z, normal.y, -normal.x); - tangent = VectorMath::Cross(tangent, normal).Normalized(); - Vector3 bitangent = VectorMath::Cross(tangent, normal); - mat = Transform::FromAxes(bitangent, tangent, normal); - } - virtual Vector3 Sample(float in) override - { - float length = Random::FloatRange(lengthMin, lengthMax); + /* + Cone directional particle parameter + Cone angle is in degrees + */ + class PPCone : public IParticleParameter + { + private: + float lengthMin, lengthMax; + float angle; + Transform mat; + public: + PPCone(Vector3 dir, float angle, float lengthMin, float lengthMax) + : lengthMin(lengthMin), lengthMax(lengthMax), angle(angle* Math::degToRad) + { + Vector3 normal = dir.Normalized(); + Vector3 tangent = Vector3(normal.y, -normal.x, normal.z); + if (normal.x == 0 && normal.y == 0) + tangent = Vector3(normal.z, normal.y, -normal.x); + tangent = VectorMath::Cross(tangent, normal).Normalized(); + Vector3 bitangent = VectorMath::Cross(tangent, normal); + mat = Transform::FromAxes(bitangent, tangent, normal); + } + virtual Vector3 Sample(float in) override + { + float length = Random::FloatRange(lengthMin, lengthMax); - float a = Random::FloatRange(-1, 1); - float b = Random::FloatRange(-1, 1); - float c = Random::FloatRange(-1, 1); - a = a * a * Math::Sign(a); - b = b * b * Math::Sign(b); - c = c * c * Math::Sign(c); + float a = Random::FloatRange(-1, 1); + float b = Random::FloatRange(-1, 1); + float c = Random::FloatRange(-1, 1); + a = a * a * Math::Sign(a); + b = b * b * Math::Sign(b); + c = c * c * Math::Sign(c); - Vector3 v = Vector3( - -sin(a * angle), - sin(b * angle), - cos(c * angle) - ); - v = v.Normalized(); + Vector3 v = Vector3( + -sin(a * angle), + sin(b * angle), + cos(c * angle) + ); + v = v.Normalized(); - v = mat.TransformDirection(v); - v *= length; - return v; - } - Vector3 GetMax() override - { - return Vector3(0, 0, lengthMax); - } - IMPLEMENT_DUPLICATE(Vector3, PPCone); + v = mat.TransformDirection(v); + v *= length; + return v; + } + Vector3 GetMax() override + { + return Vector3(0, 0, lengthMax); + } + IMPLEMENT_DUPLICATE(Vector3, PPCone); - }; + }; #undef IMPLEMENT_DUPLICATE -} \ No newline at end of file +} diff --git a/Graphics/include/Graphics/ParticleParameters.hpp b/Graphics/include/Graphics/ParticleParameters.hpp index c43fad774..a5b26117e 100644 --- a/Graphics/include/Graphics/ParticleParameters.hpp +++ b/Graphics/include/Graphics/ParticleParameters.hpp @@ -1,6 +1,6 @@ -/* - Macro file for creating particle parameter entries in particle systems - when this file is included it will call PARTICLE_PARAMETER() or PARTICLE_DEFAULT() if they are defined with the values in this file +/* + Macro file for creating particle parameter entries in particle systems + when this file is included it will call PARTICLE_PARAMETER() or PARTICLE_DEFAULT() if they are defined with the values in this file */ #ifdef PARTICLE_PARAMETER PARTICLE_PARAMETER(Lifetime, float) @@ -32,4 +32,4 @@ PARTICLE_DEFAULT(StartDrag, PPConstant(0)) PARTICLE_DEFAULT(Gravity, PPConstant({ 0, 0, 0 })) PARTICLE_DEFAULT(SpawnRate, PPConstant(20)) #undef PARTICLE_DEFAULT -#endif \ No newline at end of file +#endif diff --git a/Graphics/include/Graphics/ParticleSystem.hpp b/Graphics/include/Graphics/ParticleSystem.hpp index cbc4ce555..86b87170b 100644 --- a/Graphics/include/Graphics/ParticleSystem.hpp +++ b/Graphics/include/Graphics/ParticleSystem.hpp @@ -4,24 +4,24 @@ namespace Graphics { - /* - Particles system - contains emitters and handles the cleanup/lifetime of them. - */ - class ParticleSystemRes - { - public: - virtual ~ParticleSystemRes() = default; - static Ref Create(class OpenGL* gl); - public: - // Create a new emitter - virtual Ref AddEmitter() = 0; - virtual void Render(const class RenderState& rs, float deltaTime) = 0; - // Removes all active particle systems - virtual void Reset() = 0; - }; + /* + Particles system + contains emitters and handles the cleanup/lifetime of them. + */ + class ParticleSystemRes + { + public: + virtual ~ParticleSystemRes() = default; + static Ref Create(class OpenGL* gl); + public: + // Create a new emitter + virtual Ref AddEmitter() = 0; + virtual void Render(const class RenderState& rs, float deltaTime) = 0; + // Removes all active particle systems + virtual void Reset() = 0; + }; - typedef Ref ParticleSystem; + typedef Ref ParticleSystem; - DEFINE_RESOURCE_TYPE(ParticleSystem, ParticleSystemRes) -} \ No newline at end of file + DEFINE_RESOURCE_TYPE(ParticleSystem, ParticleSystemRes) +} diff --git a/Graphics/include/Graphics/RenderQueue.hpp b/Graphics/include/Graphics/RenderQueue.hpp index 547afcfbd..efa2be294 100644 --- a/Graphics/include/Graphics/RenderQueue.hpp +++ b/Graphics/include/Graphics/RenderQueue.hpp @@ -7,72 +7,72 @@ namespace Graphics { - using Shared::Rect; - /* - Represents a draw command that can be executed in a render queue - */ - class RenderQueueItem - { - public: - virtual ~RenderQueueItem() = default; - }; + using Shared::Rect; + /* + Represents a draw command that can be executed in a render queue + */ + class RenderQueueItem + { + public: + virtual ~RenderQueueItem() = default; + }; - // Most basic draw command that only contains a material, it's parameters and a world transform - class SimpleDrawCall : public RenderQueueItem - { - public: - SimpleDrawCall(); - // The mesh to draw - Mesh mesh; - // Material to use - Material mat; - MaterialParameterSet params; - // The world transform - Transform worldTransform; - // Scissor rectangle - Rect scissorRect; - }; + // Most basic draw command that only contains a material, it's parameters and a world transform + class SimpleDrawCall : public RenderQueueItem + { + public: + SimpleDrawCall(); + // The mesh to draw + Mesh mesh; + // Material to use + Material mat; + MaterialParameterSet params; + // The world transform + Transform worldTransform; + // Scissor rectangle + Rect scissorRect; + }; - // Command for points/lines with size/width parameter - class PointDrawCall : public RenderQueueItem - { - public: - // List of points/lines - Mesh mesh; - Material mat; - MaterialParameterSet params; - float size; - }; + // Command for points/lines with size/width parameter + class PointDrawCall : public RenderQueueItem + { + public: + // List of points/lines + Mesh mesh; + Material mat; + MaterialParameterSet params; + float size; + }; - /* - This class is a queue that collects draw commands - each of these is stored together with their wanted render state. + /* + This class is a queue that collects draw commands + each of these is stored together with their wanted render state. - When Process is called, the commands are sorted and grouped, then sent to the graphics pipeline. - */ - class RenderQueue : public Unique - { - public: - RenderQueue() = default; - RenderQueue(OpenGL* ogl, const RenderState& rs); - RenderQueue(RenderQueue&& other); - RenderQueue& operator=(RenderQueue&& other); - ~RenderQueue(); - // Processes all render commands - void Process(bool clearQueue = true); - // Clears all the render commands in the queue - void Clear(); - void Draw(Transform worldTransform, Mesh m, Material mat, const MaterialParameterSet& params = MaterialParameterSet()); - void Draw(Transform worldTransform, Ref text, Material mat, const MaterialParameterSet& params = MaterialParameterSet()); - void DrawScissored(Rect scissor, Transform worldTransform, Mesh m, Material mat, const MaterialParameterSet& params = MaterialParameterSet()); - void DrawScissored(Rect scissor, Transform worldTransform, Ref text, Material mat, const MaterialParameterSet& params = MaterialParameterSet()); + When Process is called, the commands are sorted and grouped, then sent to the graphics pipeline. + */ + class RenderQueue : public Unique + { + public: + RenderQueue() = default; + RenderQueue(OpenGL* ogl, const RenderState& rs); + RenderQueue(RenderQueue&& other); + RenderQueue& operator=(RenderQueue&& other); + ~RenderQueue(); + // Processes all render commands + void Process(bool clearQueue = true); + // Clears all the render commands in the queue + void Clear(); + void Draw(Transform worldTransform, Mesh m, Material mat, const MaterialParameterSet& params = MaterialParameterSet()); + void Draw(Transform worldTransform, Ref text, Material mat, const MaterialParameterSet& params = MaterialParameterSet()); + void DrawScissored(Rect scissor, Transform worldTransform, Mesh m, Material mat, const MaterialParameterSet& params = MaterialParameterSet()); + void DrawScissored(Rect scissor, Transform worldTransform, Ref text, Material mat, const MaterialParameterSet& params = MaterialParameterSet()); - // Draw for lines/points with point size parameter - void DrawPoints(Mesh m, Material mat, const MaterialParameterSet& params, float pointSize); + // Draw for lines/points with point size parameter + void DrawPoints(Mesh m, Material mat, const MaterialParameterSet& params, float pointSize); - private: - RenderState m_renderState; - Vector m_orderedCommands; - class OpenGL* m_ogl = nullptr; - }; -} \ No newline at end of file + private: + RenderState m_renderState; + Vector m_orderedCommands; + class OpenGL* m_ogl = nullptr; + }; +} diff --git a/Graphics/include/Graphics/RenderState.hpp b/Graphics/include/Graphics/RenderState.hpp index d95c3f569..85e1063b5 100644 --- a/Graphics/include/Graphics/RenderState.hpp +++ b/Graphics/include/Graphics/RenderState.hpp @@ -2,17 +2,17 @@ namespace Graphics { - /* - Contains all values that get passed as built-in values to material shaders - */ - class RenderState - { - public: - Transform worldTransform; - Transform projectionTransform; - Transform cameraTransform; - Vector2i viewportSize; - float aspectRatio; - float time; - }; -} \ No newline at end of file + /* + Contains all values that get passed as built-in values to material shaders + */ + class RenderState + { + public: + Transform worldTransform; + Transform projectionTransform; + Transform cameraTransform; + Vector2i viewportSize; + float aspectRatio; + float time; + }; +} diff --git a/Graphics/include/Graphics/ResourceManagers.hpp b/Graphics/include/Graphics/ResourceManagers.hpp index c5c521e18..34ad2d4ce 100644 --- a/Graphics/include/Graphics/ResourceManagers.hpp +++ b/Graphics/include/Graphics/ResourceManagers.hpp @@ -4,48 +4,48 @@ namespace Graphics { - /* - Holds all graphics resource managers used - */ - class ResourceManagers - { - private: - void m_TickAll(); + /* + Holds all graphics resource managers used + */ + class ResourceManagers + { + private: + void m_TickAll(); - public: - ResourceManagers(); - ~ResourceManagers(); - static void DestroyResourceManager(ResourceType type); - template - static void DestroyResourceManager() - { - DestroyResourceManager(E); - } + public: + ResourceManagers(); + ~ResourceManagers(); + static void DestroyResourceManager(ResourceType type); + template + static void DestroyResourceManager() + { + DestroyResourceManager(E); + } - template - static void CreateResourceManager() - { - AssignResourceManager(E, new ResourceManager::Type>()); - } - static void AssignResourceManager(ResourceType type, IResourceManager* mgr); + template + static void CreateResourceManager() + { + AssignResourceManager(E, new ResourceManager::Type>()); + } + static void AssignResourceManager(ResourceType type, IResourceManager* mgr); - template - static ResourceManager::Type>& GetResourceManager() - { - return *(ResourceManager::Type>*)GetResourceManager(E); - } - static IResourceManager* GetResourceManager(ResourceType type); + template + static ResourceManager::Type>& GetResourceManager() + { + return *(ResourceManager::Type>*)GetResourceManager(E); + } + static IResourceManager* GetResourceManager(ResourceType type); - static void SuspendGC(); - static void ContinueGC(); - static void TickAll(); + static void SuspendGC(); + static void ContinueGC(); + static void TickAll(); - private: - }; + private: + }; - template - ResourceManager::Type>& GetResourceManager() - { - return ResourceManagers::GetResourceManager(); - } -} \ No newline at end of file + template + ResourceManager::Type>& GetResourceManager() + { + return ResourceManagers::GetResourceManager(); + } +} diff --git a/Graphics/include/Graphics/ResourceTypes.hpp b/Graphics/include/Graphics/ResourceTypes.hpp index 4ce78cb92..e553b23a9 100644 --- a/Graphics/include/Graphics/ResourceTypes.hpp +++ b/Graphics/include/Graphics/ResourceTypes.hpp @@ -2,7 +2,7 @@ namespace Graphics { - // Macro used to create specializations for the ResourceManagerTypes struct + // Macro used to create specializations for the ResourceManagerTypes struct #define DEFINE_RESOURCE_TYPE(_EnumMember, _Type)\ template<> struct ResourceManagerTypes\ {\ @@ -10,23 +10,23 @@ template<> struct ResourceManagerTypes\ }; /* Enum of all graphics resource types in this library */ - enum class ResourceType - { - Image = 0, - SpriteMap, - Texture, - Font, - Mesh, - Shader, - Material, - ParticleSystem, - _Length - }; + enum class ResourceType + { + Image = 0, + SpriteMap, + Texture, + Font, + Mesh, + Shader, + Material, + ParticleSystem, + _Length + }; - template - struct ResourceManagerTypes - { - // Dummy for gcc compiler - typedef void Type; - }; -} \ No newline at end of file + template + struct ResourceManagerTypes + { + // Dummy for gcc compiler + typedef void Type; + }; +} diff --git a/Graphics/include/Graphics/Shader.hpp b/Graphics/include/Graphics/Shader.hpp index 1a70630e9..89dd1ca90 100644 --- a/Graphics/include/Graphics/Shader.hpp +++ b/Graphics/include/Graphics/Shader.hpp @@ -4,45 +4,45 @@ namespace Graphics { #ifndef EMBEDDED - extern const uint32 shaderStageMap[]; + extern const uint32 shaderStageMap[]; #endif - /* Enum of supported shader types */ - enum class ShaderType - { - Vertex, - Fragment, - Geometry - }; + /* Enum of supported shader types */ + enum class ShaderType + { + Vertex, + Fragment, + Geometry + }; - /* - A single unlinked OpenGL shader - */ - class ShaderRes - { - public: - virtual ~ShaderRes() = default; - static Ref Create(class OpenGL* gl, ShaderType type, const String& assetPath); - static void Unbind(class OpenGL* gl, ShaderType type); - friend class OpenGL; - public: - // Tries to hot-reload the shader program, only works if _DEBUG is defined - // returns true if the program was changed and thus the handle value also changed - virtual bool UpdateHotReload() = 0; - #ifndef EMBEDDED - virtual void Bind() = 0; - virtual bool IsBound() const = 0; - virtual uint32 GetLocation(const String& name) const = 0; - uint32 operator[](const char* name) const - { - return GetLocation(name); - } - #endif - virtual uint32 Handle() = 0; + /* + A single unlinked OpenGL shader + */ + class ShaderRes + { + public: + virtual ~ShaderRes() = default; + static Ref Create(class OpenGL* gl, ShaderType type, const String& assetPath); + static void Unbind(class OpenGL* gl, ShaderType type); + friend class OpenGL; + public: + // Tries to hot-reload the shader program, only works if _DEBUG is defined + // returns true if the program was changed and thus the handle value also changed + virtual bool UpdateHotReload() = 0; +#ifndef EMBEDDED + virtual void Bind() = 0; + virtual bool IsBound() const = 0; + virtual uint32 GetLocation(const String& name) const = 0; + uint32 operator[](const char* name) const + { + return GetLocation(name); + } +#endif + virtual uint32 Handle() = 0; - virtual String GetOriginalName() const = 0; - }; + virtual String GetOriginalName() const = 0; + }; - typedef Ref Shader; + typedef Ref Shader; - DEFINE_RESOURCE_TYPE(Shader, ShaderRes) -} \ No newline at end of file + DEFINE_RESOURCE_TYPE(Shader, ShaderRes) +} diff --git a/Graphics/include/Graphics/Texture.hpp b/Graphics/include/Graphics/Texture.hpp index 992316616..009d4f683 100644 --- a/Graphics/include/Graphics/Texture.hpp +++ b/Graphics/include/Graphics/Texture.hpp @@ -3,52 +3,52 @@ namespace Graphics { - enum class TextureWrap - { - Repeat, - Mirror, - Clamp, - }; + enum class TextureWrap + { + Repeat, + Mirror, + Clamp, + }; - enum class TextureFormat - { - RGBA8, - D32, - Invalid - }; + enum class TextureFormat + { + RGBA8, + D32, + Invalid + }; - class ImageRes; - /* - OpenGL texture wrapper, can be created from an Image object or raw data - */ - class TextureRes - { - public: - virtual ~TextureRes() = default; - static Ref Create(class OpenGL* gl); - static Ref Create(class OpenGL* gl, Ref image); - static Ref CreateFromFrameBuffer(class OpenGL* gl, const Vector2i& resolution); - public: - virtual void Init(Vector2i size, TextureFormat format = TextureFormat::RGBA8) = 0; - virtual void SetData(Vector2i size, void* pData) = 0; - virtual void SetFromFrameBuffer(Vector2i pos = { 0, 0 }) = 0; - virtual void SetMipmaps(bool enabled) = 0; - virtual void SetFilter(bool enabled, bool mipFiltering = true, float anisotropic = 1.0f) = 0; - virtual const Vector2i& GetSize() const = 0; + class ImageRes; + /* + OpenGL texture wrapper, can be created from an Image object or raw data + */ + class TextureRes + { + public: + virtual ~TextureRes() = default; + static Ref Create(class OpenGL* gl); + static Ref Create(class OpenGL* gl, Ref image); + static Ref CreateFromFrameBuffer(class OpenGL* gl, const Vector2i& resolution); + public: + virtual void Init(Vector2i size, TextureFormat format = TextureFormat::RGBA8) = 0; + virtual void SetData(Vector2i size, void* pData) = 0; + virtual void SetFromFrameBuffer(Vector2i pos = { 0, 0 }) = 0; + virtual void SetMipmaps(bool enabled) = 0; + virtual void SetFilter(bool enabled, bool mipFiltering = true, float anisotropic = 1.0f) = 0; + virtual const Vector2i& GetSize() const = 0; - // Gives the aspect ratio correct height for a given width - float CalculateHeight(float width); - // Gives the aspect ratio correct width for a given height - float CalculateWidth(float height); + // Gives the aspect ratio correct height for a given width + float CalculateHeight(float width); + // Gives the aspect ratio correct width for a given height + float CalculateWidth(float height); - // Binds the texture to a given texture unit (default = 0) - virtual void Bind(uint32 index = 0) = 0; - virtual uint32 Handle() = 0; - virtual void SetWrap(TextureWrap u, TextureWrap v) = 0; - virtual TextureFormat GetFormat() const = 0; - }; + // Binds the texture to a given texture unit (default = 0) + virtual void Bind(uint32 index = 0) = 0; + virtual uint32 Handle() = 0; + virtual void SetWrap(TextureWrap u, TextureWrap v) = 0; + virtual TextureFormat GetFormat() const = 0; + }; - typedef Ref Texture; + typedef Ref Texture; - DEFINE_RESOURCE_TYPE(Texture, TextureRes) -} \ No newline at end of file + DEFINE_RESOURCE_TYPE(Texture, TextureRes) +} diff --git a/Graphics/include/Graphics/VertexFormat.hpp b/Graphics/include/Graphics/VertexFormat.hpp index bcf315782..1f0c3018b 100644 --- a/Graphics/include/Graphics/VertexFormat.hpp +++ b/Graphics/include/Graphics/VertexFormat.hpp @@ -3,83 +3,83 @@ namespace Graphics { - /* - Description of a single element in a vertex type - */ - struct VertexFormatDesc - { - uint32 components; - uint32 componentSize; - bool isFloat; - bool isSigned; - }; + /* + Description of a single element in a vertex type + */ + struct VertexFormatDesc + { + uint32 components; + uint32 componentSize; + bool isFloat; + bool isSigned; + }; - typedef Vector VertexFormatList; + typedef Vector VertexFormatList; - /* - Used By VertexFormat base class - see below - */ - struct VertexFormats - { - // Vector formats - template - static void AddFormat(VertexFormatList& dsc, VectorMath::VectorBase dummy) - { - VertexFormatDesc d; - d.components = N; - d.componentSize = sizeof(VT); - d.isFloat = std::is_floating_point::value; - d.isSigned = std::is_signed::value; - dsc.push_back(d); - } - // Integer and float formats - template - static void AddFormat(VertexFormatList& dsc, K dummy) - { - VertexFormatDesc d; - d.components = 1; - d.componentSize = sizeof(K); - d.isFloat = std::is_floating_point::value; - d.isSigned = std::is_signed::value; - dsc.push_back(d); - } + /* + Used By VertexFormat base class + see below + */ + struct VertexFormats + { + // Vector formats + template + static void AddFormat(VertexFormatList& dsc, VectorMath::VectorBase dummy) + { + VertexFormatDesc d; + d.components = N; + d.componentSize = sizeof(VT); + d.isFloat = std::is_floating_point::value; + d.isSigned = std::is_signed::value; + dsc.push_back(d); + } + // Integer and float formats + template + static void AddFormat(VertexFormatList& dsc, K dummy) + { + VertexFormatDesc d; + d.components = 1; + d.componentSize = sizeof(K); + d.isFloat = std::is_floating_point::value; + d.isSigned = std::is_signed::value; + dsc.push_back(d); + } - template - static void AddFormats(VertexFormatList& dsc) - { - AddFormat(dsc, TFirst()); - AddFormats(dsc); - } - }; + template + static void AddFormats(VertexFormatList& dsc) + { + AddFormat(dsc, TFirst()); + AddFormats(dsc); + } + }; - template<> void VertexFormats::AddFormats<0, void>(VertexFormatList& dsc); + template<> void VertexFormats::AddFormats<0, void>(VertexFormatList& dsc); - /* - Structure that generates a vertex format layout based on a variadic template argument list - use this as a base class for a vertex structure and pass the data members of your structure as template arguments in order + /* + Structure that generates a vertex format layout based on a variadic template argument list + use this as a base class for a vertex structure and pass the data members of your structure as template arguments in order - Example: - struct Vert : public VertexFormat - { - Vector3 pos; - float scale; - Vector4 color; - }; + Example: + struct Vert : public VertexFormat + { + Vector3 pos; + float scale; + Vector4 color; + }; - this adds a fuction Verts::GetDescriptors() which returns an array of VertexFormatDesc - */ - template - struct VertexFormat - { - static Vector GetDescriptors() - { - Vector res; - VertexFormats::AddFormats(res); - return res; - } - private: - - }; - -} \ No newline at end of file + this adds a fuction Verts::GetDescriptors() which returns an array of VertexFormatDesc + */ + template + struct VertexFormat + { + static Vector GetDescriptors() + { + Vector res; + VertexFormats::AddFormats(res); + return res; + } + private: + + }; + +} diff --git a/Graphics/include/Graphics/Window.hpp b/Graphics/include/Graphics/Window.hpp index 206290efe..e67876b64 100644 --- a/Graphics/include/Graphics/Window.hpp +++ b/Graphics/include/Graphics/Window.hpp @@ -6,157 +6,157 @@ namespace Graphics { - /// Windowed or bordered window style - enum class WindowStyle - { - Windowed, Borderless - }; - - /// Text input data - struct TextComposition - { - String composition; - int32 cursor; - int32 selectionLength; - }; - - /* - Simple window class that manages window messages, window style and input - Renamed from Window to DesktopWindow to avoid conflicts with libX11 on Linux - */ - class Window : Unique - { - public: - /// Window position and size parameter - struct PosAndShape - { - enum class Mode { Windowed, Fullscreen, WindowedFullscreen }; - Mode mode; - - PosAndShape(bool fullscreen, bool windowedFullscreen, const Vector2i& pos, const Vector2i& size, int32 monitorId, const Vector2i& fullscreenSize) - : PosAndShape(FlagsToMode(fullscreen, windowedFullscreen), pos, size, monitorId, fullscreenSize) {} - - PosAndShape(Mode mode, const Vector2i& pos, const Vector2i& size, int32 monitorId, const Vector2i& fullscreenSize) - : mode(mode), windowPos(pos), windowSize(size), monitorId(monitorId), fullscreenSize(fullscreenSize){} - - inline static Mode FlagsToMode(bool fullscreen, bool windowedFullscreen) - { - return fullscreen ? windowedFullscreen ? Mode::WindowedFullscreen : Mode::Fullscreen : Mode::Windowed; - } - - inline void SetMode(bool fullscreen, bool windowedFullscreen) - { - mode = FlagsToMode(fullscreen, windowedFullscreen); - } - - /// Position of the window; ignored when in fullscreen mode - Vector2i windowPos; - /// Size of the window; ignored when in fullscreen mode - Vector2i windowSize; - - /// Monitor to use when in fullscreen; ignored when in windowed mode - int32 monitorId; - /// Only used for windowed fullscreen mode - Vector2i fullscreenSize; - }; - - Window(Vector2i size = Vector2i(800, 600), uint8 samplecount = 0); - ~Window(); - // Show the window - void Show(); - // Hide the window - void Hide(); - // Call every frame to update the window message loop - // returns false if the window received a close message - bool Update(); - // On windows: returns the HWND - void* Handle(); - // Set the window title (caption) - void SetCaption(const WString& cap); - // Closes the window - void Close(); - - Vector2i GetMousePos(); - void SetMousePos(const Vector2i& pos); - void SetRelativeMouseMode(bool enabled); - bool GetRelativeMouseMode(); - - // Sets cursor to use - void SetCursor(const Ref& image, Vector2i hotspot = Vector2i(0,0)); - void SetCursorVisible(bool visible); - - // Switches between borderless and windowed - void SetWindowStyle(WindowStyle style); - - // Get full window position - Vector2i GetWindowPos() const; - - // Window Client area size - Vector2i GetWindowSize() const; - - // Set vsync setting - void SetVSync(int8 setting); - - // Window is active - bool IsActive() const; - - // Set window client area size - void SetPosAndShape(const PosAndShape& posAndShape, bool ensureInBound); - bool IsFullscreen() const; - - int GetDisplayIndex() const; - - // Checks if a key is pressed - bool IsKeyPressed(SDL_Scancode key) const; - - ModifierKeys GetModifierKeys() const; - - // Start allowing text input - void StartTextInput(); - // Stop allowing text input - void StopTextInput(); - // Used to get current IME working data - const TextComposition& GetTextComposition() const; - - // Show a simple message box - // level 0 = error, 1 = warning, 2 = info - void ShowMessageBox(const String& title, const String& message, int severity); - - // Show a simple confirmation box and get the user's choice - bool ShowYesNoMessage(const String& title, const String& message); - - // Get the text currently in the clipboard - String GetClipboard() const; - - // The number of available gamepad devices - int32 GetNumGamepads() const; - // List of gamepad device names - Vector GetGamepadDeviceNames() const; - // Open a gamepad within the range of the number of gamepads - Ref OpenGamepad(int32 deviceIndex); - - - - Delegate OnKeyPressed; - Delegate OnKeyReleased; - Delegate OnMousePressed; - Delegate OnMouseReleased; - Delegate OnMouseMotion; - Delegate OnAnyEvent; - // Mouse scroll wheel - // Positive for scroll down - // Negative for scroll up - Delegate OnMouseScroll; - // Called for the initial an repeating presses of a key - Delegate OnKeyRepeat; - Delegate OnTextInput; - Delegate OnTextComposition; - Delegate OnResized; - Delegate OnMoved; - Delegate OnFocusChanged; - Delegate OnFileDropped; - - private: - class Window_Impl* m_impl; - }; + /// Windowed or bordered window style + enum class WindowStyle + { + Windowed, Borderless + }; + + /// Text input data + struct TextComposition + { + String composition; + int32 cursor; + int32 selectionLength; + }; + + /* + Simple window class that manages window messages, window style and input + Renamed from Window to DesktopWindow to avoid conflicts with libX11 on Linux + */ + class Window : Unique + { + public: + /// Window position and size parameter + struct PosAndShape + { + enum class Mode { Windowed, Fullscreen, WindowedFullscreen }; + Mode mode; + + PosAndShape(bool fullscreen, bool windowedFullscreen, const Vector2i& pos, const Vector2i& size, int32 monitorId, const Vector2i& fullscreenSize) + : PosAndShape(FlagsToMode(fullscreen, windowedFullscreen), pos, size, monitorId, fullscreenSize) {} + + PosAndShape(Mode mode, const Vector2i& pos, const Vector2i& size, int32 monitorId, const Vector2i& fullscreenSize) + : mode(mode), windowPos(pos), windowSize(size), monitorId(monitorId), fullscreenSize(fullscreenSize) {} + + inline static Mode FlagsToMode(bool fullscreen, bool windowedFullscreen) + { + return fullscreen ? windowedFullscreen ? Mode::WindowedFullscreen : Mode::Fullscreen : Mode::Windowed; + } + + inline void SetMode(bool fullscreen, bool windowedFullscreen) + { + mode = FlagsToMode(fullscreen, windowedFullscreen); + } + + /// Position of the window; ignored when in fullscreen mode + Vector2i windowPos; + /// Size of the window; ignored when in fullscreen mode + Vector2i windowSize; + + /// Monitor to use when in fullscreen; ignored when in windowed mode + int32 monitorId; + /// Only used for windowed fullscreen mode + Vector2i fullscreenSize; + }; + + Window(Vector2i size = Vector2i(800, 600), uint8 samplecount = 0); + ~Window(); + // Show the window + void Show(); + // Hide the window + void Hide(); + // Call every frame to update the window message loop + // returns false if the window received a close message + bool Update(); + // On windows: returns the HWND + void* Handle(); + // Set the window title (caption) + void SetCaption(const WString& cap); + // Closes the window + void Close(); + + Vector2i GetMousePos(); + void SetMousePos(const Vector2i& pos); + void SetRelativeMouseMode(bool enabled); + bool GetRelativeMouseMode(); + + // Sets cursor to use + void SetCursor(const Ref& image, Vector2i hotspot = Vector2i(0, 0)); + void SetCursorVisible(bool visible); + + // Switches between borderless and windowed + void SetWindowStyle(WindowStyle style); + + // Get full window position + Vector2i GetWindowPos() const; + + // Window Client area size + Vector2i GetWindowSize() const; + + // Set vsync setting + void SetVSync(int8 setting); + + // Window is active + bool IsActive() const; + + // Set window client area size + void SetPosAndShape(const PosAndShape& posAndShape, bool ensureInBound); + bool IsFullscreen() const; + + int GetDisplayIndex() const; + + // Checks if a key is pressed + bool IsKeyPressed(SDL_Scancode key) const; + + ModifierKeys GetModifierKeys() const; + + // Start allowing text input + void StartTextInput(); + // Stop allowing text input + void StopTextInput(); + // Used to get current IME working data + const TextComposition& GetTextComposition() const; + + // Show a simple message box + // level 0 = error, 1 = warning, 2 = info + void ShowMessageBox(const String& title, const String& message, int severity); + + // Show a simple confirmation box and get the user's choice + bool ShowYesNoMessage(const String& title, const String& message); + + // Get the text currently in the clipboard + String GetClipboard() const; + + // The number of available gamepad devices + int32 GetNumGamepads() const; + // List of gamepad device names + Vector GetGamepadDeviceNames() const; + // Open a gamepad within the range of the number of gamepads + Ref OpenGamepad(int32 deviceIndex); + + + + Delegate OnKeyPressed; + Delegate OnKeyReleased; + Delegate OnMousePressed; + Delegate OnMouseReleased; + Delegate OnMouseMotion; + Delegate OnAnyEvent; + // Mouse scroll wheel + // Positive for scroll down + // Negative for scroll up + Delegate OnMouseScroll; + // Called for the initial an repeating presses of a key + Delegate OnKeyRepeat; + Delegate OnTextInput; + Delegate OnTextComposition; + Delegate OnResized; + Delegate OnMoved; + Delegate OnFocusChanged; + Delegate OnFileDropped; + + private: + class Window_Impl* m_impl; + }; } diff --git a/Graphics/src/Font.cpp b/Graphics/src/Font.cpp index 52a31450c..68955fd84 100644 --- a/Graphics/src/Font.cpp +++ b/Graphics/src/Font.cpp @@ -13,379 +13,378 @@ namespace Graphics { - using Shared::Margin; - using Shared::Recti; - - struct CachedText - { - Text text; - float lastUsage; - }; - // Prevents continuous recreation of text that doesn't change - class TextCache : public Map - { - Timer timer; - public: - void Update() - { - float currentTime = timer.SecondsAsFloat(); - for(auto it = begin(); it != end();) - { - float durationSinceUsed = currentTime - it->second.lastUsage; - if(durationSinceUsed > 1.0f) - { - it = erase(it); - continue; - } - it++; - } - } - Text GetText(const WString& key) - { - auto it = find(key); - if(it != end()) - { - it->second.lastUsage = timer.SecondsAsFloat(); - return it->second.text; - } - return Text(); - } - void AddText(const WString& key, Text obj) - { - Update(); - Add(key, { obj, timer.SecondsAsFloat() }); - } - }; - - FT_Library library; - class Font_Impl; - - using Utility::Reinterpret; - using VectorMath::VectorBase; - - FT_Face fallbackFont; - uint32 fallbackFontSize = 0; - Buffer loadedFallbackFont; - - struct CharInfo - { - uint32 glyphID; - float advance; - int32 leftOffset; - int32 topOffset; - Recti coords; - }; - struct FontSize - { - SpriteMap spriteMap; - Texture textureMap; - FT_Face face; - Vector infos; - Map infoByChar; - bool bUpdated = false; - float lineHeight; - TextCache cache; - - FontSize(OpenGL* gl, FT_Face& face) - : face(face), m_gl(gl) - { - spriteMap = SpriteMapRes::Create(); - textureMap = TextureRes::Create(m_gl); - lineHeight = (float)face->size->metrics.height / 64.0f; - } - ~FontSize() - { - } - - const CharInfo& GetCharInfo(wchar_t t) - { - auto it = infoByChar.find(t); - if(it == infoByChar.end()) - return AddCharInfo(t); - return infos[it->second]; - } - Texture GetTextureMap() - { - if(bUpdated) - { - textureMap = spriteMap->GenerateTexture(m_gl); - bUpdated = false; - } - return textureMap; - } - private: - const CharInfo& AddCharInfo(wchar_t t) - { - bUpdated = true; - infoByChar.Add(t, (uint32)infos.size()); - infos.emplace_back(); - CharInfo& ci = infos.back(); - - FT_Face* pFace = &face; - - ci.glyphID = FT_Get_Char_Index(*pFace, t); - if(ci.glyphID == 0) - { - pFace = &fallbackFont; - ci.glyphID = FT_Get_Char_Index(*pFace, t); - } - FT_Load_Glyph(*pFace, ci.glyphID, FT_LOAD_DEFAULT); - - if((*pFace)->glyph->format != FT_GLYPH_FORMAT_BITMAP) - { - FT_Render_Glyph((*pFace)->glyph, FT_RENDER_MODE_NORMAL); - } - - ci.topOffset = (*pFace)->glyph->bitmap_top; - ci.leftOffset = (*pFace)->glyph->bitmap_left; - ci.advance = (float)(*pFace)->glyph->advance.x / 64.0f; - - Image img = ImageRes::Create(Vector2i((*pFace)->glyph->bitmap.width, (*pFace)->glyph->bitmap.rows)); - Colori* pDst = img->GetBits(); - uint8* pSrc = (*pFace)->glyph->bitmap.buffer; - uint32 nLen = (*pFace)->glyph->bitmap.width * (*pFace)->glyph->bitmap.rows; - for(uint32 i = 0; i < nLen; i++) - { - pDst[0].w = pSrc[0]; - Reinterpret>(pDst[0]) = VectorBase(255, 255, 255); - pSrc++; - pDst++; - } - uint32 nIndex = spriteMap->AddSegment(img); - ci.coords = spriteMap->GetCoords(nIndex); - - return ci; - } - - OpenGL* m_gl; - }; - - - TextRes::~TextRes() - { - } - - Ref TextRes::GetTexture() - { - return fontSize->GetTextureMap(); - } - void TextRes::Draw() - { - GetTexture()->Bind(); - mesh->Draw(); - } - - class Font_Impl : public FontRes - { - OpenGL* m_gl; - FT_Face m_face; - Buffer m_data; - - Map m_sizes; - uint32 m_currentSize = 0; - - - friend class TextRes; - public: - Font_Impl(class OpenGL* gl) : m_gl(gl) , m_face(nullptr) - { - - } - ~Font_Impl() - { - for(auto s : m_sizes) - { - delete s.second; - } - m_sizes.clear(); - if (m_face) - { - FT_Done_Face(m_face); - m_face = nullptr; - } - } - bool Init(const String& assetPath) - { - File in; - if(!in.OpenRead(assetPath)) - return false; - - m_data.resize(in.GetSize()); - if(m_data.size() == 0) - return false; - - in.Read(&m_data.front(), m_data.size()); - - if(FT_New_Memory_Face(library, m_data.data(), (FT_Long)m_data.size(), 0, &m_face) != 0) - return false; - - if(FT_Select_Charmap(m_face, FT_ENCODING_UNICODE) != 0) - assert(false); - - return true; - } - - FontSize* GetSize(uint32 nSize) - { - if(m_currentSize != nSize) - { - FT_Set_Pixel_Sizes(m_face, 0, nSize); - m_currentSize = nSize; - } - if(fallbackFontSize != nSize) - { - FT_Set_Pixel_Sizes(fallbackFont, 0, nSize); - fallbackFontSize = nSize; - } - - auto it = m_sizes.find(nSize); - if(it != m_sizes.end()) - return it->second; - - FontSize* pMap = new FontSize(m_gl, m_face); - m_sizes.Add(nSize, pMap); - return pMap; - } - Ref CreateText(const WString& str, uint32 nFontSize, TextOptions options) - { - FontSize* size = GetSize(nFontSize); - - Text cachedText = size->cache.GetText(str); - if(cachedText) - return cachedText; - - struct TextVertex : public VertexFormat - { - TextVertex(Vector2 point, Vector2 uv) : pos(point), tex(uv) {} - Vector2 pos; - Vector2 tex; - }; - - TextRes* ret = new TextRes(); - ret->mesh = MeshRes::Create(m_gl); - - float monospaceWidth = size->GetCharInfo(L'_').advance * 1.4f; - - Vector vertices; - Vector2 pen; - for(wchar_t c : str) - { - const CharInfo& info = size->GetCharInfo(c); - - if(c != L'\n' && c != L'\t' && info.coords.size.x != 0 && info.coords.size.y != 0) - { - Vector2 corners[4]; - corners[0] = Vector2(0, 0); - corners[1] = Vector2((float)info.coords.size.x, 0); - corners[2] = Vector2((float)info.coords.size.x, (float)info.coords.size.y); - corners[3] = Vector2(0, (float)info.coords.size.y); - - Vector2 offset = Vector2(pen.x, pen.y); - offset.x += info.leftOffset; - offset.y += nFontSize - info.topOffset; - if((options & TextOptions::Monospace) != 0) - { - offset.x += (monospaceWidth - info.coords.size.x) * 0.5f; - } - pen.x = floorf(pen.x); - pen.y = floorf(pen.y); - - vertices.emplace_back(offset + corners[2], - corners[2] + info.coords.pos); - vertices.emplace_back(offset + corners[0], - corners[0] + info.coords.pos); - vertices.emplace_back(offset + corners[1], - corners[1] + info.coords.pos); - - vertices.emplace_back(offset + corners[3], - corners[3] + info.coords.pos); - vertices.emplace_back(offset + corners[0], - corners[0] + info.coords.pos); - vertices.emplace_back(offset + corners[2], - corners[2] + info.coords.pos); - } - - if(c == L'\n') - { - pen.x = 0.0f; - pen.y += size->lineHeight; - ret->size.y = pen.y; - } - else if(c == L'\t') - { - const CharInfo& space = size->GetCharInfo(L' '); - pen.x += space.advance * 3.0f; - } - else - { - if((options & TextOptions::Monospace) != 0) - { - pen.x += monospaceWidth; - } - else - pen.x += info.advance; - } - ret->size.x = std::max(ret->size.x, pen.x); - } - - ret->size.y += size->lineHeight; - - const CharInfo& a = size->GetCharInfo(L'0'); - ret->size.z = a.topOffset; - ret->fontSize = size; - ret->mesh->SetData(vertices); - ret->mesh->SetPrimitiveType(PrimitiveType::TriangleList); - - Text textObj = Utility::MakeRef(ret); - // Insert into cache - size->cache.AddText(str, textObj); - return textObj; - } - }; - - Font FontRes::Create(OpenGL* gl, const String& assetPath) - { - Font_Impl* pImpl = new Font_Impl(gl); - if(pImpl->Init(assetPath)) - { - return GetResourceManager().Register(pImpl); - } - else - { - delete pImpl; - return Font(); - } - } - - bool FontRes::InitLibrary() - { - ProfilerScope $("Font library initialization"); - if(FT_Init_FreeType(&library) != FT_Err_Ok) - return false; - - if(!LoadFallbackFont()) - Log("Failed to load embedded fallback font", Logger::Severity::Error); - - return true; - } - - void FontRes::FreeLibrary() - { - FT_Done_Face(fallbackFont); - FT_Done_FreeType(library); - } - - bool FontRes::LoadFallbackFont() - { - File file; - if(!file.OpenRead(Path::Absolute("fonts/NotoSansCJKjp-Regular.otf"))) - return false; - - loadedFallbackFont.resize(file.GetSize()); - file.Read(loadedFallbackFont.data(), loadedFallbackFont.size()); - file.Close(); - - return FT_New_Memory_Face(library, loadedFallbackFont.data(), (uint32)loadedFallbackFont.size(), 0, &fallbackFont) == 0 - && FT_Select_Charmap(fallbackFont, FT_ENCODING_UNICODE) == 0; - } -} \ No newline at end of file + using Shared::Margin; + using Shared::Recti; + + struct CachedText + { + Text text; + float lastUsage; + }; + // Prevents continuous recreation of text that doesn't change + class TextCache : public Map + { + Timer timer; + + public: + void Update() + { + float currentTime = timer.SecondsAsFloat(); + for (auto it = begin(); it != end();) + { + float durationSinceUsed = currentTime - it->second.lastUsage; + if (durationSinceUsed > 1.0f) + { + it = erase(it); + continue; + } + it++; + } + } + Text GetText(const WString& key) + { + auto it = find(key); + if (it != end()) + { + it->second.lastUsage = timer.SecondsAsFloat(); + return it->second.text; + } + return Text(); + } + void AddText(const WString& key, Text obj) + { + Update(); + Add(key, { obj, timer.SecondsAsFloat() }); + } + }; + + FT_Library library; + class Font_Impl; + + using Utility::Reinterpret; + using VectorMath::VectorBase; + + FT_Face fallbackFont; + uint32 fallbackFontSize = 0; + Buffer loadedFallbackFont; + + struct CharInfo + { + uint32 glyphID; + float advance; + int32 leftOffset; + int32 topOffset; + Recti coords; + }; + struct FontSize + { + SpriteMap spriteMap; + Texture textureMap; + FT_Face face; + Vector infos; + Map infoByChar; + bool bUpdated = false; + float lineHeight; + TextCache cache; + + FontSize(OpenGL* gl, FT_Face& face) + : face(face), m_gl(gl) + { + spriteMap = SpriteMapRes::Create(); + textureMap = TextureRes::Create(m_gl); + lineHeight = (float)face->size->metrics.height / 64.0f; + } + ~FontSize() + { + } + + const CharInfo& GetCharInfo(wchar_t t) + { + auto it = infoByChar.find(t); + if (it == infoByChar.end()) + return AddCharInfo(t); + return infos[it->second]; + } + Texture GetTextureMap() + { + if (bUpdated) + { + textureMap = spriteMap->GenerateTexture(m_gl); + bUpdated = false; + } + return textureMap; + } + + private: + const CharInfo& AddCharInfo(wchar_t t) + { + bUpdated = true; + infoByChar.Add(t, (uint32)infos.size()); + infos.emplace_back(); + CharInfo& ci = infos.back(); + + FT_Face* pFace = &face; + + ci.glyphID = FT_Get_Char_Index(*pFace, t); + if (ci.glyphID == 0) + { + pFace = &fallbackFont; + ci.glyphID = FT_Get_Char_Index(*pFace, t); + } + FT_Load_Glyph(*pFace, ci.glyphID, FT_LOAD_DEFAULT); + + if ((*pFace)->glyph->format != FT_GLYPH_FORMAT_BITMAP) + { + FT_Render_Glyph((*pFace)->glyph, FT_RENDER_MODE_NORMAL); + } + + ci.topOffset = (*pFace)->glyph->bitmap_top; + ci.leftOffset = (*pFace)->glyph->bitmap_left; + ci.advance = (float)(*pFace)->glyph->advance.x / 64.0f; + + Image img = ImageRes::Create(Vector2i((*pFace)->glyph->bitmap.width, (*pFace)->glyph->bitmap.rows)); + Colori* pDst = img->GetBits(); + uint8* pSrc = (*pFace)->glyph->bitmap.buffer; + uint32 nLen = (*pFace)->glyph->bitmap.width * (*pFace)->glyph->bitmap.rows; + for (uint32 i = 0; i < nLen; i++) + { + pDst[0].w = pSrc[0]; + Reinterpret>(pDst[0]) = VectorBase(255, 255, 255); + pSrc++; + pDst++; + } + uint32 nIndex = spriteMap->AddSegment(img); + ci.coords = spriteMap->GetCoords(nIndex); + + return ci; + } + + OpenGL* m_gl; + }; + + TextRes::~TextRes() + { + } + + Ref TextRes::GetTexture() + { + return fontSize->GetTextureMap(); + } + void TextRes::Draw() + { + GetTexture()->Bind(); + mesh->Draw(); + } + + class Font_Impl : public FontRes + { + OpenGL* m_gl; + FT_Face m_face; + Buffer m_data; + + Map m_sizes; + uint32 m_currentSize = 0; + + friend class TextRes; + + public: + Font_Impl(class OpenGL* gl) : m_gl(gl), m_face(nullptr) + { + } + ~Font_Impl() + { + for (auto s : m_sizes) + { + delete s.second; + } + m_sizes.clear(); + if (m_face) + { + FT_Done_Face(m_face); + m_face = nullptr; + } + } + bool Init(const String& assetPath) + { + File in; + if (!in.OpenRead(assetPath)) + return false; + + m_data.resize(in.GetSize()); + if (m_data.size() == 0) + return false; + + in.Read(&m_data.front(), m_data.size()); + + if (FT_New_Memory_Face(library, m_data.data(), (FT_Long)m_data.size(), 0, &m_face) != 0) + return false; + + if (FT_Select_Charmap(m_face, FT_ENCODING_UNICODE) != 0) + assert(false); + + return true; + } + + FontSize* GetSize(uint32 nSize) + { + if (m_currentSize != nSize) + { + FT_Set_Pixel_Sizes(m_face, 0, nSize); + m_currentSize = nSize; + } + if (fallbackFontSize != nSize) + { + FT_Set_Pixel_Sizes(fallbackFont, 0, nSize); + fallbackFontSize = nSize; + } + + auto it = m_sizes.find(nSize); + if (it != m_sizes.end()) + return it->second; + + FontSize* pMap = new FontSize(m_gl, m_face); + m_sizes.Add(nSize, pMap); + return pMap; + } + Ref CreateText(const WString& str, uint32 nFontSize, TextOptions options) + { + FontSize* size = GetSize(nFontSize); + + Text cachedText = size->cache.GetText(str); + if (cachedText) + return cachedText; + + struct TextVertex : public VertexFormat + { + TextVertex(Vector2 point, Vector2 uv) : pos(point), tex(uv) {} + Vector2 pos; + Vector2 tex; + }; + + TextRes* ret = new TextRes(); + ret->mesh = MeshRes::Create(m_gl); + + float monospaceWidth = size->GetCharInfo(L'_').advance * 1.4f; + + Vector vertices; + Vector2 pen; + for (wchar_t c : str) + { + const CharInfo& info = size->GetCharInfo(c); + + if (c != L'\n' && c != L'\t' && info.coords.size.x != 0 && info.coords.size.y != 0) + { + Vector2 corners[4]; + corners[0] = Vector2(0, 0); + corners[1] = Vector2((float)info.coords.size.x, 0); + corners[2] = Vector2((float)info.coords.size.x, (float)info.coords.size.y); + corners[3] = Vector2(0, (float)info.coords.size.y); + + Vector2 offset = Vector2(pen.x, pen.y); + offset.x += info.leftOffset; + offset.y += nFontSize - info.topOffset; + if ((options & TextOptions::Monospace) != 0) + { + offset.x += (monospaceWidth - info.coords.size.x) * 0.5f; + } + pen.x = floorf(pen.x); + pen.y = floorf(pen.y); + + vertices.emplace_back(offset + corners[2], + corners[2] + info.coords.pos); + vertices.emplace_back(offset + corners[0], + corners[0] + info.coords.pos); + vertices.emplace_back(offset + corners[1], + corners[1] + info.coords.pos); + + vertices.emplace_back(offset + corners[3], + corners[3] + info.coords.pos); + vertices.emplace_back(offset + corners[0], + corners[0] + info.coords.pos); + vertices.emplace_back(offset + corners[2], + corners[2] + info.coords.pos); + } + + if (c == L'\n') + { + pen.x = 0.0f; + pen.y += size->lineHeight; + ret->size.y = pen.y; + } + else if (c == L'\t') + { + const CharInfo& space = size->GetCharInfo(L' '); + pen.x += space.advance * 3.0f; + } + else + { + if ((options & TextOptions::Monospace) != 0) + { + pen.x += monospaceWidth; + } + else + pen.x += info.advance; + } + ret->size.x = std::max(ret->size.x, pen.x); + } + + ret->size.y += size->lineHeight; + + const CharInfo& a = size->GetCharInfo(L'0'); + ret->size.z = a.topOffset; + ret->fontSize = size; + ret->mesh->SetData(vertices); + ret->mesh->SetPrimitiveType(PrimitiveType::TriangleList); + + Text textObj = Utility::MakeRef(ret); + // Insert into cache + size->cache.AddText(str, textObj); + return textObj; + } + }; + + Font FontRes::Create(OpenGL* gl, const String& assetPath) + { + Font_Impl* pImpl = new Font_Impl(gl); + if (pImpl->Init(assetPath)) + { + return GetResourceManager().Register(pImpl); + } + else + { + delete pImpl; + return Font(); + } + } + + bool FontRes::InitLibrary() + { + ProfilerScope $("Font library initialization"); + if (FT_Init_FreeType(&library) != FT_Err_Ok) + return false; + + if (!LoadFallbackFont()) + Log("Failed to load embedded fallback font", Logger::Severity::Error); + + return true; + } + + void FontRes::FreeLibrary() + { + FT_Done_Face(fallbackFont); + FT_Done_FreeType(library); + } + + bool FontRes::LoadFallbackFont() + { + File file; + if (!file.OpenRead(Path::Absolute("fonts/NotoSansCJKjp-Regular.otf"))) + return false; + + loadedFallbackFont.resize(file.GetSize()); + file.Read(loadedFallbackFont.data(), loadedFallbackFont.size()); + file.Close(); + + return FT_New_Memory_Face(library, loadedFallbackFont.data(), (uint32)loadedFallbackFont.size(), 0, &fallbackFont) == 0 && FT_Select_Charmap(fallbackFont, FT_ENCODING_UNICODE) == 0; + } +} diff --git a/Graphics/src/Gamepad_Impl.cpp b/Graphics/src/Gamepad_Impl.cpp index 1e4f3a475..fac6b284e 100644 --- a/Graphics/src/Gamepad_Impl.cpp +++ b/Graphics/src/Gamepad_Impl.cpp @@ -5,70 +5,70 @@ namespace Graphics { - Gamepad_Impl::~Gamepad_Impl() - { - SDL_JoystickClose(m_joystick); - } - bool Gamepad_Impl::Init(Window* window, uint32 deviceIndex) - { - Logf("Trying to open joystick %d", Logger::Severity::Info, deviceIndex); + Gamepad_Impl::~Gamepad_Impl() + { + SDL_JoystickClose(m_joystick); + } + bool Gamepad_Impl::Init(Window* window, uint32 deviceIndex) + { + Logf("Trying to open joystick %d", Logger::Severity::Info, deviceIndex); - m_window = window; - m_deviceIndex = deviceIndex; - m_joystick = SDL_JoystickOpen(deviceIndex); - if(!m_joystick) - { - Logf("Failed to open joystick %d", Logger::Severity::Error, deviceIndex); - return false; - } + m_window = window; + m_deviceIndex = deviceIndex; + m_joystick = SDL_JoystickOpen(deviceIndex); + if (!m_joystick) + { + Logf("Failed to open joystick %d", Logger::Severity::Error, deviceIndex); + return false; + } - for(int32 i = 0; i < SDL_JoystickNumButtons(m_joystick); i++) - m_buttonStates.Add(0); - for(int32 i = 0; i < SDL_JoystickNumAxes(m_joystick); i++) - m_axisState.Add(0.0f); + for (int32 i = 0; i < SDL_JoystickNumButtons(m_joystick); i++) + m_buttonStates.Add(0); + for (int32 i = 0; i < SDL_JoystickNumAxes(m_joystick); i++) + m_axisState.Add(0.0f); - String deviceName = SDL_JoystickName(m_joystick); - Logf("Joystick device \"%s\" opened with %d buttons and %d axes", Logger::Severity::Info, - deviceName, m_buttonStates.size(), m_axisState.size()); + String deviceName = SDL_JoystickName(m_joystick); + Logf("Joystick device \"%s\" opened with %d buttons and %d axes", Logger::Severity::Info, + deviceName, m_buttonStates.size(), m_axisState.size()); - return true; - } + return true; + } - void Gamepad_Impl::HandleInputEvent(uint32 buttonIndex, uint8 newState, int32 delta) - { - m_buttonStates[buttonIndex] = newState; - if(newState != 0) - OnButtonPressed.Call(buttonIndex, delta); - else - OnButtonReleased.Call(buttonIndex, delta); - } - void Gamepad_Impl::HandleAxisEvent(uint32 axisIndex, int16 newValue) - { - m_axisState[axisIndex] = (float)newValue / (float)0x7fff; - } - void Gamepad_Impl::HandleHatEvent(uint32 hadIndex, uint8 newValue) - { - // TODO Maybe use this if required - } + void Gamepad_Impl::HandleInputEvent(uint32 buttonIndex, uint8 newState, int32 delta) + { + m_buttonStates[buttonIndex] = newState; + if (newState != 0) + OnButtonPressed.Call(buttonIndex, delta); + else + OnButtonReleased.Call(buttonIndex, delta); + } + void Gamepad_Impl::HandleAxisEvent(uint32 axisIndex, int16 newValue) + { + m_axisState[axisIndex] = (float)newValue / (float)0x7fff; + } + void Gamepad_Impl::HandleHatEvent(uint32 hadIndex, uint8 newValue) + { + // TODO Maybe use this if required + } - bool Gamepad_Impl::GetButton(uint8 button) const - { - if(button >= m_buttonStates.size()) - return false; - return m_buttonStates[button] != 0; - } - float Gamepad_Impl::GetAxis(uint8 idx) const - { - if(idx >= m_axisState.size()) - return 0.0f; - return m_axisState[idx]; - } - uint32 Gamepad_Impl::NumButtons() const - { - return (uint32)m_buttonStates.size(); - } - uint32 Gamepad_Impl::NumAxes() const - { - return (uint32)m_axisState.size(); - } -} \ No newline at end of file + bool Gamepad_Impl::GetButton(uint8 button) const + { + if (button >= m_buttonStates.size()) + return false; + return m_buttonStates[button] != 0; + } + float Gamepad_Impl::GetAxis(uint8 idx) const + { + if (idx >= m_axisState.size()) + return 0.0f; + return m_axisState[idx]; + } + uint32 Gamepad_Impl::NumButtons() const + { + return (uint32)m_buttonStates.size(); + } + uint32 Gamepad_Impl::NumAxes() const + { + return (uint32)m_axisState.size(); + } +} diff --git a/Graphics/src/Gamepad_Impl.hpp b/Graphics/src/Gamepad_Impl.hpp index 5d3dbe9f1..1a356c20d 100644 --- a/Graphics/src/Gamepad_Impl.hpp +++ b/Graphics/src/Gamepad_Impl.hpp @@ -5,28 +5,28 @@ namespace Graphics { - class Window; - class Gamepad_Impl : public Gamepad - { - public: - ~Gamepad_Impl(); - bool Init(Graphics::Window* window, uint32 deviceIndex); + class Window; + class Gamepad_Impl : public Gamepad + { + public: + ~Gamepad_Impl(); + bool Init(Graphics::Window* window, uint32 deviceIndex); - // Handles input events straight from the event loop - void HandleInputEvent(uint32 buttonIndex, uint8 newState, int32 delta); - void HandleAxisEvent(uint32 axisIndex, int16 newValue); - void HandleHatEvent(uint32 hadIndex, uint8 newValue); + // Handles input events straight from the event loop + void HandleInputEvent(uint32 buttonIndex, uint8 newState, int32 delta); + void HandleAxisEvent(uint32 axisIndex, int16 newValue); + void HandleHatEvent(uint32 hadIndex, uint8 newValue); - class Window* m_window; - uint32 m_deviceIndex; - SDL_Joystick* m_joystick; + class Window* m_window; + uint32 m_deviceIndex; + SDL_Joystick* m_joystick; - Vector m_axisState; - Vector m_buttonStates; + Vector m_axisState; + Vector m_buttonStates; - virtual bool GetButton(uint8 button) const override; - virtual float GetAxis(uint8 idx) const override; - virtual uint32 NumButtons() const override; - virtual uint32 NumAxes() const override; - }; -} \ No newline at end of file + virtual bool GetButton(uint8 button) const override; + virtual float GetAxis(uint8 idx) const override; + virtual uint32 NumButtons() const override; + virtual uint32 NumAxes() const override; + }; +} diff --git a/Graphics/src/Image.cpp b/Graphics/src/Image.cpp index b52f8c205..22d826142 100644 --- a/Graphics/src/Image.cpp +++ b/Graphics/src/Image.cpp @@ -12,218 +12,222 @@ namespace Graphics { - class Image_Impl : public ImageRes - { - Vector2i m_size; - Colori* m_pData = nullptr; - size_t m_nDataLength; - public: - Image_Impl() - { - - } - ~Image_Impl() - { - Clear(); - } - void Clear() - { - if(m_pData) - delete[] m_pData; - m_pData = nullptr; - } - void Allocate() - { - m_nDataLength = m_size.x * m_size.y; - if(m_nDataLength == 0) - return; - m_pData = new Colori[m_nDataLength]; - } - - void SetSize(Vector2i size) - { - m_size = size; - Clear(); - Allocate(); - } - void ReSize(Vector2i size) - { - size_t new_DataLength = size.x * size.y; - if (new_DataLength == 0){ - return; - } - Colori* new_pData = new Colori[new_DataLength]; - - for (int32 ix = 0; ix < size.x; ++ix){ - for (int32 iy = 0; iy < size.y; ++iy){ - int32 sampledX = ix * ((double)m_size.x / (double)size.x); - int32 sampledY = iy * ((double)m_size.y / (double)size.y); - new_pData[size.x * iy + ix] = m_pData[m_size.x * sampledY + sampledX]; - } - } - - delete[] m_pData; - m_pData = new_pData; - m_size = size; - m_nDataLength = m_size.x * m_size.y; - } - bool Screenshot(OpenGL* gl,Vector2i pos) - { - GLuint texture; - GLenum err; - while ((err = glGetError()) != GL_NO_ERROR) //Clear out preexisting errors - { - Logf("OpenGL Error: 0x%p", Logger::Severity::Debug, err); - } - - //Create texture - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_size.x, m_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - glBindTexture(GL_TEXTURE_2D, 0); - glFinish(); - - while ((err = glGetError()) != GL_NO_ERROR) - { - Logf("OpenGL Error: 0x%p", Logger::Severity::Error, err); - return false; - } - - //Set texture from buffer - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glReadBuffer(GL_BACK); - glBindTexture(GL_TEXTURE_2D, texture); - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pos.x, pos.y, m_size.x, m_size.y); - - while ((err = glGetError()) != GL_NO_ERROR) - { - Logf("OpenGL Error: 0x%p", Logger::Severity::Error, err); - return false; - } - glFinish(); - - - //Copy texture contents to image pData - #ifdef EMBEDDED - glReadPixels(0, 0, m_size.x, m_size.y, GL_RGBA, GL_UNSIGNED_BYTE, m_pData); - #else - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_pData); - #endif - glBindTexture(GL_TEXTURE_2D, 0); - glFinish(); - - while ((err = glGetError()) != GL_NO_ERROR) - { - Logf("OpenGL Error: 0x%p", Logger::Severity::Error, err); - return false; - } - - //Delete texture - glDeleteTextures(1, &texture); - return true; - } - void SavePNG(const String& file) - { - ///TODO: Use shared/File.hpp instead? - File pngfile; - pngfile.OpenWrite(Path::Normalize(file)); - - png_structp png_ptr = NULL; - png_infop info_ptr = NULL; - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - info_ptr = png_create_info_struct(png_ptr); - if (setjmp(png_jmpbuf(png_ptr))) - { - /* If we get here, we had a problem writing the file */ - pngfile.Close(); - png_destroy_write_struct(&png_ptr, &info_ptr); - assert(false); - } - png_set_IHDR(png_ptr, info_ptr, m_size.x, m_size.y, - 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * m_size.y); - for (int i = 0; i < m_size.y; ++i) { - row_pointers[m_size.y - i - 1] = (png_bytep)(m_pData + i * m_size.x); - } - - png_set_write_fn(png_ptr, &pngfile, pngfile_write_data, pngfile_flush); - png_set_rows(png_ptr, info_ptr, row_pointers); - png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); - pngfile.Close(); - free(row_pointers); - } - Vector2i GetSize() const - { - return m_size; - } - Colori* GetBits() - { - return m_pData; - } - const Colori* GetBits() const - { - return m_pData; - } - private: - static void pngfile_write_data(png_structp png_ptr, png_bytep data, png_size_t length) - { - File* pngFile = (File*)png_get_io_ptr(png_ptr); - pngFile->Write(data, length); - } - static void pngfile_flush(png_structp png_ptr) - { - } - }; - - Image ImageRes::Create(Vector2i size) - { - Image_Impl* pImpl = new Image_Impl(); - pImpl->SetSize(size); - return GetResourceManager().Register(pImpl); - } - Ref ImageRes::Create(Buffer & b) - { - Image_Impl* pImpl = new Image_Impl(); - if (ImageLoader::Load(pImpl, b)) - { - return GetResourceManager().Register(pImpl); - } - else - { - delete pImpl; - pImpl = nullptr; - } - return Image(); - } - Image ImageRes::Create(const String& assetPath) - { - Image_Impl* pImpl = new Image_Impl(); - if(ImageLoader::Load(pImpl, assetPath)) - { - return GetResourceManager().Register(pImpl); - } - else - { - delete pImpl; - pImpl = nullptr; - } - return Image(); - } - Image ImageRes::Screenshot(OpenGL* gl, Vector2i size, Vector2i pos) - { - Image_Impl* pImpl = new Image_Impl(); - pImpl->SetSize(size); - if (!pImpl->Screenshot(gl, pos)) - { - delete pImpl; - pImpl = nullptr; - } - else - { - return GetResourceManager().Register(pImpl); - } - return Image(); - } + class Image_Impl : public ImageRes + { + Vector2i m_size; + Colori* m_pData = nullptr; + size_t m_nDataLength; + + public: + Image_Impl() + { + } + ~Image_Impl() + { + Clear(); + } + void Clear() + { + if (m_pData) + delete[] m_pData; + m_pData = nullptr; + } + void Allocate() + { + m_nDataLength = m_size.x * m_size.y; + if (m_nDataLength == 0) + return; + m_pData = new Colori[m_nDataLength]; + } + + void SetSize(Vector2i size) + { + m_size = size; + Clear(); + Allocate(); + } + void ReSize(Vector2i size) + { + size_t new_DataLength = size.x * size.y; + if (new_DataLength == 0) + { + return; + } + Colori* new_pData = new Colori[new_DataLength]; + + for (int32 ix = 0; ix < size.x; ++ix) + { + for (int32 iy = 0; iy < size.y; ++iy) + { + int32 sampledX = ix * ((double)m_size.x / (double)size.x); + int32 sampledY = iy * ((double)m_size.y / (double)size.y); + new_pData[size.x * iy + ix] = m_pData[m_size.x * sampledY + sampledX]; + } + } + + delete[] m_pData; + m_pData = new_pData; + m_size = size; + m_nDataLength = m_size.x * m_size.y; + } + bool Screenshot(OpenGL* gl, Vector2i pos) + { + GLuint texture; + GLenum err; + while ((err = glGetError()) != GL_NO_ERROR) // Clear out preexisting errors + { + Logf("OpenGL Error: 0x%p", Logger::Severity::Debug, err); + } + + // Create texture + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_size.x, m_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glBindTexture(GL_TEXTURE_2D, 0); + glFinish(); + + while ((err = glGetError()) != GL_NO_ERROR) + { + Logf("OpenGL Error: 0x%p", Logger::Severity::Error, err); + return false; + } + + // Set texture from buffer + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glReadBuffer(GL_BACK); + glBindTexture(GL_TEXTURE_2D, texture); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pos.x, pos.y, m_size.x, m_size.y); + + while ((err = glGetError()) != GL_NO_ERROR) + { + Logf("OpenGL Error: 0x%p", Logger::Severity::Error, err); + return false; + } + glFinish(); + + // Copy texture contents to image pData +#ifdef EMBEDDED + glReadPixels(0, 0, m_size.x, m_size.y, GL_RGBA, GL_UNSIGNED_BYTE, m_pData); +#else + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_pData); +#endif + glBindTexture(GL_TEXTURE_2D, 0); + glFinish(); + + while ((err = glGetError()) != GL_NO_ERROR) + { + Logf("OpenGL Error: 0x%p", Logger::Severity::Error, err); + return false; + } + + // Delete texture + glDeleteTextures(1, &texture); + return true; + } + void SavePNG(const String& file) + { + /// TODO: Use shared/File.hpp instead? + File pngfile; + pngfile.OpenWrite(Path::Normalize(file)); + + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + info_ptr = png_create_info_struct(png_ptr); + if (setjmp(png_jmpbuf(png_ptr))) + { + /* If we get here, we had a problem writing the file */ + pngfile.Close(); + png_destroy_write_struct(&png_ptr, &info_ptr); + assert(false); + } + png_set_IHDR(png_ptr, info_ptr, m_size.x, m_size.y, + 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * m_size.y); + for (int i = 0; i < m_size.y; ++i) + { + row_pointers[m_size.y - i - 1] = (png_bytep)(m_pData + i * m_size.x); + } + + png_set_write_fn(png_ptr, &pngfile, pngfile_write_data, pngfile_flush); + png_set_rows(png_ptr, info_ptr, row_pointers); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + pngfile.Close(); + free(row_pointers); + } + Vector2i GetSize() const + { + return m_size; + } + Colori* GetBits() + { + return m_pData; + } + const Colori* GetBits() const + { + return m_pData; + } + + private: + static void pngfile_write_data(png_structp png_ptr, png_bytep data, png_size_t length) + { + File* pngFile = (File*)png_get_io_ptr(png_ptr); + pngFile->Write(data, length); + } + static void pngfile_flush(png_structp png_ptr) + { + } + }; + + Image ImageRes::Create(Vector2i size) + { + Image_Impl* pImpl = new Image_Impl(); + pImpl->SetSize(size); + return GetResourceManager().Register(pImpl); + } + Ref ImageRes::Create(Buffer& b) + { + Image_Impl* pImpl = new Image_Impl(); + if (ImageLoader::Load(pImpl, b)) + { + return GetResourceManager().Register(pImpl); + } + else + { + delete pImpl; + pImpl = nullptr; + } + return Image(); + } + Image ImageRes::Create(const String& assetPath) + { + Image_Impl* pImpl = new Image_Impl(); + if (ImageLoader::Load(pImpl, assetPath)) + { + return GetResourceManager().Register(pImpl); + } + else + { + delete pImpl; + pImpl = nullptr; + } + return Image(); + } + Image ImageRes::Screenshot(OpenGL* gl, Vector2i size, Vector2i pos) + { + Image_Impl* pImpl = new Image_Impl(); + pImpl->SetSize(size); + if (!pImpl->Screenshot(gl, pos)) + { + delete pImpl; + pImpl = nullptr; + } + else + { + return GetResourceManager().Register(pImpl); + } + return Image(); + } } diff --git a/Graphics/src/ImageLoader.cpp b/Graphics/src/ImageLoader.cpp index 96d0ec56e..f9514b6ab 100644 --- a/Graphics/src/ImageLoader.cpp +++ b/Graphics/src/ImageLoader.cpp @@ -14,155 +14,153 @@ namespace Graphics { - class ImageLoader_Impl - { - ImageLoader_Impl() - { - } - ~ImageLoader_Impl() - { - } - public: - - // error handling - struct jpegErrorMgr : public jpeg_error_mgr - { - jmp_buf jmpBuf; - }; - static void jpegErrorExit(jpeg_common_struct* cinfo) - { - longjmp(((jpegErrorMgr*)cinfo->err)->jmpBuf, 1); - } - static void jpegErrorReset(jpeg_common_struct* cinfo) - { - } - static void jpegEmitMessage(jpeg_common_struct* cinfo, int msgLvl) - { - } - static void jpegOutputMessage(jpeg_common_struct* cinfo) - { - } - static void jpegFormatMessage(jpeg_common_struct* cinfo, char * buffer) - { - } - - bool LoadJPEG(ImageRes* pImage, Buffer& in) - { - - /* This struct contains the JPEG decompression parameters and pointers to - * working space (which is allocated as needed by the JPEG library). - */ - jpeg_decompress_struct cinfo; - jpegErrorMgr jerr = {}; - jerr.reset_error_mgr = &jpegErrorReset; - jerr.error_exit = &jpegErrorExit; - jerr.emit_message = &jpegEmitMessage; - jerr.format_message = &jpegFormatMessage; - jerr.output_message = &jpegOutputMessage; - cinfo.err = &jerr; - - // Return point for long jump - if(setjmp(jerr.jmpBuf) == 0) - { - jpeg_create_decompress(&cinfo); - jpeg_mem_src(&cinfo, in.data(), (uint32)in.size()); - int res = jpeg_read_header(&cinfo, TRUE); - - jpeg_start_decompress(&cinfo); - int row_stride = cinfo.output_width * cinfo.output_components; - JSAMPARRAY sample = (*cinfo.mem->alloc_sarray) - ((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1); - - Vector2i size = Vector2i(cinfo.output_width, cinfo.output_height); - pImage->SetSize(size); - Colori* pBits = pImage->GetBits(); - - size_t pixelSize = cinfo.out_color_components; - cinfo.out_color_space = JCS_RGB; - - while(cinfo.output_scanline < cinfo.output_height) - { - jpeg_read_scanlines(&cinfo, sample, 1); - for(size_t i = 0; i < cinfo.output_width; i++) - { - memcpy(pBits + i, sample[0] + i * pixelSize, pixelSize); - pBits[i].w = 0xFF; - } - - pBits += size.x; - } - - jpeg_finish_decompress(&cinfo); - jpeg_destroy_decompress(&cinfo); - return true; - } - - // If we get here, the loading of the jpeg failed - return false; - } - bool LoadPNG(ImageRes* pImage, Buffer& in) - { - png_image image; - memset(&image, 0, (sizeof image)); - image.version = PNG_IMAGE_VERSION; - - if(png_image_begin_read_from_memory(&image, in.data(), in.size()) == 0) - return false; - - image.format = PNG_FORMAT_RGBA; - - pImage->SetSize(Vector2i(image.width, image.height)); - Colori* pBuffer = pImage->GetBits(); - if(!pBuffer) - return false; - - if((image.width * image.height * 4) != PNG_IMAGE_SIZE(image)) - return false; - - if(png_image_finish_read(&image, nullptr, pBuffer, 0, nullptr) == 0) - return false; - - png_image_free(&image); - return true; - } - bool Load(ImageRes* pImage, const String& fullPath) - { - File f; - if(!f.OpenRead(fullPath)) - return false; - - Buffer b(f.GetSize()); - f.Read(b.data(), b.size()); - if(b.size() < 4) - return false; - - return Load(pImage, b); - } - - bool Load(ImageRes* pImage, Buffer& b) - { - // Check for PNG based on first 4 bytes - if (std::memcmp(b.data(), "\x89PNG", 4) == 0) - return LoadPNG(pImage, b); - else // jay-PEG ? - return LoadJPEG(pImage, b); - } - - static ImageLoader_Impl& Main() - { - static ImageLoader_Impl inst; - return inst; - } - }; - - - bool ImageLoader::Load(ImageRes* pImage, const String& fullPath) - { - return ImageLoader_Impl::Main().Load(pImage, fullPath); - } - - bool ImageLoader::Load(ImageRes* pImage, Buffer& b) - { - return ImageLoader_Impl::Main().Load(pImage, b); - } -} \ No newline at end of file + class ImageLoader_Impl + { + ImageLoader_Impl() + { + } + ~ImageLoader_Impl() + { + } + + public: + // error handling + struct jpegErrorMgr : public jpeg_error_mgr + { + jmp_buf jmpBuf; + }; + static void jpegErrorExit(jpeg_common_struct* cinfo) + { + longjmp(((jpegErrorMgr*)cinfo->err)->jmpBuf, 1); + } + static void jpegErrorReset(jpeg_common_struct* cinfo) + { + } + static void jpegEmitMessage(jpeg_common_struct* cinfo, int msgLvl) + { + } + static void jpegOutputMessage(jpeg_common_struct* cinfo) + { + } + static void jpegFormatMessage(jpeg_common_struct* cinfo, char* buffer) + { + } + + bool LoadJPEG(ImageRes* pImage, Buffer& in) + { + + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + jpeg_decompress_struct cinfo; + jpegErrorMgr jerr = {}; + jerr.reset_error_mgr = &jpegErrorReset; + jerr.error_exit = &jpegErrorExit; + jerr.emit_message = &jpegEmitMessage; + jerr.format_message = &jpegFormatMessage; + jerr.output_message = &jpegOutputMessage; + cinfo.err = &jerr; + + // Return point for long jump + if (setjmp(jerr.jmpBuf) == 0) + { + jpeg_create_decompress(&cinfo); + jpeg_mem_src(&cinfo, in.data(), (uint32)in.size()); + int res = jpeg_read_header(&cinfo, TRUE); + + jpeg_start_decompress(&cinfo); + int row_stride = cinfo.output_width * cinfo.output_components; + JSAMPARRAY sample = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1); + + Vector2i size = Vector2i(cinfo.output_width, cinfo.output_height); + pImage->SetSize(size); + Colori* pBits = pImage->GetBits(); + + size_t pixelSize = cinfo.out_color_components; + cinfo.out_color_space = JCS_RGB; + + while (cinfo.output_scanline < cinfo.output_height) + { + jpeg_read_scanlines(&cinfo, sample, 1); + for (size_t i = 0; i < cinfo.output_width; i++) + { + memcpy(pBits + i, sample[0] + i * pixelSize, pixelSize); + pBits[i].w = 0xFF; + } + + pBits += size.x; + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + return true; + } + + // If we get here, the loading of the jpeg failed + return false; + } + bool LoadPNG(ImageRes* pImage, Buffer& in) + { + png_image image; + memset(&image, 0, (sizeof image)); + image.version = PNG_IMAGE_VERSION; + + if (png_image_begin_read_from_memory(&image, in.data(), in.size()) == 0) + return false; + + image.format = PNG_FORMAT_RGBA; + + pImage->SetSize(Vector2i(image.width, image.height)); + Colori* pBuffer = pImage->GetBits(); + if (!pBuffer) + return false; + + if ((image.width * image.height * 4) != PNG_IMAGE_SIZE(image)) + return false; + + if (png_image_finish_read(&image, nullptr, pBuffer, 0, nullptr) == 0) + return false; + + png_image_free(&image); + return true; + } + bool Load(ImageRes* pImage, const String& fullPath) + { + File f; + if (!f.OpenRead(fullPath)) + return false; + + Buffer b(f.GetSize()); + f.Read(b.data(), b.size()); + if (b.size() < 4) + return false; + + return Load(pImage, b); + } + + bool Load(ImageRes* pImage, Buffer& b) + { + // Check for PNG based on first 4 bytes + if (std::memcmp(b.data(), "\x89PNG", 4) == 0) + return LoadPNG(pImage, b); + else // jay-PEG ? + return LoadJPEG(pImage, b); + } + + static ImageLoader_Impl& Main() + { + static ImageLoader_Impl inst; + return inst; + } + }; + + bool ImageLoader::Load(ImageRes* pImage, const String& fullPath) + { + return ImageLoader_Impl::Main().Load(pImage, fullPath); + } + + bool ImageLoader::Load(ImageRes* pImage, Buffer& b) + { + return ImageLoader_Impl::Main().Load(pImage, b); + } +} diff --git a/Graphics/src/Material.cpp b/Graphics/src/Material.cpp index 5495b6758..be254d99b 100644 --- a/Graphics/src/Material.cpp +++ b/Graphics/src/Material.cpp @@ -6,513 +6,534 @@ namespace Graphics { - // Defines build in shader variables - enum BuiltInShaderVariable - { - SV_World = 0, - SV_Proj, - SV_Camera, - SV_BillboardMatrix, - SV_Viewport, - SV_AspectRatio, - SV_Time, - SV__BuiltInEnd, - SV_User = 0x100, // Start defining user variables here - }; - const char* builtInShaderVariableNames[] = - { - "world", - "proj", - "camera", - "billboard", - "viewport", - "aspectRatio", - "time", - }; - class BuiltInShaderVariableMap : public Map - { - public: - BuiltInShaderVariableMap() - { - for(int32 i = 0; i < SV__BuiltInEnd; i++) - { - Add(builtInShaderVariableNames[i], (BuiltInShaderVariable)i); - } - } - }; - BuiltInShaderVariableMap builtInShaderVariableMap; + // Defines build in shader variables + enum BuiltInShaderVariable + { + SV_World = 0, + SV_Proj, + SV_Camera, + SV_BillboardMatrix, + SV_Viewport, + SV_AspectRatio, + SV_Time, + SV__BuiltInEnd, + SV_User = 0x100, // Start defining user variables here + }; + const char* builtInShaderVariableNames[] = + { + "world", + "proj", + "camera", + "billboard", + "viewport", + "aspectRatio", + "time", + }; + class BuiltInShaderVariableMap : public Map + { + public: + BuiltInShaderVariableMap() + { + for (int32 i = 0; i < SV__BuiltInEnd; i++) + { + Add(builtInShaderVariableNames[i], (BuiltInShaderVariable)i); + } + } + }; + BuiltInShaderVariableMap builtInShaderVariableMap; - struct BoundParameterInfo - { - BoundParameterInfo(ShaderType shaderType, uint32 paramType, uint32 location) - :shaderType(shaderType), paramType(paramType), location(location) - { - } + struct BoundParameterInfo + { + BoundParameterInfo(ShaderType shaderType, uint32 paramType, uint32 location) + : shaderType(shaderType), paramType(paramType), location(location) + { + } - ShaderType shaderType; - uint32 paramType; - uint32 location; - }; - struct BoundParameterList : public Vector - { - }; + ShaderType shaderType; + uint32 paramType; + uint32 location; + }; + struct BoundParameterList : public Vector + { + }; - class Material_Impl : public MaterialRes - { - public: - OpenGL* m_gl; - Shader m_shaders[3]; + class Material_Impl : public MaterialRes + { + public: + OpenGL* m_gl; + Shader m_shaders[3]; #if _DEBUG - String m_debugNames[3]; + String m_debugNames[3]; #endif #ifdef EMBEDDED - uint32 m_program; + uint32 m_program; #else - uint32 m_pipeline; + uint32 m_pipeline; #endif - Map m_boundParameters; - Map m_mappedParameters; - Map m_textureIDs; - uint32 m_userID = SV_User; - uint32 m_textureID = 0; - Set m_uniforms; + Map m_boundParameters; + Map m_mappedParameters; + Map m_textureIDs; + uint32 m_userID = SV_User; + uint32 m_textureID = 0; + Set m_uniforms; - Material_Impl(OpenGL* gl) : m_gl(gl) - { + Material_Impl(OpenGL* gl) : m_gl(gl) + { #ifdef EMBEDDED - m_program = glCreateProgram(); + m_program = glCreateProgram(); #else - glGenProgramPipelines(1, &m_pipeline); + glGenProgramPipelines(1, &m_pipeline); #endif - } - ~Material_Impl() - { - #ifdef EMBEDDED - if (glIsProgram(m_program)) - glDeleteProgram(m_program); - #else - glDeleteProgramPipelines(1, &m_pipeline); - #endif - } - void AssignShader(ShaderType t, Shader shader) override - { - m_shaders[(size_t)t] = shader; + } + ~Material_Impl() + { +#ifdef EMBEDDED + if (glIsProgram(m_program)) + glDeleteProgram(m_program); +#else + glDeleteProgramPipelines(1, &m_pipeline); +#endif + } + void AssignShader(ShaderType t, Shader shader) override + { + m_shaders[(size_t)t] = shader; - if (shader.get() == nullptr) - return; + if (shader.get() == nullptr) + return; - uint32 handle = shader->Handle(); + uint32 handle = shader->Handle(); #ifdef _DEBUG - Logf("Listing shader uniforms for %s", Logger::Severity::Info, shader->GetOriginalName()); + Logf("Listing shader uniforms for %s", Logger::Severity::Info, shader->GetOriginalName()); #endif // _DEBUG - int32 numUniforms; + int32 numUniforms; +#ifdef EMBEDDED + glAttachShader(m_program, handle); + glLinkProgram(m_program); + + glGetProgramiv(m_program, GL_ACTIVE_UNIFORMS, &numUniforms); +#else + glGetProgramiv(handle, GL_ACTIVE_UNIFORMS, &numUniforms); +#endif + + for (int32 i = 0; i < numUniforms; i++) + { + char name[64]; + int32 nameLen, size; + uint32 type; #ifdef EMBEDDED - glAttachShader(m_program, handle); - glLinkProgram(m_program); - - glGetProgramiv(m_program, GL_ACTIVE_UNIFORMS, &numUniforms); + glGetActiveUniform(m_program, i, sizeof(name), &nameLen, &size, &type, name); + uint32 loc = glGetUniformLocation(m_program, name); #else - glGetProgramiv(handle, GL_ACTIVE_UNIFORMS, &numUniforms); + glGetActiveUniform(handle, i, sizeof(name), &nameLen, &size, &type, name); + uint32 loc = glGetUniformLocation(handle, name); #endif - - for(int32 i = 0; i < numUniforms; i++) - { - char name[64]; - int32 nameLen, size; - uint32 type; - #ifdef EMBEDDED - glGetActiveUniform(m_program, i, sizeof(name), &nameLen, &size, &type, name); - uint32 loc = glGetUniformLocation(m_program, name); - #else - glGetActiveUniform(handle, i, sizeof(name), &nameLen, &size, &type, name); - uint32 loc = glGetUniformLocation(handle, name); - #endif - m_uniforms.Add(name); - // Select type - uint32 textureID = 0; - String typeName = "Unknown"; - if(type == GL_SAMPLER_2D) - { - typeName = "Sampler2D"; - if(!m_textureIDs.Contains(name)) - m_textureIDs.Add(name, m_textureID++); - } - else if(type == GL_FLOAT_MAT4) - { - typeName = "Transform"; - } - else if(type == GL_FLOAT_VEC4) - { - typeName = "Vector4"; - } - else if(type == GL_FLOAT_VEC3) - { - typeName = "Vector3"; - } - else if(type == GL_FLOAT_VEC2) - { - typeName = "Vector2"; - } - else if(type == GL_FLOAT) - { - typeName = "Float"; - } + m_uniforms.Add(name); + // Select type + uint32 textureID = 0; + String typeName = "Unknown"; + if (type == GL_SAMPLER_2D) + { + typeName = "Sampler2D"; + if (!m_textureIDs.Contains(name)) + m_textureIDs.Add(name, m_textureID++); + } + else if (type == GL_FLOAT_MAT4) + { + typeName = "Transform"; + } + else if (type == GL_FLOAT_VEC4) + { + typeName = "Vector4"; + } + else if (type == GL_FLOAT_VEC3) + { + typeName = "Vector3"; + } + else if (type == GL_FLOAT_VEC2) + { + typeName = "Vector2"; + } + else if (type == GL_FLOAT) + { + typeName = "Float"; + } - // Built in variable? - uint32 targetID = 0; - if(builtInShaderVariableMap.Contains(name)) - { - targetID = builtInShaderVariableMap[name]; - } - else - { - if(m_mappedParameters.Contains(name)) - targetID = m_mappedParameters[name]; - else - targetID = m_mappedParameters.Add(name, m_userID++); - } + // Built in variable? + uint32 targetID = 0; + if (builtInShaderVariableMap.Contains(name)) + { + targetID = builtInShaderVariableMap[name]; + } + else + { + if (m_mappedParameters.Contains(name)) + targetID = m_mappedParameters[name]; + else + targetID = m_mappedParameters.Add(name, m_userID++); + } - BoundParameterInfo& param = m_boundParameters.FindOrAdd(targetID).Add(BoundParameterInfo(t, type, loc)); + BoundParameterInfo& param = m_boundParameters.FindOrAdd(targetID).Add(BoundParameterInfo(t, type, loc)); #ifdef _DEBUG - Logf("Uniform [%d, loc=%d, %s] = %s", Logger::Severity::Info, - i, loc, Utility::Sprintf("Unknown [%d]", type), name); + Logf("Uniform [%d, loc=%d, %s] = %s", Logger::Severity::Info, + i, loc, Utility::Sprintf("Unknown [%d]", type), name); #endif // _DEBUG - } + } #ifndef EMBEDDED - glUseProgramStages(m_pipeline, shaderStageMap[(size_t)t], shader->Handle()); + glUseProgramStages(m_pipeline, shaderStageMap[(size_t)t], shader->Handle()); #endif - } + } - // Bind render state and params and shaders to context - virtual void Bind(const RenderState& rs, const MaterialParameterSet& params) override - { + // Bind render state and params and shaders to context + virtual void Bind(const RenderState& rs, const MaterialParameterSet& params) override + { #if _DEBUG - bool reloadedShaders = false; - for(uint32 i = 0; i < 3; i++) - { - if(m_shaders[i] && m_shaders[i]->UpdateHotReload()) - { - reloadedShaders = true; - } - } + bool reloadedShaders = false; + for (uint32 i = 0; i < 3; i++) + { + if (m_shaders[i] && m_shaders[i]->UpdateHotReload()) + { + reloadedShaders = true; + } + } - // Regenerate parameter map - if(reloadedShaders) - { - Log("Reloading material", Logger::Severity::Info); - m_boundParameters.clear(); - m_textureIDs.clear(); - m_mappedParameters.clear(); - m_userID = SV_User; - m_textureID = 0; - for(uint32 i = 0; i < 3; i++) - { - if(m_shaders[i]) - AssignShader(ShaderType(i), m_shaders[i]); - #ifdef EMBEDDED - glLinkProgram(m_program); - #endif - } - } + // Regenerate parameter map + if (reloadedShaders) + { + Log("Reloading material", Logger::Severity::Info); + m_boundParameters.clear(); + m_textureIDs.clear(); + m_mappedParameters.clear(); + m_userID = SV_User; + m_textureID = 0; + for (uint32 i = 0; i < 3; i++) + { + if (m_shaders[i]) + AssignShader(ShaderType(i), m_shaders[i]); +#ifdef EMBEDDED + glLinkProgram(m_program); +#endif + } + } +#endif +#ifdef EMBEDDED + BindToContext(); #endif - #ifdef EMBEDDED - BindToContext(); - #endif - // Bind renderstate variables - BindAll(SV_Proj, rs.projectionTransform); - BindAll(SV_Camera, rs.cameraTransform); - BindAll(SV_Viewport, rs.viewportSize); - BindAll(SV_AspectRatio, rs.aspectRatio); - Transform billboard = CameraMatrix::BillboardMatrix(rs.cameraTransform); - BindAll(SV_BillboardMatrix, billboard); - BindAll(SV_Time, rs.time); - - // Bind parameters - BindParameters(params, rs.worldTransform); - #ifndef EMBEDDED - BindToContext(); - #endif - } + // Bind renderstate variables + BindAll(SV_Proj, rs.projectionTransform); + BindAll(SV_Camera, rs.cameraTransform); + BindAll(SV_Viewport, rs.viewportSize); + BindAll(SV_AspectRatio, rs.aspectRatio); + Transform billboard = CameraMatrix::BillboardMatrix(rs.cameraTransform); + BindAll(SV_BillboardMatrix, billboard); + BindAll(SV_Time, rs.time); - // Bind only parameters - void BindParameters(const MaterialParameterSet& params, const Transform& worldTransform) override - { - BindAll(SV_World, worldTransform); - for(auto p : params) - { - switch(p.second.parameterType) - { - case GL_INT: - BindAll(p.first, p.second.Get()); - break; - case GL_FLOAT: - BindAll(p.first, p.second.Get()); - break; - case GL_INT_VEC2: - BindAll(p.first, p.second.Get()); - break; - case GL_INT_VEC3: - BindAll(p.first, p.second.Get()); - break; - case GL_INT_VEC4: - BindAll(p.first, p.second.Get()); - break; - case GL_FLOAT_VEC2: - BindAll(p.first, p.second.Get()); - break; - case GL_FLOAT_VEC3: - BindAll(p.first, p.second.Get()); - break; - case GL_FLOAT_VEC4: - BindAll(p.first, p.second.Get()); - break; - case GL_FLOAT_MAT4: - BindAll(p.first, p.second.Get()); - break; - case GL_SAMPLER_2D: - { - uint32* textureUnit = m_textureIDs.Find(p.first); - if(!textureUnit) - { - /// TODO: Add print once mechanism for these kind of errors - //Logf("Texture not found \"%s\"", Logger::Warning, p.first); - break; - } - Ref texture = p.second.Get>(); + // Bind parameters + BindParameters(params, rs.worldTransform); +#ifndef EMBEDDED + BindToContext(); +#endif + } - // Bind the texture - texture->Bind(*textureUnit); + // Bind only parameters + void BindParameters(const MaterialParameterSet& params, const Transform& worldTransform) override + { + BindAll(SV_World, worldTransform); + for (auto p : params) + { + switch (p.second.parameterType) + { + case GL_INT: + BindAll(p.first, p.second.Get()); + break; + case GL_FLOAT: + BindAll(p.first, p.second.Get()); + break; + case GL_INT_VEC2: + BindAll(p.first, p.second.Get()); + break; + case GL_INT_VEC3: + BindAll(p.first, p.second.Get()); + break; + case GL_INT_VEC4: + BindAll(p.first, p.second.Get()); + break; + case GL_FLOAT_VEC2: + BindAll(p.first, p.second.Get()); + break; + case GL_FLOAT_VEC3: + BindAll(p.first, p.second.Get()); + break; + case GL_FLOAT_VEC4: + BindAll(p.first, p.second.Get()); + break; + case GL_FLOAT_MAT4: + BindAll(p.first, p.second.Get()); + break; + case GL_SAMPLER_2D: + { + uint32* textureUnit = m_textureIDs.Find(p.first); + if (!textureUnit) + { + /// TODO: Add print once mechanism for these kind of errors + // Logf("Texture not found \"%s\"", Logger::Warning, p.first); + break; + } + Ref texture = p.second.Get>(); + // Bind the texture + texture->Bind(*textureUnit); - // Bind sampler - BindAll(p.first, *textureUnit); - break; - } - default: - assert(false); - } - } - } + // Bind sampler + BindAll(p.first, *textureUnit); + break; + } + default: + assert(false); + } + } + } - void BindToContext() override - { - // Bind pipeline to context - #ifdef EMBEDDED - glUseProgram(m_program); - #else - glBindProgramPipeline(m_pipeline); - #endif - } + void BindToContext() override + { + // Bind pipeline to context +#ifdef EMBEDDED + glUseProgram(m_program); +#else + glBindProgramPipeline(m_pipeline); +#endif + } - virtual bool HasUniform(String name) override - { - return m_uniforms.Contains(name); - } + virtual bool HasUniform(String name) override + { + return m_uniforms.Contains(name); + } - BoundParameterInfo* GetBoundParameters(const String& name, uint32& count) - { - uint32* mappedID = m_mappedParameters.Find(name); - if(!mappedID) - return nullptr; - return GetBoundParameters((BuiltInShaderVariable)*mappedID, count); - } - BoundParameterInfo* GetBoundParameters(BuiltInShaderVariable bsv, uint32& count) - { - BoundParameterList* l = m_boundParameters.Find(bsv); - if(!l) - return nullptr; - else - { - count = (uint32)l->size(); - return l->data(); - } - } - template void BindAll(const String& name, const T& obj) - { - uint32 num = 0; - #ifdef EMBEDDED - glUseProgram(m_program); - #endif - BoundParameterInfo* bp = GetBoundParameters(name, num); - for(uint32 i = 0; bp && i < num; i++) - { - BindShaderVar(m_shaders[(size_t)bp[i].shaderType]->Handle(), bp[i].location, obj); - } - } - template void BindAll(BuiltInShaderVariable bsv, const T& obj) - { - uint32 num = 0; - #ifdef EMBEDDED - glUseProgram(m_program); - #endif - BoundParameterInfo* bp = GetBoundParameters(bsv, num); - for(uint32 i = 0; bp && i < num; i++) - { - BindShaderVar(m_shaders[(size_t)bp[i].shaderType]->Handle(), bp[i].location, obj); - } - } + BoundParameterInfo* GetBoundParameters(const String& name, uint32& count) + { + uint32* mappedID = m_mappedParameters.Find(name); + if (!mappedID) + return nullptr; + return GetBoundParameters((BuiltInShaderVariable)*mappedID, count); + } + BoundParameterInfo* GetBoundParameters(BuiltInShaderVariable bsv, uint32& count) + { + BoundParameterList* l = m_boundParameters.Find(bsv); + if (!l) + return nullptr; + else + { + count = (uint32)l->size(); + return l->data(); + } + } + template + void BindAll(const String& name, const T& obj) + { + uint32 num = 0; +#ifdef EMBEDDED + glUseProgram(m_program); +#endif + BoundParameterInfo* bp = GetBoundParameters(name, num); + for (uint32 i = 0; bp && i < num; i++) + { + BindShaderVar(m_shaders[(size_t)bp[i].shaderType]->Handle(), bp[i].location, obj); + } + } + template + void BindAll(BuiltInShaderVariable bsv, const T& obj) + { + uint32 num = 0; +#ifdef EMBEDDED + glUseProgram(m_program); +#endif + BoundParameterInfo* bp = GetBoundParameters(bsv, num); + for (uint32 i = 0; bp && i < num; i++) + { + BindShaderVar(m_shaders[(size_t)bp[i].shaderType]->Handle(), bp[i].location, obj); + } + } + + template + void BindShaderVar(uint32 shader, uint32 loc, const T& obj) + { + static_assert(sizeof(T) != 0, "Incompatible shader uniform type"); + } + }; - template void BindShaderVar(uint32 shader, uint32 loc, const T& obj) - { - static_assert(sizeof(T) != 0, "Incompatible shader uniform type"); - } - }; - #ifdef EMBEDDED - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector4& obj) - { - glUniform4fv(loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector3& obj) - { - glUniform3fv(loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector2& obj) - { - glUniform2fv(loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const float& obj) - { - glUniform1fv(loc, 1, &obj); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Colori& obj) - { - Color c = obj; - glUniform4fv(loc, 1, &c.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector4i& obj) - { - glUniform4iv(loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector3i& obj) - { - glUniform3iv(loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector2i& obj) - { - glUniform2iv(loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const int32& obj) - { - glUniform1iv(loc, 1, &obj); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Transform& obj) - { - glUniformMatrix4fv(loc, 1, GL_FALSE, obj.mat); - } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector4& obj) + { + glUniform4fv(loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector3& obj) + { + glUniform3fv(loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector2& obj) + { + glUniform2fv(loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const float& obj) + { + glUniform1fv(loc, 1, &obj); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Colori& obj) + { + Color c = obj; + glUniform4fv(loc, 1, &c.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector4i& obj) + { + glUniform4iv(loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector3i& obj) + { + glUniform3iv(loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector2i& obj) + { + glUniform2iv(loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const int32& obj) + { + glUniform1iv(loc, 1, &obj); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Transform& obj) + { + glUniformMatrix4fv(loc, 1, GL_FALSE, obj.mat); + } #else - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector4& obj) - { - glProgramUniform4fv(shader, loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector3& obj) - { - glProgramUniform3fv(shader, loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector2& obj) - { - glProgramUniform2fv(shader, loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const float& obj) - { - glProgramUniform1fv(shader, loc, 1, &obj); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Colori& obj) - { - Color c = obj; - glProgramUniform4fv(shader, loc, 1, &c.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector4i& obj) - { - glProgramUniform4iv(shader, loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector3i& obj) - { - glProgramUniform3iv(shader, loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector2i& obj) - { - glProgramUniform2iv(shader, loc, 1, &obj.x); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const int32& obj) - { - glProgramUniform1iv(shader, loc, 1, &obj); - } - template<> void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Transform& obj) - { - glProgramUniformMatrix4fv(shader, loc, 1, GL_FALSE, obj.mat); - } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector4& obj) + { + glProgramUniform4fv(shader, loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector3& obj) + { + glProgramUniform3fv(shader, loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector2& obj) + { + glProgramUniform2fv(shader, loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const float& obj) + { + glProgramUniform1fv(shader, loc, 1, &obj); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Colori& obj) + { + Color c = obj; + glProgramUniform4fv(shader, loc, 1, &c.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector4i& obj) + { + glProgramUniform4iv(shader, loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector3i& obj) + { + glProgramUniform3iv(shader, loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Vector2i& obj) + { + glProgramUniform2iv(shader, loc, 1, &obj.x); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const int32& obj) + { + glProgramUniform1iv(shader, loc, 1, &obj); + } + template <> + void Material_Impl::BindShaderVar(uint32 shader, uint32 loc, const Transform& obj) + { + glProgramUniformMatrix4fv(shader, loc, 1, GL_FALSE, obj.mat); + } #endif - Material MaterialRes::Create(OpenGL* gl) - { - Material_Impl* impl = new Material_Impl(gl); - return GetResourceManager().Register(impl); - - } - Material MaterialRes::Create(OpenGL* gl, const String& vsPath, const String& fsPath) - { - Material_Impl* impl = new Material_Impl(gl); - impl->AssignShader(ShaderType::Vertex, ShaderRes::Create(gl, ShaderType::Vertex, vsPath)); - impl->AssignShader(ShaderType::Fragment, ShaderRes::Create(gl, ShaderType::Fragment, fsPath)); + Material MaterialRes::Create(OpenGL* gl) + { + Material_Impl* impl = new Material_Impl(gl); + return GetResourceManager().Register(impl); + } + Material MaterialRes::Create(OpenGL* gl, const String& vsPath, const String& fsPath) + { + Material_Impl* impl = new Material_Impl(gl); + impl->AssignShader(ShaderType::Vertex, ShaderRes::Create(gl, ShaderType::Vertex, vsPath)); + impl->AssignShader(ShaderType::Fragment, ShaderRes::Create(gl, ShaderType::Fragment, fsPath)); #if _DEBUG - impl->m_debugNames[(size_t)ShaderType::Vertex] = vsPath; - impl->m_debugNames[(size_t)ShaderType::Fragment] = fsPath; + impl->m_debugNames[(size_t)ShaderType::Vertex] = vsPath; + impl->m_debugNames[(size_t)ShaderType::Fragment] = fsPath; #endif - if(!impl->m_shaders[(size_t)ShaderType::Vertex]) - { - Logf("Failed to load vertex shader for material from %s", Logger::Severity::Error, vsPath); - delete impl; - return Material(); - } - if(!impl->m_shaders[(size_t)ShaderType::Fragment]) - { - Logf("Failed to load fragment shader for material from %s", Logger::Severity::Error, fsPath); - delete impl; - return Material(); - } + if (!impl->m_shaders[(size_t)ShaderType::Vertex]) + { + Logf("Failed to load vertex shader for material from %s", Logger::Severity::Error, vsPath); + delete impl; + return Material(); + } + if (!impl->m_shaders[(size_t)ShaderType::Fragment]) + { + Logf("Failed to load fragment shader for material from %s", Logger::Severity::Error, fsPath); + delete impl; + return Material(); + } - return GetResourceManager().Register(impl); - } + return GetResourceManager().Register(impl); + } - void MaterialParameterSet::SetParameter(const String& name, int sc) - { - Add(name, MaterialParameter::Create(sc, GL_INT)); - } - void MaterialParameterSet::SetParameter(const String& name, float sc) - { - Add(name, MaterialParameter::Create(sc, GL_FLOAT)); - } - void MaterialParameterSet::SetParameter(const String& name, const Vector4& vec) - { - Add(name, MaterialParameter::Create(vec, GL_FLOAT_VEC4)); - } - void MaterialParameterSet::SetParameter(const String& name, const Colori& color) - { - Add(name, MaterialParameter::Create(Color(color), GL_FLOAT_VEC4)); - } - void MaterialParameterSet::SetParameter(const String& name, const Vector2& vec2) - { - Add(name, MaterialParameter::Create(vec2, GL_FLOAT_VEC2)); - } - void MaterialParameterSet::SetParameter(const String& name, const Vector3& vec3) - { - Add(name, MaterialParameter::Create(vec3, GL_FLOAT_VEC3)); - } - void MaterialParameterSet::SetParameter(const String& name, const Transform& tf) - { - Add(name, MaterialParameter::Create(tf, GL_FLOAT_MAT4)); - } - void MaterialParameterSet::SetParameter(const String& name, Ref tex) - { - Add(name, MaterialParameter::Create(tex, GL_SAMPLER_2D)); - } - void MaterialParameterSet::SetParameter(const String& name, const Vector2i& vec2) - { - Add(name, MaterialParameter::Create(vec2, GL_INT_VEC2)); - } + void MaterialParameterSet::SetParameter(const String& name, int sc) + { + Add(name, MaterialParameter::Create(sc, GL_INT)); + } + void MaterialParameterSet::SetParameter(const String& name, float sc) + { + Add(name, MaterialParameter::Create(sc, GL_FLOAT)); + } + void MaterialParameterSet::SetParameter(const String& name, const Vector4& vec) + { + Add(name, MaterialParameter::Create(vec, GL_FLOAT_VEC4)); + } + void MaterialParameterSet::SetParameter(const String& name, const Colori& color) + { + Add(name, MaterialParameter::Create(Color(color), GL_FLOAT_VEC4)); + } + void MaterialParameterSet::SetParameter(const String& name, const Vector2& vec2) + { + Add(name, MaterialParameter::Create(vec2, GL_FLOAT_VEC2)); + } + void MaterialParameterSet::SetParameter(const String& name, const Vector3& vec3) + { + Add(name, MaterialParameter::Create(vec3, GL_FLOAT_VEC3)); + } + void MaterialParameterSet::SetParameter(const String& name, const Transform& tf) + { + Add(name, MaterialParameter::Create(tf, GL_FLOAT_MAT4)); + } + void MaterialParameterSet::SetParameter(const String& name, Ref tex) + { + Add(name, MaterialParameter::Create(tex, GL_SAMPLER_2D)); + } + void MaterialParameterSet::SetParameter(const String& name, const Vector2i& vec2) + { + Add(name, MaterialParameter::Create(vec2, GL_INT_VEC2)); + } } diff --git a/Graphics/src/Mesh.cpp b/Graphics/src/Mesh.cpp index 9f5b572ec..9ea779de8 100644 --- a/Graphics/src/Mesh.cpp +++ b/Graphics/src/Mesh.cpp @@ -4,134 +4,135 @@ namespace Graphics { - uint32 primitiveTypeMap[] = - { - GL_TRIANGLES, - GL_TRIANGLE_STRIP, - GL_TRIANGLE_FAN, - GL_LINES, - GL_LINE_STRIP, - GL_POINTS, - }; - class Mesh_Impl : public MeshRes - { - uint32 m_buffer = 0; - uint32 m_vao = 0; - PrimitiveType m_type; - uint32 m_glType; - size_t m_vertexCount; - bool m_bDynamic = true; - public: - Mesh_Impl() - { - } - ~Mesh_Impl() - { - if(m_buffer) - glDeleteBuffers(1, &m_buffer); - if(m_vao) - glDeleteVertexArrays(1, &m_vao); - } - bool Init() - { - glGenBuffers(1, &m_buffer); - glGenVertexArrays(1, &m_vao); - return m_buffer != 0 && m_vao != 0; - } + uint32 primitiveTypeMap[] = + { + GL_TRIANGLES, + GL_TRIANGLE_STRIP, + GL_TRIANGLE_FAN, + GL_LINES, + GL_LINE_STRIP, + GL_POINTS, + }; + class Mesh_Impl : public MeshRes + { + uint32 m_buffer = 0; + uint32 m_vao = 0; + PrimitiveType m_type; + uint32 m_glType; + size_t m_vertexCount; + bool m_bDynamic = true; - void SetData(const void* pData, size_t vertexCount, const VertexFormatList& desc) override - { - glBindVertexArray(m_vao); - glBindBuffer(GL_ARRAY_BUFFER, m_buffer); + public: + Mesh_Impl() + { + } + ~Mesh_Impl() + { + if (m_buffer) + glDeleteBuffers(1, &m_buffer); + if (m_vao) + glDeleteVertexArrays(1, &m_vao); + } + bool Init() + { + glGenBuffers(1, &m_buffer); + glGenVertexArrays(1, &m_vao); + return m_buffer != 0 && m_vao != 0; + } - m_vertexCount = vertexCount; - size_t totalVertexSize = 0; - for(auto e : desc) - totalVertexSize += e.componentSize * e.components; - size_t index = 0; - size_t offset = 0; - for(auto e : desc) - { - uint32 type = -1; - if(!e.isFloat) - { - if(e.componentSize == 4) - type = e.isSigned ? GL_INT : GL_UNSIGNED_INT; - else if(e.componentSize == 2) - type = e.isSigned ? GL_SHORT : GL_UNSIGNED_SHORT; - else if(e.componentSize == 1) - type = e.isSigned ? GL_BYTE : GL_UNSIGNED_BYTE; - } - else - { - #ifdef EMBEDDED - type = GL_FLOAT; - #else - if(e.componentSize == 4) - type = GL_FLOAT; - else if(e.componentSize == 8) - type = GL_DOUBLE; - #endif - } - assert(type != (uint32)-1); - glVertexAttribPointer((int)index, (int)e.components, type, GL_TRUE, (int)totalVertexSize, (void*)offset); - glEnableVertexAttribArray((int)index); - offset += e.componentSize * e.components; - index++; - } - glBufferData(GL_ARRAY_BUFFER, totalVertexSize * vertexCount, pData, m_bDynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); + void SetData(const void* pData, size_t vertexCount, const VertexFormatList& desc) override + { + glBindVertexArray(m_vao); + glBindBuffer(GL_ARRAY_BUFFER, m_buffer); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - - #ifdef EMBEDDED - void Draw() override - { - glBindVertexArray(m_vao); - glDrawArrays(m_glType, 0, (int)m_vertexCount); - glBindVertexArray(0); - } - void Redraw() override - { - glBindVertexArray(m_vao); - glDrawArrays(m_glType, 0, (int)m_vertexCount); - glBindVertexArray(0); - } - #else - void Draw() override - { - glBindVertexArray(m_vao); - glDrawArrays(m_glType, 0, (int)m_vertexCount); - } - void Redraw() override - { - glDrawArrays(m_glType, 0, (int)m_vertexCount); - } - #endif + m_vertexCount = vertexCount; + size_t totalVertexSize = 0; + for (auto e : desc) + totalVertexSize += e.componentSize * e.components; + size_t index = 0; + size_t offset = 0; + for (auto e : desc) + { + uint32 type = -1; + if (!e.isFloat) + { + if (e.componentSize == 4) + type = e.isSigned ? GL_INT : GL_UNSIGNED_INT; + else if (e.componentSize == 2) + type = e.isSigned ? GL_SHORT : GL_UNSIGNED_SHORT; + else if (e.componentSize == 1) + type = e.isSigned ? GL_BYTE : GL_UNSIGNED_BYTE; + } + else + { +#ifdef EMBEDDED + type = GL_FLOAT; +#else + if (e.componentSize == 4) + type = GL_FLOAT; + else if (e.componentSize == 8) + type = GL_DOUBLE; +#endif + } + assert(type != (uint32)-1); + glVertexAttribPointer((int)index, (int)e.components, type, GL_TRUE, (int)totalVertexSize, (void*)offset); + glEnableVertexAttribArray((int)index); + offset += e.componentSize * e.components; + index++; + } + glBufferData(GL_ARRAY_BUFFER, totalVertexSize * vertexCount, pData, m_bDynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); - void SetPrimitiveType(PrimitiveType pt) override - { - m_type = pt; - m_glType = primitiveTypeMap[(size_t)pt]; - } - virtual PrimitiveType GetPrimitiveType() const override - { - return m_type; - } - }; + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } - Mesh MeshRes::Create(class OpenGL* gl) - { - Mesh_Impl* pImpl = new Mesh_Impl(); - if(!pImpl->Init()) - { - delete pImpl; - return Mesh(); - } - else - { - return GetResourceManager().Register(pImpl); - } - } -} \ No newline at end of file +#ifdef EMBEDDED + void Draw() override + { + glBindVertexArray(m_vao); + glDrawArrays(m_glType, 0, (int)m_vertexCount); + glBindVertexArray(0); + } + void Redraw() override + { + glBindVertexArray(m_vao); + glDrawArrays(m_glType, 0, (int)m_vertexCount); + glBindVertexArray(0); + } +#else + void Draw() override + { + glBindVertexArray(m_vao); + glDrawArrays(m_glType, 0, (int)m_vertexCount); + } + void Redraw() override + { + glDrawArrays(m_glType, 0, (int)m_vertexCount); + } +#endif + + void SetPrimitiveType(PrimitiveType pt) override + { + m_type = pt; + m_glType = primitiveTypeMap[(size_t)pt]; + } + virtual PrimitiveType GetPrimitiveType() const override + { + return m_type; + } + }; + + Mesh MeshRes::Create(class OpenGL* gl) + { + Mesh_Impl* pImpl = new Mesh_Impl(); + if (!pImpl->Init()) + { + delete pImpl; + return Mesh(); + } + else + { + return GetResourceManager().Register(pImpl); + } + } +} diff --git a/Graphics/src/MeshGenerators.cpp b/Graphics/src/MeshGenerators.cpp index b1df4331a..9147f33c6 100644 --- a/Graphics/src/MeshGenerators.cpp +++ b/Graphics/src/MeshGenerators.cpp @@ -3,59 +3,89 @@ namespace Graphics { - Mesh MeshGenerators::Quad(OpenGL* gl, Vector2 pos, Vector2 size /*= Vector2(1,1)*/) - { - Vector verts = - { - { { 0.0f, size.y, 0.0f }, { 0.0f, 0.0f } }, - { { size.x, 0.0f, 0.0f }, { 1.0f, 1.0f } }, - { { size.x, size.y, 0.0f }, { 1.0f, 0.0f } }, - - { { 0.0f, size.y, 0.0f }, { 0.0f, 0.0f } }, - { { 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f } }, - { { size.x, 0.0f, 0.0f }, { 1.0f, 1.0f } }, - }; - - for(auto& v : verts) - { - v.pos += pos; - } - - Mesh mesh = MeshRes::Create(gl); - mesh->SetData(verts); - mesh->SetPrimitiveType(PrimitiveType::TriangleList); - return mesh; - } - - void MeshGenerators::GenerateSimpleXYQuad(Rect3D r, Rect uv, Vector& out) - { - Vector verts = - { - { { r.Left(), r.Top(), 0.0f },{ uv.Left(), uv.Top() } }, - { { r.Right(), r.Bottom(), 0.0f },{ uv.Right(), uv.Bottom() } }, - { { r.Right(), r.Top(), 0.0f },{ uv.Right(), uv.Top() } }, - - { { r.Left(), r.Top(), 0.0f },{ uv.Left(), uv.Top() } }, - { { r.Left(), r.Bottom(), 0.0f },{ uv.Left(), uv.Bottom() } }, - { { r.Right(), r.Bottom(), 0.0f },{ uv.Right(), uv.Bottom() } }, - }; - for(auto& v : verts) - out.Add(v); - } - void MeshGenerators::GenerateSimpleXZQuad(Rect3D r, Rect uv, Vector& out) - { - Vector verts = - { - { { r.Left(), 0.0f, r.Top(), },{ uv.Left(), uv.Top() } }, - { { r.Right(), 0.0f, r.Bottom(), },{ uv.Right(), uv.Bottom() } }, - { { r.Right(), 0.0f, r.Top(), },{ uv.Right(), uv.Top() } }, - - { { r.Left(), 0.0f, r.Top(), },{ uv.Left(), uv.Top() } }, - { { r.Left(), 0.0f, r.Bottom(), },{ uv.Left(), uv.Bottom() } }, - { { r.Right(), 0.0f, r.Bottom(), },{ uv.Right(), uv.Bottom() } }, - }; - for(auto& v : verts) - out.Add(v); - } + Mesh MeshGenerators::Quad(OpenGL* gl, Vector2 pos, Vector2 size /*= Vector2(1,1)*/) + { + Vector verts = + { + {{0.0f, size.y, 0.0f}, {0.0f, 0.0f}}, + {{size.x, 0.0f, 0.0f}, {1.0f, 1.0f}}, + {{size.x, size.y, 0.0f}, {1.0f, 0.0f}}, + + {{0.0f, size.y, 0.0f}, {0.0f, 0.0f}}, + {{0.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}, + {{size.x, 0.0f, 0.0f}, {1.0f, 1.0f}}, + }; + + for (auto& v : verts) + { + v.pos += pos; + } + + Mesh mesh = MeshRes::Create(gl); + mesh->SetData(verts); + mesh->SetPrimitiveType(PrimitiveType::TriangleList); + return mesh; + } + + void MeshGenerators::GenerateSimpleXYQuad(Rect3D r, Rect uv, Vector& out) + { + Vector verts = + { + {{r.Left(), r.Top(), 0.0f}, {uv.Left(), uv.Top()}}, + {{r.Right(), r.Bottom(), 0.0f}, {uv.Right(), uv.Bottom()}}, + {{r.Right(), r.Top(), 0.0f}, {uv.Right(), uv.Top()}}, + + {{r.Left(), r.Top(), 0.0f}, {uv.Left(), uv.Top()}}, + {{r.Left(), r.Bottom(), 0.0f}, {uv.Left(), uv.Bottom()}}, + {{r.Right(), r.Bottom(), 0.0f}, {uv.Right(), uv.Bottom()}}, + }; + for (auto& v : verts) + out.Add(v); + } + void MeshGenerators::GenerateSimpleXZQuad(Rect3D r, Rect uv, Vector& out) + { + Vector verts = + { + {{ + r.Left(), + 0.0f, + r.Top(), + }, + {uv.Left(), uv.Top()}}, + {{ + r.Right(), + 0.0f, + r.Bottom(), + }, + {uv.Right(), uv.Bottom()}}, + {{ + r.Right(), + 0.0f, + r.Top(), + }, + {uv.Right(), uv.Top()}}, + + {{ + r.Left(), + 0.0f, + r.Top(), + }, + {uv.Left(), uv.Top()}}, + {{ + r.Left(), + 0.0f, + r.Bottom(), + }, + {uv.Left(), uv.Bottom()}}, + {{ + r.Right(), + 0.0f, + r.Bottom(), + }, + {uv.Right(), uv.Bottom()}}, + }; + for (auto& v : verts) + out.Add(v); + } } diff --git a/Graphics/src/OpenGL.cpp b/Graphics/src/OpenGL.cpp index 24c4a2701..7ba9c8160 100644 --- a/Graphics/src/OpenGL.cpp +++ b/Graphics/src/OpenGL.cpp @@ -16,213 +16,212 @@ namespace Graphics { - class OpenGL_Impl - { - public: - SDL_GLContext context; - std::thread::id threadId; - }; - - OpenGL::OpenGL() - { - m_impl = new OpenGL_Impl(); - } - OpenGL::~OpenGL() - { - if(m_impl->context) - { - // Cleanup resource managers - ResourceManagers::DestroyResourceManager(); - ResourceManagers::DestroyResourceManager(); - ResourceManagers::DestroyResourceManager(); - ResourceManagers::DestroyResourceManager(); - ResourceManagers::DestroyResourceManager(); - ResourceManagers::DestroyResourceManager(); + class OpenGL_Impl + { + public: + SDL_GLContext context; + std::thread::id threadId; + }; + + OpenGL::OpenGL() + { + m_impl = new OpenGL_Impl(); + } + OpenGL::~OpenGL() + { + if (m_impl->context) + { + // Cleanup resource managers + ResourceManagers::DestroyResourceManager(); + ResourceManagers::DestroyResourceManager(); + ResourceManagers::DestroyResourceManager(); + ResourceManagers::DestroyResourceManager(); + ResourceManagers::DestroyResourceManager(); + ResourceManagers::DestroyResourceManager(); #ifndef EMBEDDED - if(glBindProgramPipeline) - { - glDeleteProgramPipelines(1, &m_mainProgramPipeline); - } + if (glBindProgramPipeline) + { + glDeleteProgramPipelines(1, &m_mainProgramPipeline); + } #endif - SDL_GL_DeleteContext(m_impl->context); - m_impl->context = nullptr; - } - delete m_impl; - } - void OpenGL::InitResourceManagers() - { - ResourceManagers::CreateResourceManager(); - ResourceManagers::CreateResourceManager(); - ResourceManagers::CreateResourceManager(); - ResourceManagers::CreateResourceManager(); - ResourceManagers::CreateResourceManager(); - ResourceManagers::CreateResourceManager(); - } - bool OpenGL::Init(Window& window, uint32 antialiasing) - { - if(m_impl->context) - return true; // Already initialized - - // Store the thread ID that the OpenGL context runs on - m_impl->threadId = std::this_thread::get_id(); - - m_window = &window; - SDL_Window* sdlWnd = (SDL_Window*)m_window->Handle(); + SDL_GL_DeleteContext(m_impl->context); + m_impl->context = nullptr; + } + delete m_impl; + } + void OpenGL::InitResourceManagers() + { + ResourceManagers::CreateResourceManager(); + ResourceManagers::CreateResourceManager(); + ResourceManagers::CreateResourceManager(); + ResourceManagers::CreateResourceManager(); + ResourceManagers::CreateResourceManager(); + ResourceManagers::CreateResourceManager(); + } + bool OpenGL::Init(Window& window, uint32 antialiasing) + { + if (m_impl->context) + return true; // Already initialized + + // Store the thread ID that the OpenGL context runs on + m_impl->threadId = std::this_thread::get_id(); + + m_window = &window; + SDL_Window* sdlWnd = (SDL_Window*)m_window->Handle(); #ifdef EMBEDDED - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #else - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); #endif - // Create a context - m_impl->context = SDL_GL_CreateContext(sdlWnd); - if(!m_impl->context) - { + // Create a context + m_impl->context = SDL_GL_CreateContext(sdlWnd); + if (!m_impl->context) + { Logf("Failed to create OpenGL context: %s", Logger::Severity::Error, SDL_GetError()); return false; - } + } - if (SDL_GL_MakeCurrent(sdlWnd, m_impl->context) < 0) - { - Logf("Failed to set OpenGL context to current: %s", Logger::Severity::Error, SDL_GetError()); - return false; - } + if (SDL_GL_MakeCurrent(sdlWnd, m_impl->context) < 0) + { + Logf("Failed to set OpenGL context to current: %s", Logger::Severity::Error, SDL_GetError()); + return false; + } - //windows always needs glew + // windows always needs glew #ifdef _WIN32 - glewExperimental = true; - glewInit(); + glewExperimental = true; + glewInit(); #else - // macOS and embedded doesnt need glew - #ifndef EMBEDDED - #ifndef __APPLE__ - // To allow usage of experimental features - glewExperimental = true; - glewInit(); - #endif - #endif +// macOS and embedded doesnt need glew +#ifndef EMBEDDED +#ifndef __APPLE__ + // To allow usage of experimental features + glewExperimental = true; + glewInit(); +#endif +#endif #endif - //#define LIST_OGL_EXTENSIONS + //#define LIST_OGL_EXTENSIONS #ifdef LIST_OGL_EXTENSIONS - Logf("Listing OpenGL Extensions:", Logger::Info); - GLint n, i; - glGetIntegerv(GL_NUM_EXTENSIONS, &n); - for(i = 0; i < n; i++) { - Logf("%s", Logger::Info, glGetStringi(GL_EXTENSIONS, i)); - } + Logf("Listing OpenGL Extensions:", Logger::Info); + GLint n, i; + glGetIntegerv(GL_NUM_EXTENSIONS, &n); + for (i = 0; i < n; i++) + { + Logf("%s", Logger::Info, glGetStringi(GL_EXTENSIONS, i)); + } #endif #ifdef _DEBUG - // Setup GL debug messages to go to the console - if(glDebugMessageCallback && glDebugMessageControl) - { - Log("OpenGL Logging on.", Logger::Severity::Info); - glDebugMessageCallback(GLDebugProc, 0); - glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE); - glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, 0, 0, GL_FALSE); - } + // Setup GL debug messages to go to the console + if (glDebugMessageCallback && glDebugMessageControl) + { + Log("OpenGL Logging on.", Logger::Severity::Info); + glDebugMessageCallback(GLDebugProc, 0); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE); + glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, 0, 0, GL_FALSE); + } #endif - Logf("OpenGL Version: %s", Logger::Severity::Info, glGetString(GL_VERSION)); - Logf("OpenGL Shading Language Version: %s", Logger::Severity::Info, glGetString(GL_SHADING_LANGUAGE_VERSION)); - Logf("OpenGL Renderer: %s", Logger::Severity::Info, glGetString(GL_RENDERER)); - Logf("OpenGL Vendor: %s", Logger::Severity::Info, glGetString(GL_VENDOR)); + Logf("OpenGL Version: %s", Logger::Severity::Info, glGetString(GL_VERSION)); + Logf("OpenGL Shading Language Version: %s", Logger::Severity::Info, glGetString(GL_SHADING_LANGUAGE_VERSION)); + Logf("OpenGL Renderer: %s", Logger::Severity::Info, glGetString(GL_RENDERER)); + Logf("OpenGL Vendor: %s", Logger::Severity::Info, glGetString(GL_VENDOR)); - InitResourceManagers(); + InitResourceManagers(); #ifndef EMBEDDED - // Create pipeline for the program - glGenProgramPipelines(1, &m_mainProgramPipeline); - glBindProgramPipeline(m_mainProgramPipeline); - glEnable(GL_TEXTURE_2D); - glEnable(GL_MULTISAMPLE); + // Create pipeline for the program + glGenProgramPipelines(1, &m_mainProgramPipeline); + glBindProgramPipeline(m_mainProgramPipeline); + glEnable(GL_TEXTURE_2D); + glEnable(GL_MULTISAMPLE); #endif - glDisable(GL_DEPTH_TEST); - glEnable(GL_CULL_FACE); - glEnable(GL_BLEND); - glEnable(GL_STENCIL_TEST); - return true; - } - - - Recti OpenGL::GetViewport() const - { - Recti vp; - glGetIntegerv(GL_VIEWPORT, &vp.pos.x); - return vp; - } - uint32 OpenGL::GetFramebufferHandle() - { - return GL_BACK; - } - - void OpenGL::SetViewport(Recti vp) - { - glViewport(vp.pos.x, vp.pos.y, vp.size.x, vp.size.y); - } - - //https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading - void OpenGL::MakeCurrent() - { - assert(m_impl->threadId != std::this_thread::get_id()); - SDL_GL_MakeCurrent((SDL_Window*)m_window->Handle(), m_impl->context); - m_impl->threadId = std::this_thread::get_id(); - - } - void OpenGL::ReleaseCurrent() - { - assert(m_impl->threadId == std::this_thread::get_id()); - SDL_GL_MakeCurrent(NULL, NULL); - } - - void OpenGL::SetViewport(Vector2i size) - { - glViewport(0, 0, size.x, size.y); - } - bool OpenGL::IsOpenGLThread() const - { - return m_impl->threadId == std::this_thread::get_id(); - } - - void OpenGL::SwapBuffers() - { - glFlush(); - SDL_Window* sdlWnd = (SDL_Window*)m_window->Handle(); - SDL_GL_SwapWindow(sdlWnd); - } - - #ifdef _WIN32 - void APIENTRY GLDebugProc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) - #else - void GLDebugProc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) - #endif - { -#define DEBUG_SEVERITY_HIGH 0x9146 -#define DEBUG_SEVERITY_MEDIUM 0x9147 -#define DEBUG_SEVERITY_LOW 0x9148 -#define DEBUG_SEVERITY_NOTIFICATION 0x826B - - Logger::Severity mySeverity; - switch(severity) - { - case DEBUG_SEVERITY_MEDIUM: - case DEBUG_SEVERITY_HIGH: - mySeverity = Logger::Severity::Warning; - break; - default: - mySeverity = Logger::Severity::Info; - break; - } - String msgString = String(message, message + length); - Logf("GLDebug: %s", mySeverity, msgString); - } + glDisable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + glEnable(GL_STENCIL_TEST); + return true; + } + + Recti OpenGL::GetViewport() const + { + Recti vp; + glGetIntegerv(GL_VIEWPORT, &vp.pos.x); + return vp; + } + uint32 OpenGL::GetFramebufferHandle() + { + return GL_BACK; + } + + void OpenGL::SetViewport(Recti vp) + { + glViewport(vp.pos.x, vp.pos.y, vp.size.x, vp.size.y); + } + + // https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading + void OpenGL::MakeCurrent() + { + assert(m_impl->threadId != std::this_thread::get_id()); + SDL_GL_MakeCurrent((SDL_Window*)m_window->Handle(), m_impl->context); + m_impl->threadId = std::this_thread::get_id(); + } + void OpenGL::ReleaseCurrent() + { + assert(m_impl->threadId == std::this_thread::get_id()); + SDL_GL_MakeCurrent(NULL, NULL); + } + + void OpenGL::SetViewport(Vector2i size) + { + glViewport(0, 0, size.x, size.y); + } + bool OpenGL::IsOpenGLThread() const + { + return m_impl->threadId == std::this_thread::get_id(); + } + + void OpenGL::SwapBuffers() + { + glFlush(); + SDL_Window* sdlWnd = (SDL_Window*)m_window->Handle(); + SDL_GL_SwapWindow(sdlWnd); + } + +#ifdef _WIN32 + void APIENTRY GLDebugProc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) +#else + void GLDebugProc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) +#endif + { +#define DEBUG_SEVERITY_HIGH 0x9146 +#define DEBUG_SEVERITY_MEDIUM 0x9147 +#define DEBUG_SEVERITY_LOW 0x9148 +#define DEBUG_SEVERITY_NOTIFICATION 0x826B + + Logger::Severity mySeverity; + switch (severity) + { + case DEBUG_SEVERITY_MEDIUM: + case DEBUG_SEVERITY_HIGH: + mySeverity = Logger::Severity::Warning; + break; + default: + mySeverity = Logger::Severity::Info; + break; + } + String msgString = String(message, message + length); + Logf("GLDebug: %s", mySeverity, msgString); + } } diff --git a/Graphics/src/ParticleSystem.cpp b/Graphics/src/ParticleSystem.cpp index 5edd6cc57..94fae7c1b 100644 --- a/Graphics/src/ParticleSystem.cpp +++ b/Graphics/src/ParticleSystem.cpp @@ -7,307 +7,307 @@ namespace Graphics { - struct ParticleVertex : VertexFormat - { - ParticleVertex(Vector3 pos, Color color, Vector4 params) : pos(pos), color(color), params(params) {}; - Vector3 pos; - Color color; - // X = scale - // Y = rotation - // Z = animation frame - Vector4 params; - }; - - class ParticleSystem_Impl : public ParticleSystemRes - { - friend class ParticleEmitter; - Vector> m_emitters; - - public: - OpenGL* gl; - - public: - virtual void Render(const class RenderState& rs, float deltaTime) override - { - // Enable blending for all particles - glEnable(GL_BLEND); - - // Tick all emitters and remove old ones - for(auto it = m_emitters.begin(); it != m_emitters.end();) - { - (*it)->Render(rs, deltaTime); - - if(it->use_count() == 1) - { - if((*it)->HasFinished()) - { - // Remove unreferenced and finished emitters - it = m_emitters.erase(it); - continue; - } - else if((*it)->loops == 0) - { - // Deactivate unreferenced infinte duration emitters - (*it)->Deactivate(); - } - } - - it++; - } - } - Ref AddEmitter() override - { - Ref newEmitter = Utility::MakeRef(new ParticleEmitter(this)); - m_emitters.Add(newEmitter); - return newEmitter; - } - void Reset() override - { - for(auto em : m_emitters) - { - em.reset(); - } - m_emitters.clear(); - } - }; - - Ref ParticleSystemRes::Create(class OpenGL* gl) - { - ParticleSystem_Impl* impl = new ParticleSystem_Impl(); - impl->gl = gl; - return GetResourceManager().Register(impl); - } - - - // Particle instance class - class Particle - { - public: - float life = 0.0f; - float maxLife = 0.0f; - float rotation = 0.0f; - float startSize = 0.0f; - Color startColor; - Vector3 pos; - Vector3 velocity; - float scale; - float fade; - float drag; - - - bool IsAlive() const - { - return life > 0.0f; - } - inline void Init(ParticleEmitter* emitter) - { - const float& et = emitter->m_emitterRate; - life = maxLife = emitter->m_param_Lifetime->Init(et); - pos = emitter->m_param_StartPosition->Init(et) * emitter->scale; - - // Velocity of startvelocity and spawn offset scale - velocity = emitter->m_param_StartVelocity->Init(et) * emitter->scale; - float spawnVelScale = emitter->m_param_SpawnVelocityScale->Init(et); - if(spawnVelScale > 0) - velocity += pos.Normalized() * spawnVelScale * emitter->scale; - - // Add emitter offset to location - pos += emitter->position; - - startColor = emitter->m_param_StartColor->Init(et); - rotation = emitter->m_param_StartRotation->Init(et); - startSize = emitter->m_param_StartSize->Init(et) * emitter->scale; - drag = emitter->m_param_StartDrag->Init(et); - } - inline void Simulate(ParticleEmitter* emitter, float deltaTime) - { - float c = 1 - life / maxLife; - - // Add gravity - velocity += emitter->m_param_Gravity->Sample(emitter->m_emitterTime) * deltaTime * emitter->scale; - pos += velocity * deltaTime; - - // Add drag - velocity += -velocity * deltaTime * drag; - - fade = emitter->m_param_FadeOverTime->Sample(c); - scale = emitter->m_param_ScaleOverTime->Sample(c); - life -= deltaTime; - } - }; - - ParticleEmitter::ParticleEmitter(ParticleSystem_Impl* sys) : m_system(sys) - { - m_mesh = MeshRes::Create(m_system->gl); - m_mesh->SetPrimitiveType(PrimitiveType::PointList); - - // Set parameter defaults -#define PARTICLE_DEFAULT(__name, __value)\ + struct ParticleVertex : VertexFormat + { + ParticleVertex(Vector3 pos, Color color, Vector4 params) : pos(pos), color(color), params(params) {}; + Vector3 pos; + Color color; + // X = scale + // Y = rotation + // Z = animation frame + Vector4 params; + }; + + class ParticleSystem_Impl : public ParticleSystemRes + { + friend class ParticleEmitter; + Vector> m_emitters; + + public: + OpenGL* gl; + + public: + virtual void Render(const class RenderState& rs, float deltaTime) override + { + // Enable blending for all particles + glEnable(GL_BLEND); + + // Tick all emitters and remove old ones + for (auto it = m_emitters.begin(); it != m_emitters.end();) + { + (*it)->Render(rs, deltaTime); + + if (it->use_count() == 1) + { + if ((*it)->HasFinished()) + { + // Remove unreferenced and finished emitters + it = m_emitters.erase(it); + continue; + } + else if ((*it)->loops == 0) + { + // Deactivate unreferenced infinte duration emitters + (*it)->Deactivate(); + } + } + + it++; + } + } + Ref AddEmitter() override + { + Ref newEmitter = Utility::MakeRef(new ParticleEmitter(this)); + m_emitters.Add(newEmitter); + return newEmitter; + } + void Reset() override + { + for (auto em : m_emitters) + { + em.reset(); + } + m_emitters.clear(); + } + }; + + Ref ParticleSystemRes::Create(class OpenGL* gl) + { + ParticleSystem_Impl* impl = new ParticleSystem_Impl(); + impl->gl = gl; + return GetResourceManager().Register(impl); + } + + // Particle instance class + class Particle + { + public: + float life = 0.0f; + float maxLife = 0.0f; + float rotation = 0.0f; + float startSize = 0.0f; + Color startColor; + Vector3 pos; + Vector3 velocity; + float scale; + float fade; + float drag; + + bool IsAlive() const + { + return life > 0.0f; + } + inline void Init(ParticleEmitter* emitter) + { + const float& et = emitter->m_emitterRate; + life = maxLife = emitter->m_param_Lifetime->Init(et); + pos = emitter->m_param_StartPosition->Init(et) * emitter->scale; + + // Velocity of startvelocity and spawn offset scale + velocity = emitter->m_param_StartVelocity->Init(et) * emitter->scale; + float spawnVelScale = emitter->m_param_SpawnVelocityScale->Init(et); + if (spawnVelScale > 0) + velocity += pos.Normalized() * spawnVelScale * emitter->scale; + + // Add emitter offset to location + pos += emitter->position; + + startColor = emitter->m_param_StartColor->Init(et); + rotation = emitter->m_param_StartRotation->Init(et); + startSize = emitter->m_param_StartSize->Init(et) * emitter->scale; + drag = emitter->m_param_StartDrag->Init(et); + } + inline void Simulate(ParticleEmitter* emitter, float deltaTime) + { + float c = 1 - life / maxLife; + + // Add gravity + velocity += emitter->m_param_Gravity->Sample(emitter->m_emitterTime) * deltaTime * emitter->scale; + pos += velocity * deltaTime; + + // Add drag + velocity += -velocity * deltaTime * drag; + + fade = emitter->m_param_FadeOverTime->Sample(c); + scale = emitter->m_param_ScaleOverTime->Sample(c); + life -= deltaTime; + } + }; + + ParticleEmitter::ParticleEmitter(ParticleSystem_Impl* sys) : m_system(sys) + { + m_mesh = MeshRes::Create(m_system->gl); + m_mesh->SetPrimitiveType(PrimitiveType::PointList); + + // Set parameter defaults +#define PARTICLE_DEFAULT(__name, __value) \ Set##__name(__value); #include "ParticleParameters.hpp" + } + ParticleEmitter::~ParticleEmitter() + { + // Cleanup particle parameters +#define PARTICLE_PARAMETER(__name, __type) \ + if (m_param_##__name) \ + { \ + delete m_param_##__name; \ + m_param_##__name = nullptr; \ } - ParticleEmitter::~ParticleEmitter() - { - // Cleanup particle parameters -#define PARTICLE_PARAMETER(__name, __type)\ - if(m_param_##__name){\ - delete m_param_##__name; m_param_##__name = nullptr; } #include "ParticleParameters.hpp" - if(m_particles) - { - delete[] m_particles; - } - } - - void ParticleEmitter::m_ReallocatePool(uint32 newCapacity) - { - Particle* oldParticles = m_particles; - uint32 oldSize = m_poolSize; - - // Create new pool - m_poolSize = newCapacity; - if(newCapacity > 0) - { - m_particles = new Particle[m_poolSize]; - for (size_t i = 0; i < m_poolSize; i++) - { - m_particles[i] = Particle(); - } - - } - else - { - m_particles = nullptr; - } - - if(oldParticles && m_particles) - { - memcpy(m_particles, oldParticles, Math::Min(oldSize, m_poolSize) * sizeof(Particle)); - } - - if(oldParticles) - delete[] oldParticles; - } - void ParticleEmitter::Render(const class RenderState& rs, float deltaTime) - { - if(m_finished) - return; - - uint32 maxDuration = (uint32)ceilf(m_param_Lifetime->GetMax()); - uint32 maxSpawns = (uint32)ceilf(m_param_SpawnRate->GetMax()); - uint32 maxParticles = maxSpawns * maxDuration; - // Round up to 64 - maxParticles = (uint32)ceil((float)maxParticles / 64.0f) * 64; - - if(maxParticles > m_poolSize) - m_ReallocatePool(maxParticles); - - // Resulting vertex bufffer - Vector verts; - - // Increment emitter time - m_emitterTime += deltaTime; - while(m_emitterTime > duration) - { - m_emitterTime -= duration; - m_emitterLoopIndex++; - } - m_emitterRate = m_emitterTime / duration; - - // Increment spawn counter - m_spawnCounter += deltaTime * m_param_SpawnRate->Sample(m_emitterRate); - - uint32 numSpawns = 0; - float spawnTimeOffset = 0.0f; - float spawnTimeOffsetStep = 0; - if(loops > 0 && m_emitterLoopIndex >= loops) // Should spawn particles ? - m_deactivated = true; - - if(!m_deactivated) - { - // Calculate number of new particles to spawn - float spawnsf; - m_spawnCounter = modff(m_spawnCounter, &spawnsf); - numSpawns = (uint32)spawnsf; - spawnTimeOffsetStep = deltaTime / spawnsf; - } - - bool updatedSomething = false; - for(uint32 i = 0; i < m_poolSize; i++) - { - Particle& p = m_particles[i]; - - bool render = false; - if(!m_particles[i].IsAlive()) - { - // Try to spawn a new particle in this slot - if(numSpawns > 0) - { - p.Init(this); - p.Simulate(this, spawnTimeOffset); - spawnTimeOffset += spawnTimeOffsetStep; - numSpawns--; - render = true; - } - } - else - { - p.Simulate(this, deltaTime); - render = true; - updatedSomething = true; - } - - if(render) - { - verts.Add({ p.pos, p.startColor.WithAlpha(p.fade), Vector4(p.startSize * p.scale, p.rotation, 0, 0) }); - } - } - - if(m_deactivated) - { - m_finished = !updatedSomething; - } - - MaterialParameterSet params; - if(texture) - { - params.SetParameter("mainTex", texture); - } - material->Bind(rs, params); - - // Select blending mode based on material - switch(material->blendMode) - { - case MaterialBlendMode::Normal: - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case MaterialBlendMode::Additive: - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - case MaterialBlendMode::Multiply: - glBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR); - break; - } - - m_mesh->SetData(verts); - m_mesh->Draw(); - } - - void ParticleEmitter::Reset() - { - m_deactivated = false; - m_finished = false; - delete[] m_particles; - m_particles = nullptr; - m_emitterLoopIndex = 0; - m_emitterTime = 0; - m_spawnCounter = 0; - m_poolSize = 0; - } - - void ParticleEmitter::Deactivate() - { - m_deactivated = true; - } + if (m_particles) + { + delete[] m_particles; + } + } + + void ParticleEmitter::m_ReallocatePool(uint32 newCapacity) + { + Particle* oldParticles = m_particles; + uint32 oldSize = m_poolSize; + + // Create new pool + m_poolSize = newCapacity; + if (newCapacity > 0) + { + m_particles = new Particle[m_poolSize]; + for (size_t i = 0; i < m_poolSize; i++) + { + m_particles[i] = Particle(); + } + } + else + { + m_particles = nullptr; + } + + if (oldParticles && m_particles) + { + memcpy(m_particles, oldParticles, Math::Min(oldSize, m_poolSize) * sizeof(Particle)); + } + + if (oldParticles) + delete[] oldParticles; + } + void ParticleEmitter::Render(const class RenderState& rs, float deltaTime) + { + if (m_finished) + return; + + uint32 maxDuration = (uint32)ceilf(m_param_Lifetime->GetMax()); + uint32 maxSpawns = (uint32)ceilf(m_param_SpawnRate->GetMax()); + uint32 maxParticles = maxSpawns * maxDuration; + // Round up to 64 + maxParticles = (uint32)ceil((float)maxParticles / 64.0f) * 64; + + if (maxParticles > m_poolSize) + m_ReallocatePool(maxParticles); + + // Resulting vertex bufffer + Vector verts; + + // Increment emitter time + m_emitterTime += deltaTime; + while (m_emitterTime > duration) + { + m_emitterTime -= duration; + m_emitterLoopIndex++; + } + m_emitterRate = m_emitterTime / duration; + + // Increment spawn counter + m_spawnCounter += deltaTime * m_param_SpawnRate->Sample(m_emitterRate); + + uint32 numSpawns = 0; + float spawnTimeOffset = 0.0f; + float spawnTimeOffsetStep = 0; + if (loops > 0 && m_emitterLoopIndex >= loops) // Should spawn particles ? + m_deactivated = true; + + if (!m_deactivated) + { + // Calculate number of new particles to spawn + float spawnsf; + m_spawnCounter = modff(m_spawnCounter, &spawnsf); + numSpawns = (uint32)spawnsf; + spawnTimeOffsetStep = deltaTime / spawnsf; + } + + bool updatedSomething = false; + for (uint32 i = 0; i < m_poolSize; i++) + { + Particle& p = m_particles[i]; + + bool render = false; + if (!m_particles[i].IsAlive()) + { + // Try to spawn a new particle in this slot + if (numSpawns > 0) + { + p.Init(this); + p.Simulate(this, spawnTimeOffset); + spawnTimeOffset += spawnTimeOffsetStep; + numSpawns--; + render = true; + } + } + else + { + p.Simulate(this, deltaTime); + render = true; + updatedSomething = true; + } + + if (render) + { + verts.Add({ p.pos, p.startColor.WithAlpha(p.fade), Vector4(p.startSize * p.scale, p.rotation, 0, 0) }); + } + } + + if (m_deactivated) + { + m_finished = !updatedSomething; + } + + MaterialParameterSet params; + if (texture) + { + params.SetParameter("mainTex", texture); + } + material->Bind(rs, params); + + // Select blending mode based on material + switch (material->blendMode) + { + case MaterialBlendMode::Normal: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case MaterialBlendMode::Additive: + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case MaterialBlendMode::Multiply: + glBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR); + break; + } + + m_mesh->SetData(verts); + m_mesh->Draw(); + } + + void ParticleEmitter::Reset() + { + m_deactivated = false; + m_finished = false; + delete[] m_particles; + m_particles = nullptr; + m_emitterLoopIndex = 0; + m_emitterTime = 0; + m_spawnCounter = 0; + m_poolSize = 0; + } + + void ParticleEmitter::Deactivate() + { + m_deactivated = true; + } } diff --git a/Graphics/src/RenderQueue.cpp b/Graphics/src/RenderQueue.cpp index 48914d011..a651f8cee 100644 --- a/Graphics/src/RenderQueue.cpp +++ b/Graphics/src/RenderQueue.cpp @@ -5,258 +5,258 @@ using Utility::Cast; namespace Graphics { - RenderQueue::RenderQueue(OpenGL* ogl, const RenderState& rs) - { - m_ogl = ogl; - m_renderState = rs; - } - RenderQueue::RenderQueue(RenderQueue&& other) - { - m_ogl = other.m_ogl; - other.m_ogl = nullptr; - m_orderedCommands = move(other.m_orderedCommands); - m_renderState = other.m_renderState; - } - RenderQueue& RenderQueue::operator=(RenderQueue&& other) - { - Clear(); - m_ogl = other.m_ogl; - other.m_ogl = nullptr; - m_orderedCommands = move(other.m_orderedCommands); - m_renderState = other.m_renderState; - return *this; - } - RenderQueue::~RenderQueue() - { - Clear(); - } - void RenderQueue::Process(bool clearQueue) - { - assert(m_ogl); + RenderQueue::RenderQueue(OpenGL* ogl, const RenderState& rs) + { + m_ogl = ogl; + m_renderState = rs; + } + RenderQueue::RenderQueue(RenderQueue&& other) + { + m_ogl = other.m_ogl; + other.m_ogl = nullptr; + m_orderedCommands = move(other.m_orderedCommands); + m_renderState = other.m_renderState; + } + RenderQueue& RenderQueue::operator=(RenderQueue&& other) + { + Clear(); + m_ogl = other.m_ogl; + other.m_ogl = nullptr; + m_orderedCommands = move(other.m_orderedCommands); + m_renderState = other.m_renderState; + return *this; + } + RenderQueue::~RenderQueue() + { + Clear(); + } + void RenderQueue::Process(bool clearQueue) + { + assert(m_ogl); - bool scissorEnabled = false; - bool blendEnabled = false; - MaterialBlendMode activeBlendMode = (MaterialBlendMode)-1; + bool scissorEnabled = false; + bool blendEnabled = false; + MaterialBlendMode activeBlendMode = (MaterialBlendMode)-1; - Set initializedShaders; - Mesh currentMesh; - Material currentMaterial; + Set initializedShaders; + Mesh currentMesh; + Material currentMaterial; - // Create a new list of items - for(RenderQueueItem* item : m_orderedCommands) - { - auto SetupMaterial = [&](Material& mat, MaterialParameterSet& params) - { - // Only bind params if material is already bound to context - if(currentMaterial == mat) - mat->BindParameters(params, m_renderState.worldTransform); - else - { - if(initializedShaders.Contains(mat)) - { - // Only bind params and rebind - mat->BindParameters(params, m_renderState.worldTransform); - mat->BindToContext(); - currentMaterial = mat; - } - else - { - mat->Bind(m_renderState, params); - initializedShaders.Add(mat); - currentMaterial = mat; - } - } + // Create a new list of items + for (RenderQueueItem* item : m_orderedCommands) + { + auto SetupMaterial = [&](Material& mat, MaterialParameterSet& params) + { + // Only bind params if material is already bound to context + if (currentMaterial == mat) + mat->BindParameters(params, m_renderState.worldTransform); + else + { + if (initializedShaders.Contains(mat)) + { + // Only bind params and rebind + mat->BindParameters(params, m_renderState.worldTransform); + mat->BindToContext(); + currentMaterial = mat; + } + else + { + mat->Bind(m_renderState, params); + initializedShaders.Add(mat); + currentMaterial = mat; + } + } - // Setup Render state for transparent object - if(mat->opaque) - { - if(blendEnabled) - { - glDisable(GL_BLEND); - blendEnabled = false; - } - } - else - { - if(!blendEnabled) - { - glEnable(GL_BLEND); - blendEnabled = true; - } - if(activeBlendMode != mat->blendMode) - { - switch(mat->blendMode) - { - case MaterialBlendMode::Normal: - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE); - break; - case MaterialBlendMode::Additive: - glBlendFunc(GL_ONE, GL_ONE); - break; - case MaterialBlendMode::Multiply: - glBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR); - break; - } - } - } - }; + // Setup Render state for transparent object + if (mat->opaque) + { + if (blendEnabled) + { + glDisable(GL_BLEND); + blendEnabled = false; + } + } + else + { + if (!blendEnabled) + { + glEnable(GL_BLEND); + blendEnabled = true; + } + if (activeBlendMode != mat->blendMode) + { + switch (mat->blendMode) + { + case MaterialBlendMode::Normal: + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE); + break; + case MaterialBlendMode::Additive: + glBlendFunc(GL_ONE, GL_ONE); + break; + case MaterialBlendMode::Multiply: + glBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR); + break; + } + } + } + }; - // Draw mesh helper - auto DrawOrRedrawMesh = [&](Mesh& mesh) - { - if(currentMesh == mesh) - mesh->Redraw(); - else - { - mesh->Draw(); - currentMesh = mesh; - } - }; + // Draw mesh helper + auto DrawOrRedrawMesh = [&](Mesh& mesh) + { + if (currentMesh == mesh) + mesh->Redraw(); + else + { + mesh->Draw(); + currentMesh = mesh; + } + }; - if(Cast(item)) - { - SimpleDrawCall* sdc = (SimpleDrawCall*)item; - m_renderState.worldTransform = sdc->worldTransform; - SetupMaterial(sdc->mat, sdc->params); + if (Cast(item)) + { + SimpleDrawCall* sdc = (SimpleDrawCall*)item; + m_renderState.worldTransform = sdc->worldTransform; + SetupMaterial(sdc->mat, sdc->params); - // Check if scissor is enabled - bool useScissor = (sdc->scissorRect.size.x >= 0); - if(useScissor) - { - // Apply scissor - if(!scissorEnabled) - { - glEnable(GL_SCISSOR_TEST); - scissorEnabled = true; - } - float scissorY = m_renderState.viewportSize.y - sdc->scissorRect.Bottom(); - glScissor((int32)sdc->scissorRect.Left(), (int32)scissorY, - (int32)sdc->scissorRect.size.x, (int32)sdc->scissorRect.size.y); - } - else - { - if(scissorEnabled) - { - glDisable(GL_SCISSOR_TEST); - scissorEnabled = false; - } - } + // Check if scissor is enabled + bool useScissor = (sdc->scissorRect.size.x >= 0); + if (useScissor) + { + // Apply scissor + if (!scissorEnabled) + { + glEnable(GL_SCISSOR_TEST); + scissorEnabled = true; + } + float scissorY = m_renderState.viewportSize.y - sdc->scissorRect.Bottom(); + glScissor((int32)sdc->scissorRect.Left(), (int32)scissorY, + (int32)sdc->scissorRect.size.x, (int32)sdc->scissorRect.size.y); + } + else + { + if (scissorEnabled) + { + glDisable(GL_SCISSOR_TEST); + scissorEnabled = false; + } + } - DrawOrRedrawMesh(sdc->mesh); - #ifdef EMBEDDED - glUseProgram(0); - #endif - } - else if(Cast(item)) - { - if(scissorEnabled) - { - // Disable scissor - glDisable(GL_SCISSOR_TEST); - scissorEnabled = false; - } + DrawOrRedrawMesh(sdc->mesh); +#ifdef EMBEDDED + glUseProgram(0); +#endif + } + else if (Cast(item)) + { + if (scissorEnabled) + { + // Disable scissor + glDisable(GL_SCISSOR_TEST); + scissorEnabled = false; + } - PointDrawCall* pdc = (PointDrawCall*)item; - m_renderState.worldTransform = Transform(); - SetupMaterial(pdc->mat, pdc->params); - PrimitiveType pt = pdc->mesh->GetPrimitiveType(); - if(pt >= PrimitiveType::LineList && pt <= PrimitiveType::LineStrip) - { - glLineWidth(pdc->size); - } - else - { - #ifndef EMBEDDED - glPointSize(pdc->size); - #endif - } - - DrawOrRedrawMesh(pdc->mesh); - #ifdef EMBEDDED - glUseProgram(0); - #endif - } - } + PointDrawCall* pdc = (PointDrawCall*)item; + m_renderState.worldTransform = Transform(); + SetupMaterial(pdc->mat, pdc->params); + PrimitiveType pt = pdc->mesh->GetPrimitiveType(); + if (pt >= PrimitiveType::LineList && pt <= PrimitiveType::LineStrip) + { + glLineWidth(pdc->size); + } + else + { +#ifndef EMBEDDED + glPointSize(pdc->size); +#endif + } - // Disable all states that were on - glDisable(GL_BLEND); - glDisable(GL_SCISSOR_TEST); + DrawOrRedrawMesh(pdc->mesh); +#ifdef EMBEDDED + glUseProgram(0); +#endif + } + } - if(clearQueue) - { - Clear(); - } - } + // Disable all states that were on + glDisable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); - void RenderQueue::Clear() - { - // Cleanup the list of items - for(RenderQueueItem* item : m_orderedCommands) - { - delete item; - } - m_orderedCommands.clear(); - } + if (clearQueue) + { + Clear(); + } + } - void RenderQueue::Draw(Transform worldTransform, Mesh m, Material mat, const MaterialParameterSet& params) - { - SimpleDrawCall* sdc = new SimpleDrawCall(); - sdc->mat = mat; - sdc->mesh = m; - sdc->params = params; - sdc->worldTransform = worldTransform; - m_orderedCommands.push_back(sdc); - } - void RenderQueue::Draw(Transform worldTransform, Ref text, Material mat, const MaterialParameterSet& params) - { - SimpleDrawCall* sdc = new SimpleDrawCall(); - sdc->mat = mat; - sdc->mesh = text->GetMesh(); - sdc->params = params; - // Set Font texture map - sdc->params.SetParameter("mainTex", text->GetTexture()); - sdc->worldTransform = worldTransform; - m_orderedCommands.push_back(sdc); - } + void RenderQueue::Clear() + { + // Cleanup the list of items + for (RenderQueueItem* item : m_orderedCommands) + { + delete item; + } + m_orderedCommands.clear(); + } - void RenderQueue::DrawScissored(Rect scissor, Transform worldTransform, Mesh m, Material mat, const MaterialParameterSet& params /*= MaterialParameterSet()*/) - { - SimpleDrawCall* sdc = new SimpleDrawCall(); - sdc->mat = mat; - sdc->mesh = m; - sdc->params = params; - sdc->worldTransform = worldTransform; - sdc->scissorRect = scissor; - m_orderedCommands.push_back(sdc); - } - void RenderQueue::DrawScissored(Rect scissor, Transform worldTransform, Ref text, Material mat, const MaterialParameterSet& params /*= MaterialParameterSet()*/) - { - SimpleDrawCall* sdc = new SimpleDrawCall(); - sdc->mat = mat; - sdc->mesh = text->GetMesh(); - sdc->params = params; - // Set Font texture map - sdc->params.SetParameter("mainTex", text->GetTexture()); - sdc->params.SetParameter("mapSize", text->GetTexture()->GetSize()); - sdc->worldTransform = worldTransform; - sdc->scissorRect = scissor; - m_orderedCommands.push_back(sdc); - } + void RenderQueue::Draw(Transform worldTransform, Mesh m, Material mat, const MaterialParameterSet& params) + { + SimpleDrawCall* sdc = new SimpleDrawCall(); + sdc->mat = mat; + sdc->mesh = m; + sdc->params = params; + sdc->worldTransform = worldTransform; + m_orderedCommands.push_back(sdc); + } + void RenderQueue::Draw(Transform worldTransform, Ref text, Material mat, const MaterialParameterSet& params) + { + SimpleDrawCall* sdc = new SimpleDrawCall(); + sdc->mat = mat; + sdc->mesh = text->GetMesh(); + sdc->params = params; + // Set Font texture map + sdc->params.SetParameter("mainTex", text->GetTexture()); + sdc->worldTransform = worldTransform; + m_orderedCommands.push_back(sdc); + } - void RenderQueue::DrawPoints(Mesh m, Material mat, const MaterialParameterSet& params, float pointSize) - { - PointDrawCall* pdc = new PointDrawCall(); - pdc->mat = mat; - pdc->mesh = m; - pdc->params = params; - pdc->size = pointSize; - m_orderedCommands.push_back(pdc); - } + void RenderQueue::DrawScissored(Rect scissor, Transform worldTransform, Mesh m, Material mat, const MaterialParameterSet& params /*= MaterialParameterSet()*/) + { + SimpleDrawCall* sdc = new SimpleDrawCall(); + sdc->mat = mat; + sdc->mesh = m; + sdc->params = params; + sdc->worldTransform = worldTransform; + sdc->scissorRect = scissor; + m_orderedCommands.push_back(sdc); + } + void RenderQueue::DrawScissored(Rect scissor, Transform worldTransform, Ref text, Material mat, const MaterialParameterSet& params /*= MaterialParameterSet()*/) + { + SimpleDrawCall* sdc = new SimpleDrawCall(); + sdc->mat = mat; + sdc->mesh = text->GetMesh(); + sdc->params = params; + // Set Font texture map + sdc->params.SetParameter("mainTex", text->GetTexture()); + sdc->params.SetParameter("mapSize", text->GetTexture()->GetSize()); + sdc->worldTransform = worldTransform; + sdc->scissorRect = scissor; + m_orderedCommands.push_back(sdc); + } - // Initializes the simple draw call structure - SimpleDrawCall::SimpleDrawCall() - : scissorRect(Vector2(), Vector2(-1)) - { - } + void RenderQueue::DrawPoints(Mesh m, Material mat, const MaterialParameterSet& params, float pointSize) + { + PointDrawCall* pdc = new PointDrawCall(); + pdc->mat = mat; + pdc->mesh = m; + pdc->params = params; + pdc->size = pointSize; + m_orderedCommands.push_back(pdc); + } + + // Initializes the simple draw call structure + SimpleDrawCall::SimpleDrawCall() + : scissorRect(Vector2(), Vector2(-1)) + { + } } diff --git a/Graphics/src/ResourceManagers.cpp b/Graphics/src/ResourceManagers.cpp index 125abde43..f1d7fa254 100644 --- a/Graphics/src/ResourceManagers.cpp +++ b/Graphics/src/ResourceManagers.cpp @@ -5,80 +5,80 @@ namespace Graphics { - static Timer gcTimer; - static Timer cleanupTimer; - static int disabled = 0; + static Timer gcTimer; + static Timer cleanupTimer; + static int disabled = 0; - static ResourceManagers inst; - static IResourceManager* managers[(size_t)ResourceType::_Length] = { nullptr }; + static ResourceManagers inst; + static IResourceManager* managers[(size_t)ResourceType::_Length] = { nullptr }; - ResourceManagers::ResourceManagers() - { - // Create common resource managers - CreateResourceManager(); - CreateResourceManager(); - } - ResourceManagers::~ResourceManagers() - { - for(size_t i = 0; i < (size_t)ResourceType::_Length; i++) - { - if(managers[i]) - { - delete managers[i]; - managers[i] = nullptr; - } - } - } + ResourceManagers::ResourceManagers() + { + // Create common resource managers + CreateResourceManager(); + CreateResourceManager(); + } + ResourceManagers::~ResourceManagers() + { + for (size_t i = 0; i < (size_t)ResourceType::_Length; i++) + { + if (managers[i]) + { + delete managers[i]; + managers[i] = nullptr; + } + } + } - void ResourceManagers::DestroyResourceManager(ResourceType type) - { - size_t idx = (size_t)type; - if(managers[idx]) - { - managers[idx]->ReleaseAll(); + void ResourceManagers::DestroyResourceManager(ResourceType type) + { + size_t idx = (size_t)type; + if (managers[idx]) + { + managers[idx]->ReleaseAll(); - delete managers[idx]; - managers[idx] = nullptr; - } - } - void ResourceManagers::AssignResourceManager(ResourceType type, IResourceManager* mgr) - { - size_t idx = (size_t)type; - assert(managers[idx] == nullptr); - managers[idx] = mgr; - } + delete managers[idx]; + managers[idx] = nullptr; + } + } + void ResourceManagers::AssignResourceManager(ResourceType type, IResourceManager* mgr) + { + size_t idx = (size_t)type; + assert(managers[idx] == nullptr); + managers[idx] = mgr; + } - IResourceManager* ResourceManagers::GetResourceManager(ResourceType type) - { - size_t idx = (size_t)type; - assert(managers[idx] != nullptr); - return managers[idx]; - } + IResourceManager* ResourceManagers::GetResourceManager(ResourceType type) + { + size_t idx = (size_t)type; + assert(managers[idx] != nullptr); + return managers[idx]; + } - void ResourceManagers::SuspendGC() - { - disabled++; - } - void ResourceManagers::ContinueGC() - { - disabled--; - if(disabled < 0) - disabled = 0; - } - void ResourceManagers::TickAll() - { - inst.m_TickAll(); - } - void ResourceManagers::m_TickAll() - { - if(gcTimer.Milliseconds() > 250 && disabled == 0) - { - for(auto rm : managers) - { - if(rm) - rm->GarbageCollect(); - } - gcTimer.Restart(); - } - } + void ResourceManagers::SuspendGC() + { + disabled++; + } + void ResourceManagers::ContinueGC() + { + disabled--; + if (disabled < 0) + disabled = 0; + } + void ResourceManagers::TickAll() + { + inst.m_TickAll(); + } + void ResourceManagers::m_TickAll() + { + if (gcTimer.Milliseconds() > 250 && disabled == 0) + { + for (auto rm : managers) + { + if (rm) + rm->GarbageCollect(); + } + gcTimer.Restart(); + } + } } diff --git a/Graphics/src/Shader.cpp b/Graphics/src/Shader.cpp index dd31dcfb1..fc8d6bc5e 100644 --- a/Graphics/src/Shader.cpp +++ b/Graphics/src/Shader.cpp @@ -6,298 +6,298 @@ namespace Graphics { #ifdef EMBEDDED - const uint32 typeMap[] = - { - GL_VERTEX_SHADER, - GL_FRAGMENT_SHADER, - }; + const uint32 typeMap[] = + { + GL_VERTEX_SHADER, + GL_FRAGMENT_SHADER, + }; #else - const uint32 typeMap[] = - { - GL_VERTEX_SHADER, - GL_FRAGMENT_SHADER, - GL_GEOMETRY_SHADER, - }; - const uint32 shaderStageMap[] = - { - GL_VERTEX_SHADER_BIT, - GL_FRAGMENT_SHADER_BIT, - GL_GEOMETRY_SHADER_BIT, - }; + const uint32 typeMap[] = + { + GL_VERTEX_SHADER, + GL_FRAGMENT_SHADER, + GL_GEOMETRY_SHADER, + }; + const uint32 shaderStageMap[] = + { + GL_VERTEX_SHADER_BIT, + GL_FRAGMENT_SHADER_BIT, + GL_GEOMETRY_SHADER_BIT, + }; #endif - class Shader_Impl : public ShaderRes - { - ShaderType m_type; - uint32 m_prog; - OpenGL* m_gl; + class Shader_Impl : public ShaderRes + { + ShaderType m_type; + uint32 m_prog; + OpenGL* m_gl; - String m_sourcePath; + String m_sourcePath; - // Hot Reload detection on windows + // Hot Reload detection on windows #ifdef _WIN32 - HANDLE m_changeNotification = INVALID_HANDLE_VALUE; - uint64 m_lwt = -1; + HANDLE m_changeNotification = INVALID_HANDLE_VALUE; + uint64 m_lwt = -1; #endif - public: - Shader_Impl(OpenGL* gl) : m_gl(gl) - { - } - ~Shader_Impl() - { - // Cleanup OpenGL resource - if(glIsProgram(m_prog)) - { - glDeleteProgram(m_prog); - } + public: + Shader_Impl(OpenGL* gl) : m_gl(gl) + { + } + ~Shader_Impl() + { + // Cleanup OpenGL resource + if (glIsProgram(m_prog)) + { + glDeleteProgram(m_prog); + } #ifdef _WIN32 - // Close change notification handle - if(m_changeNotification != INVALID_HANDLE_VALUE) - { - CloseHandle(m_changeNotification); - } + // Close change notification handle + if (m_changeNotification != INVALID_HANDLE_VALUE) + { + CloseHandle(m_changeNotification); + } #endif - } - void SetupChangeHandler() - { + } + void SetupChangeHandler() + { #ifdef _WIN32 - if(m_changeNotification != INVALID_HANDLE_VALUE) - { - CloseHandle(m_changeNotification); - m_changeNotification = INVALID_HANDLE_VALUE; - } + if (m_changeNotification != INVALID_HANDLE_VALUE) + { + CloseHandle(m_changeNotification); + m_changeNotification = INVALID_HANDLE_VALUE; + } - WString rootFolder = Utility::ConvertToWString(Path::RemoveLast(m_sourcePath)); - m_changeNotification = FindFirstChangeNotificationW(*rootFolder, false, FILE_NOTIFY_CHANGE_LAST_WRITE); + WString rootFolder = Utility::ConvertToWString(Path::RemoveLast(m_sourcePath)); + m_changeNotification = FindFirstChangeNotificationW(*rootFolder, false, FILE_NOTIFY_CHANGE_LAST_WRITE); #endif - } + } #ifdef EMBEDDED - bool LoadProgram(uint32& programOut) - { - File in; - if(!in.OpenRead(m_sourcePath)) - return false; + bool LoadProgram(uint32& programOut) + { + File in; + if (!in.OpenRead(m_sourcePath)) + return false; - String sourceStr; - sourceStr.resize(in.GetSize()); - if(sourceStr.size() == 0) - return false; + String sourceStr; + sourceStr.resize(in.GetSize()); + if (sourceStr.size() == 0) + return false; - in.Read(&sourceStr.front(), sourceStr.size()); - sourceStr = "#version 100\n#define EMBEDDED\n#define target gl_FragColor\n#define texture texture2D\nprecision mediump float;\n" + sourceStr; - const GLint programsize = sourceStr.size(); + in.Read(&sourceStr.front(), sourceStr.size()); + sourceStr = "#version 100\n#define EMBEDDED\n#define target gl_FragColor\n#define texture texture2D\nprecision mediump float;\n" + sourceStr; + const GLint programsize = sourceStr.size(); - const char* pChars = *sourceStr; - glShaderSource(programOut, 1, &pChars, &programsize); - glCompileShader(programOut); + const char* pChars = *sourceStr; + glShaderSource(programOut, 1, &pChars, &programsize); + glCompileShader(programOut); - int nStatus = 0; - glGetShaderiv(programOut, GL_COMPILE_STATUS, &nStatus); - if(nStatus == GL_FALSE) - { - static char infoLogBuffer[2048]; - int s = 0; - glGetShaderInfoLog(programOut, sizeof(infoLogBuffer), &s, infoLogBuffer); + int nStatus = 0; + glGetShaderiv(programOut, GL_COMPILE_STATUS, &nStatus); + if (nStatus == GL_FALSE) + { + static char infoLogBuffer[2048]; + int s = 0; + glGetShaderInfoLog(programOut, sizeof(infoLogBuffer), &s, infoLogBuffer); - Logf("Shader program compile log for %s: %s", Logger::Severity::Error, m_sourcePath, infoLogBuffer); - return false; - } + Logf("Shader program compile log for %s: %s", Logger::Severity::Error, m_sourcePath, infoLogBuffer); + return false; + } - // Shader hot-reload in debug mode + // Shader hot-reload in debug mode #if defined(_DEBUG) && defined(_WIN32) - // Store last write time - m_lwt = in.GetLastWriteTime(); - SetupChangeHandler(); + // Store last write time + m_lwt = in.GetLastWriteTime(); + SetupChangeHandler(); #endif - return true; - } + return true; + } #else - - bool LoadProgram(uint32& programOut) - { - File in; - if(!in.OpenRead(m_sourcePath)) - return false; - String sourceStr; - sourceStr.resize(in.GetSize()); - if(sourceStr.size() == 0) - return false; + bool LoadProgram(uint32& programOut) + { + File in; + if (!in.OpenRead(m_sourcePath)) + return false; - in.Read(&sourceStr.front(), sourceStr.size()); - String firstLine; - sourceStr.Split("\n", &firstLine, nullptr); - firstLine.Trim('\r'); - firstLine.ToLower(); - if (firstLine.compare("#version 330") != 0) - { - sourceStr = "#version 330\n" + sourceStr; - } - const char* pChars = *sourceStr; - programOut = glCreateShaderProgramv(typeMap[(size_t)m_type], 1, &pChars); - if(programOut == 0) - return false; + String sourceStr; + sourceStr.resize(in.GetSize()); + if (sourceStr.size() == 0) + return false; - int nStatus = 0; - glGetProgramiv(programOut, GL_LINK_STATUS, &nStatus); - if(nStatus == 0) - { - static char infoLogBuffer[2048]; - int s = 0; - glGetProgramInfoLog(programOut, sizeof(infoLogBuffer), &s, infoLogBuffer); + in.Read(&sourceStr.front(), sourceStr.size()); + String firstLine; + sourceStr.Split("\n", &firstLine, nullptr); + firstLine.Trim('\r'); + firstLine.ToLower(); + if (firstLine.compare("#version 330") != 0) + { + sourceStr = "#version 330\n" + sourceStr; + } + const char* pChars = *sourceStr; + programOut = glCreateShaderProgramv(typeMap[(size_t)m_type], 1, &pChars); + if (programOut == 0) + return false; - Logf("Shader program compile log for %s: %s", Logger::Severity::Error, m_sourcePath, infoLogBuffer); - return false; - } + int nStatus = 0; + glGetProgramiv(programOut, GL_LINK_STATUS, &nStatus); + if (nStatus == 0) + { + static char infoLogBuffer[2048]; + int s = 0; + glGetProgramInfoLog(programOut, sizeof(infoLogBuffer), &s, infoLogBuffer); - // Shader hot-reload in debug mode + Logf("Shader program compile log for %s: %s", Logger::Severity::Error, m_sourcePath, infoLogBuffer); + return false; + } + + // Shader hot-reload in debug mode #if defined(_DEBUG) && defined(_WIN32) - // Store last write time - m_lwt = in.GetLastWriteTime(); - SetupChangeHandler(); + // Store last write time + m_lwt = in.GetLastWriteTime(); + SetupChangeHandler(); #endif - return true; - } - + return true; + } + #endif - bool UpdateHotReload() override - { + bool UpdateHotReload() override + { #ifdef _WIN32 - if(m_changeNotification != INVALID_HANDLE_VALUE) - { - if(WaitForSingleObject(m_changeNotification, 0) == WAIT_OBJECT_0) - { - uint64 newLwt = File::GetLastWriteTime(m_sourcePath); - if(newLwt != -1 && newLwt > m_lwt) - { - uint32 newProgram = 0; - if(LoadProgram(newProgram)) - { - // Successfully reloaded - m_prog = newProgram; - return true; - } - } + if (m_changeNotification != INVALID_HANDLE_VALUE) + { + if (WaitForSingleObject(m_changeNotification, 0) == WAIT_OBJECT_0) + { + uint64 newLwt = File::GetLastWriteTime(m_sourcePath); + if (newLwt != -1 && newLwt > m_lwt) + { + uint32 newProgram = 0; + if (LoadProgram(newProgram)) + { + // Successfully reloaded + m_prog = newProgram; + return true; + } + } + + // Watch for new change + SetupChangeHandler(); + } + } +#endif + return false; + } - // Watch for new change - SetupChangeHandler(); - } - } + bool Init(ShaderType type, const String& name) + { + m_sourcePath = Path::Normalize(name); + m_type = type; + +#ifdef EMBEDDED + m_prog = glCreateShader(typeMap[(size_t)type]); #endif - return false; - } - bool Init(ShaderType type, const String& name) - { - m_sourcePath = Path::Normalize(name); - m_type = type; - - #ifdef EMBEDDED - m_prog = glCreateShader(typeMap[(size_t)type]); - #endif - - return LoadProgram(m_prog); - } + return LoadProgram(m_prog); + } #ifndef EMBEDDED - void Bind() override - { - if(m_gl->m_activeShaders[(size_t)m_type] != this) - { - glUseProgramStages(m_gl->m_mainProgramPipeline, shaderStageMap[(size_t)m_type], m_prog); - m_gl->m_activeShaders[(size_t)m_type] = this; - } - } - bool IsBound() const override - { - return m_gl->m_activeShaders[(size_t)m_type] == this; - } - uint32 GetLocation(const String& name) const override - { - return glGetUniformLocation(m_prog, name.c_str()); - } - virtual void BindUniform(uint32 loc, const Transform& mat) - { - glProgramUniformMatrix4fv(m_prog, loc, 1, false, mat.mat); - } - virtual void BindUniformVec2(uint32 loc, const Vector2& v) - { - glProgramUniform2fv(m_prog, loc, 1, &v.x); - } - virtual void BindUniformVec3(uint32 loc, const Vector3& v) - { - glProgramUniform3fv(m_prog, loc, 1, &v.x); - } - virtual void BindUniformVec4(uint32 loc, const Vector4& v) - { - glProgramUniform4fv(m_prog, loc, 1, &v.x); - } - virtual void BindUniform(uint32 loc, int i) - { - glProgramUniform1i(m_prog, loc, i); - } - virtual void BindUniform(uint32 loc, float i) - { - glProgramUniform1f(m_prog, loc, i); - } - virtual void BindUniformArray(uint32 loc, const Transform* mat, size_t count) - { - glProgramUniformMatrix4fv(m_prog, loc, (int)count, false, (float*)mat); - } - virtual void BindUniformArray(uint32 loc, const Vector2* v2, size_t count) - { - glProgramUniform2fv(m_prog, loc, (int)count, (float*)v2); - } - virtual void BindUniformArray(uint32 loc, const Vector3* v3, size_t count) - { - glProgramUniform3fv(m_prog, loc, (int)count, (float*)v3); - } - virtual void BindUniformArray(uint32 loc, const Vector4* v4, size_t count) - { - glProgramUniform4fv(m_prog, loc, (int)count, (float*)v4); - } - virtual void BindUniformArray(uint32 loc, const float* i, size_t count) - { - glProgramUniform1fv(m_prog, loc, (int)count, i); - } - virtual void BindUniformArray(uint32 loc, const int* i, size_t count) - { - glProgramUniform1iv(m_prog, loc, (int)count, i); - } + void Bind() override + { + if (m_gl->m_activeShaders[(size_t)m_type] != this) + { + glUseProgramStages(m_gl->m_mainProgramPipeline, shaderStageMap[(size_t)m_type], m_prog); + m_gl->m_activeShaders[(size_t)m_type] = this; + } + } + bool IsBound() const override + { + return m_gl->m_activeShaders[(size_t)m_type] == this; + } + uint32 GetLocation(const String& name) const override + { + return glGetUniformLocation(m_prog, name.c_str()); + } + virtual void BindUniform(uint32 loc, const Transform& mat) + { + glProgramUniformMatrix4fv(m_prog, loc, 1, false, mat.mat); + } + virtual void BindUniformVec2(uint32 loc, const Vector2& v) + { + glProgramUniform2fv(m_prog, loc, 1, &v.x); + } + virtual void BindUniformVec3(uint32 loc, const Vector3& v) + { + glProgramUniform3fv(m_prog, loc, 1, &v.x); + } + virtual void BindUniformVec4(uint32 loc, const Vector4& v) + { + glProgramUniform4fv(m_prog, loc, 1, &v.x); + } + virtual void BindUniform(uint32 loc, int i) + { + glProgramUniform1i(m_prog, loc, i); + } + virtual void BindUniform(uint32 loc, float i) + { + glProgramUniform1f(m_prog, loc, i); + } + virtual void BindUniformArray(uint32 loc, const Transform* mat, size_t count) + { + glProgramUniformMatrix4fv(m_prog, loc, (int)count, false, (float*)mat); + } + virtual void BindUniformArray(uint32 loc, const Vector2* v2, size_t count) + { + glProgramUniform2fv(m_prog, loc, (int)count, (float*)v2); + } + virtual void BindUniformArray(uint32 loc, const Vector3* v3, size_t count) + { + glProgramUniform3fv(m_prog, loc, (int)count, (float*)v3); + } + virtual void BindUniformArray(uint32 loc, const Vector4* v4, size_t count) + { + glProgramUniform4fv(m_prog, loc, (int)count, (float*)v4); + } + virtual void BindUniformArray(uint32 loc, const float* i, size_t count) + { + glProgramUniform1fv(m_prog, loc, (int)count, i); + } + virtual void BindUniformArray(uint32 loc, const int* i, size_t count) + { + glProgramUniform1iv(m_prog, loc, (int)count, i); + } #endif - virtual uint32 Handle() override - { - return m_prog; - } + virtual uint32 Handle() override + { + return m_prog; + } - String GetOriginalName() const override - { - return m_sourcePath; - } - }; + String GetOriginalName() const override + { + return m_sourcePath; + } + }; - Shader ShaderRes::Create(class OpenGL* gl, ShaderType type, const String& assetPath) - { - Shader_Impl* pImpl = new Shader_Impl(gl); - if(!pImpl->Init(type, assetPath)) - { - delete pImpl; - return Shader(); - } - else - { - return GetResourceManager().Register(pImpl); - } - } - void ShaderRes::Unbind(class OpenGL* gl, ShaderType type) - { - #ifndef EMBEDDED - if(gl->m_activeShaders[(size_t)type] != 0) - { - glUseProgramStages(gl->m_mainProgramPipeline, shaderStageMap[(size_t)type], 0); - gl->m_activeShaders[(size_t)type] = 0; - } - #endif - } + Shader ShaderRes::Create(class OpenGL* gl, ShaderType type, const String& assetPath) + { + Shader_Impl* pImpl = new Shader_Impl(gl); + if (!pImpl->Init(type, assetPath)) + { + delete pImpl; + return Shader(); + } + else + { + return GetResourceManager().Register(pImpl); + } + } + void ShaderRes::Unbind(class OpenGL* gl, ShaderType type) + { +#ifndef EMBEDDED + if (gl->m_activeShaders[(size_t)type] != 0) + { + glUseProgramStages(gl->m_mainProgramPipeline, shaderStageMap[(size_t)type], 0); + gl->m_activeShaders[(size_t)type] = 0; + } +#endif + } } diff --git a/Graphics/src/SpriteMap.cpp b/Graphics/src/SpriteMap.cpp index 36e06ea46..b20e77dee 100644 --- a/Graphics/src/SpriteMap.cpp +++ b/Graphics/src/SpriteMap.cpp @@ -6,194 +6,195 @@ namespace Graphics { - using Shared::Recti; - - static uint32 maxHeight = 1024; - static uint32 border = 2; - - struct Category - { - uint32 width = 0; - Vector2i offset; - Vector segments; - }; - struct Segment - { - Recti coords; - }; - - class SpriteMap_Impl : public SpriteMapRes - { - friend class ImageRes; - - // The image that contains the current data - Image m_image; - - // Used size over the X axis - int32 m_usedSize = 0; - - // Linear index of al segements - Vector m_segments; - Vector m_widths; - std::multimap m_categoryByWidth; - public: - SpriteMap_Impl() - { - m_image = ImageRes::Create(); - } - ~SpriteMap_Impl() - { - Clear(); - } - void Clear() override - { - for(auto s : m_segments) - { - delete s; - } - m_segments.clear(); - m_widths.clear(); - m_categoryByWidth.clear(); - m_usedSize = 0; - m_image->SetSize(Vector2i(0)); - } - - virtual Vector2i GetSize() const - { - return m_image->GetSize(); - } - virtual Colori* GetBits() - { - return m_image->GetBits(); - } - virtual const Colori* GetBits() const - { - return m_image->GetBits(); - } - - // Returns the category that has space for the requested size - Category& AssignCategory(Vector2i requestedSize) - { - Category* dstCat = nullptr; - - while(true) - { - auto range = m_categoryByWidth.equal_range(requestedSize.x); - // Find a suitable category first - for(auto it = range.first; it != range.second; it++) - { - Category& cat = m_widths[it->second]; - int32 remainingY = m_image->GetSize().y - cat.offset.y; - if(remainingY > requestedSize.y) - { - // This category is OK - dstCat = &cat; - break; - } - } - - // Create a new category if required - if(!dstCat) - { - int32 remainingX = m_image->GetSize().x - m_usedSize; - // Use horizontal space to add another collumn - // if height of image is big enough - if(m_image->GetSize().y >= requestedSize.y && remainingX >= requestedSize.x) - { - Category& cat = m_widths.Add(); - cat.width = requestedSize.x; - cat.offset = Vector2i(m_usedSize, 0); - m_categoryByWidth.insert(std::make_pair(cat.width, (uint32)m_widths.size()-1)); - m_usedSize += cat.width + 1; - } - else - { - int32 remainingX = (m_image->GetSize().x - m_usedSize); - - // Resize image - int32 largestDim = Math::Max(m_usedSize + requestedSize.x, - Math::Max(m_image->GetSize().y, requestedSize.y)); - int32 targetSize = (int32)pow(2, ceil(log(largestDim) / log(2))); - if(m_image->GetSize().x != targetSize) - { - // Resize image - Image newImage = ImageRes::Create(Vector2i(targetSize)); - // Copy old image into new image - if(m_image->GetSize().x > 0) - CopySubImage(newImage, m_image, Vector2i()); - m_image = newImage; - } - } - } - else - { - break; - } - } - - return *dstCat; - } - uint32 AddSegment(Image image) override - { - // Create a new segment - uint32 nI = (uint32)m_segments.size(); - Segment* pCurrentSegment = m_segments.Add(new Segment()); - pCurrentSegment->coords.size = image->GetSize(); - - // Get a category that has space - Category& cat = AssignCategory(image->GetSize()); - - // Set offset for current segment - pCurrentSegment->coords.pos = cat.offset; - // Add size offset in category - cat.offset.y += pCurrentSegment->coords.size.y + 1; - - // Copy image data - CopySubImage(m_image, image, pCurrentSegment->coords.pos); - - // Add segment to this category - cat.segments.push_back(nI); - return nI; - } - - void CopySubImage(Image dst, Image src, Vector2i dstPos) - { - Vector2i srcSize = src->GetSize(); - - Colori* pSrc = src->GetBits(); - uint32 nDstPitch = dst->GetSize().x; - Colori* pDst = dst->GetBits() + dstPos.x + dstPos.y * nDstPitch; - for(uint32 y = 0; y < (uint32)srcSize.y; y++) - { - for(uint32 x = 0; x < (uint32)srcSize.x; x++) - { - *pDst = *pSrc; - pSrc++; - pDst++; - } - pDst += (nDstPitch - srcSize.x); - } - } - Recti GetCoords(uint32 nIndex) override - { - assert(nIndex < m_segments.size()); - return m_segments[nIndex]->coords; - } - Ref GetImage() override - { - return m_image; - } - Texture GenerateTexture(OpenGL* gl) override - { - Texture tex = TextureRes::Create(gl, m_image); - tex->SetWrap(TextureWrap::Clamp, TextureWrap::Clamp); - return tex; - } - }; - - SpriteMap SpriteMapRes::Create() - { - SpriteMap_Impl* pImpl = new SpriteMap_Impl(); - return GetResourceManager().Register(pImpl); - } + using Shared::Recti; + + static uint32 maxHeight = 1024; + static uint32 border = 2; + + struct Category + { + uint32 width = 0; + Vector2i offset; + Vector segments; + }; + struct Segment + { + Recti coords; + }; + + class SpriteMap_Impl : public SpriteMapRes + { + friend class ImageRes; + + // The image that contains the current data + Image m_image; + + // Used size over the X axis + int32 m_usedSize = 0; + + // Linear index of al segements + Vector m_segments; + Vector m_widths; + std::multimap m_categoryByWidth; + + public: + SpriteMap_Impl() + { + m_image = ImageRes::Create(); + } + ~SpriteMap_Impl() + { + Clear(); + } + void Clear() override + { + for (auto s : m_segments) + { + delete s; + } + m_segments.clear(); + m_widths.clear(); + m_categoryByWidth.clear(); + m_usedSize = 0; + m_image->SetSize(Vector2i(0)); + } + + virtual Vector2i GetSize() const + { + return m_image->GetSize(); + } + virtual Colori* GetBits() + { + return m_image->GetBits(); + } + virtual const Colori* GetBits() const + { + return m_image->GetBits(); + } + + // Returns the category that has space for the requested size + Category& AssignCategory(Vector2i requestedSize) + { + Category* dstCat = nullptr; + + while (true) + { + auto range = m_categoryByWidth.equal_range(requestedSize.x); + // Find a suitable category first + for (auto it = range.first; it != range.second; it++) + { + Category& cat = m_widths[it->second]; + int32 remainingY = m_image->GetSize().y - cat.offset.y; + if (remainingY > requestedSize.y) + { + // This category is OK + dstCat = &cat; + break; + } + } + + // Create a new category if required + if (!dstCat) + { + int32 remainingX = m_image->GetSize().x - m_usedSize; + // Use horizontal space to add another collumn + // if height of image is big enough + if (m_image->GetSize().y >= requestedSize.y && remainingX >= requestedSize.x) + { + Category& cat = m_widths.Add(); + cat.width = requestedSize.x; + cat.offset = Vector2i(m_usedSize, 0); + m_categoryByWidth.insert(std::make_pair(cat.width, (uint32)m_widths.size() - 1)); + m_usedSize += cat.width + 1; + } + else + { + int32 remainingX = (m_image->GetSize().x - m_usedSize); + + // Resize image + int32 largestDim = Math::Max(m_usedSize + requestedSize.x, + Math::Max(m_image->GetSize().y, requestedSize.y)); + int32 targetSize = (int32)pow(2, ceil(log(largestDim) / log(2))); + if (m_image->GetSize().x != targetSize) + { + // Resize image + Image newImage = ImageRes::Create(Vector2i(targetSize)); + // Copy old image into new image + if (m_image->GetSize().x > 0) + CopySubImage(newImage, m_image, Vector2i()); + m_image = newImage; + } + } + } + else + { + break; + } + } + + return *dstCat; + } + uint32 AddSegment(Image image) override + { + // Create a new segment + uint32 nI = (uint32)m_segments.size(); + Segment* pCurrentSegment = m_segments.Add(new Segment()); + pCurrentSegment->coords.size = image->GetSize(); + + // Get a category that has space + Category& cat = AssignCategory(image->GetSize()); + + // Set offset for current segment + pCurrentSegment->coords.pos = cat.offset; + // Add size offset in category + cat.offset.y += pCurrentSegment->coords.size.y + 1; + + // Copy image data + CopySubImage(m_image, image, pCurrentSegment->coords.pos); + + // Add segment to this category + cat.segments.push_back(nI); + return nI; + } + + void CopySubImage(Image dst, Image src, Vector2i dstPos) + { + Vector2i srcSize = src->GetSize(); + + Colori* pSrc = src->GetBits(); + uint32 nDstPitch = dst->GetSize().x; + Colori* pDst = dst->GetBits() + dstPos.x + dstPos.y * nDstPitch; + for (uint32 y = 0; y < (uint32)srcSize.y; y++) + { + for (uint32 x = 0; x < (uint32)srcSize.x; x++) + { + *pDst = *pSrc; + pSrc++; + pDst++; + } + pDst += (nDstPitch - srcSize.x); + } + } + Recti GetCoords(uint32 nIndex) override + { + assert(nIndex < m_segments.size()); + return m_segments[nIndex]->coords; + } + Ref GetImage() override + { + return m_image; + } + Texture GenerateTexture(OpenGL* gl) override + { + Texture tex = TextureRes::Create(gl, m_image); + tex->SetWrap(TextureWrap::Clamp, TextureWrap::Clamp); + return tex; + } + }; + + SpriteMap SpriteMapRes::Create() + { + SpriteMap_Impl* pImpl = new SpriteMap_Impl(); + return GetResourceManager().Register(pImpl); + } } diff --git a/Graphics/src/Texture.cpp b/Graphics/src/Texture.cpp index 2b58cc3ae..062e7ca14 100644 --- a/Graphics/src/Texture.cpp +++ b/Graphics/src/Texture.cpp @@ -4,243 +4,240 @@ #include "Image.hpp" #include - - namespace Graphics { - class Texture_Impl : public TextureRes - { - uint32 m_texture = 0; - TextureWrap m_wmode[2] = { TextureWrap::Repeat }; - TextureFormat m_format = TextureFormat::Invalid; - Vector2i m_size; - bool m_filter = true; - bool m_mipFilter = true; - bool m_mipmaps = false; - float m_anisotropic = 1.0f; - void* m_data = nullptr; - OpenGL* m_gl = nullptr; - - public: - Texture_Impl(OpenGL* gl) - { - m_gl = gl; - } - ~Texture_Impl() - { - if(m_texture) - glDeleteTextures(1, &m_texture); - } - bool Init() - { - glGenTextures(1, &m_texture); - if(m_texture == 0) - return false; - return true; - } - void Init(Vector2i size, TextureFormat format) override - { - m_format = format; - m_size = size; - - uint32 ifmt = -1; - uint32 fmt = -1; - uint32 type = -1; - if(format == TextureFormat::D32) - { - #ifdef EMBEDDED - ifmt = GL_DEPTH_COMPONENT16; - #else - ifmt = GL_DEPTH_COMPONENT32; - #endif - fmt = GL_DEPTH_COMPONENT; - type = GL_FLOAT; - } - else if(format == TextureFormat::RGBA8) - { - ifmt = GL_RGBA8; - fmt = GL_RGBA; - type = GL_UNSIGNED_BYTE; - } - else - { - assert(false); - } - - glBindTexture(GL_TEXTURE_2D, m_texture); - glTexImage2D(GL_TEXTURE_2D, 0, ifmt, size.x, size.y, 0, fmt, type, nullptr); - glBindTexture(GL_TEXTURE_2D, 0); - - UpdateFilterState(); - UpdateWrap(); - } - void SetFromFrameBuffer(Vector2i pos = { 0, 0 }) override - { - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glReadBuffer(GL_BACK); - glBindTexture(GL_TEXTURE_2D, m_texture); - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pos.x, pos.y, m_size.x, m_size.y); - glBindTexture(GL_TEXTURE_2D, 0); - } - - void SetData(Vector2i size, void* pData) override - { - m_format = TextureFormat::RGBA8; - m_size = size; - m_data = pData; - glBindTexture(GL_TEXTURE_2D, m_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_data); - glBindTexture(GL_TEXTURE_2D, 0); - - UpdateFilterState(); - UpdateWrap(); - } - void UpdateFilterState() - { - glBindTexture(GL_TEXTURE_2D, m_texture); - if(!m_mipmaps) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter ? GL_LINEAR : GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_filter ? GL_LINEAR : GL_NEAREST); - } - else - { - if(m_mipFilter) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR); - else - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter ? GL_LINEAR : GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_filter ? GL_LINEAR : GL_NEAREST); - } - #ifndef EMBEDDED - if(GL_TEXTURE_MAX_ANISOTROPY_EXT) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, m_anisotropic); - } - #endif - glBindTexture(GL_TEXTURE_2D, 0); - } - void SetFilter(bool enabled, bool mipFiltering, float anisotropic) override - { - m_mipFilter = mipFiltering; - m_filter = enabled; - m_anisotropic = anisotropic; - assert(m_anisotropic >= 1.0f && m_anisotropic <= 16.0f); - UpdateFilterState(); - } - void SetMipmaps(bool enabled) override - { - if(enabled) - { - glBindTexture(GL_TEXTURE_2D, m_texture); - glGenerateMipmap(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, 0); - } - m_mipmaps = enabled; - UpdateFilterState(); - } - const Vector2i& GetSize() const override - { - return m_size; - } - void Bind(uint32 index) override - { - glActiveTexture(GL_TEXTURE0 + index); - glBindTexture(GL_TEXTURE_2D, m_texture); - - } - uint32 Handle() override - { - return m_texture; - } - - void SetWrap(TextureWrap u, TextureWrap v) override - { - m_wmode[0] = u; - m_wmode[1] = v; - UpdateWrap(); - } - - void UpdateWrap() - { - uint32 wmode[] = { - GL_REPEAT, - GL_MIRRORED_REPEAT, - GL_CLAMP_TO_EDGE, - }; - - glBindTexture(GL_TEXTURE_2D, m_texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wmode[(size_t)m_wmode[0]]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wmode[(size_t)m_wmode[1]]); - glBindTexture(GL_TEXTURE_2D, 0); - } - - TextureFormat GetFormat() const override - { - return m_format; - } - }; - - Texture TextureRes::Create(OpenGL* gl) - { - Texture_Impl* pImpl = new Texture_Impl(gl); - if(pImpl->Init()) - { - return GetResourceManager().Register(pImpl); - } - else - { - delete pImpl; pImpl = nullptr; - } - return Texture(); - } - Texture TextureRes::Create(OpenGL* gl, Image image) - { - if(!image) - return Texture(); - Texture_Impl* pImpl = new Texture_Impl(gl); - if(pImpl->Init()) - { - pImpl->SetData(image->GetSize(), image->GetBits()); - return GetResourceManager().Register(pImpl); - } - else - { - delete pImpl; - return Texture(); - } - } - - Texture TextureRes::CreateFromFrameBuffer(class OpenGL* gl, const Vector2i& resolution) - { - Texture_Impl* pImpl = new Texture_Impl(gl); - if (pImpl->Init()) - { - pImpl->Init(resolution, TextureFormat::RGBA8); - pImpl->SetWrap(TextureWrap::Clamp, TextureWrap::Clamp); - pImpl->SetFromFrameBuffer(); - return GetResourceManager().Register(pImpl); - } - else - { - delete pImpl; pImpl = nullptr; - } - return Texture(); - } - - float TextureRes::CalculateHeight(float width) - { - Vector2 size = GetSize(); - float aspect = size.y / size.x; - return aspect * width; - } - - float TextureRes::CalculateWidth(float height) - { - Vector2 size = GetSize(); - float aspect = size.x / size.y; - return aspect * height; - } - - + class Texture_Impl : public TextureRes + { + uint32 m_texture = 0; + TextureWrap m_wmode[2] = { TextureWrap::Repeat }; + TextureFormat m_format = TextureFormat::Invalid; + Vector2i m_size; + bool m_filter = true; + bool m_mipFilter = true; + bool m_mipmaps = false; + float m_anisotropic = 1.0f; + void* m_data = nullptr; + OpenGL* m_gl = nullptr; + + public: + Texture_Impl(OpenGL* gl) + { + m_gl = gl; + } + ~Texture_Impl() + { + if (m_texture) + glDeleteTextures(1, &m_texture); + } + bool Init() + { + glGenTextures(1, &m_texture); + if (m_texture == 0) + return false; + return true; + } + void Init(Vector2i size, TextureFormat format) override + { + m_format = format; + m_size = size; + + uint32 ifmt = -1; + uint32 fmt = -1; + uint32 type = -1; + if (format == TextureFormat::D32) + { +#ifdef EMBEDDED + ifmt = GL_DEPTH_COMPONENT16; +#else + ifmt = GL_DEPTH_COMPONENT32; +#endif + fmt = GL_DEPTH_COMPONENT; + type = GL_FLOAT; + } + else if (format == TextureFormat::RGBA8) + { + ifmt = GL_RGBA8; + fmt = GL_RGBA; + type = GL_UNSIGNED_BYTE; + } + else + { + assert(false); + } + + glBindTexture(GL_TEXTURE_2D, m_texture); + glTexImage2D(GL_TEXTURE_2D, 0, ifmt, size.x, size.y, 0, fmt, type, nullptr); + glBindTexture(GL_TEXTURE_2D, 0); + + UpdateFilterState(); + UpdateWrap(); + } + void SetFromFrameBuffer(Vector2i pos = { 0, 0 }) override + { + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glReadBuffer(GL_BACK); + glBindTexture(GL_TEXTURE_2D, m_texture); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pos.x, pos.y, m_size.x, m_size.y); + glBindTexture(GL_TEXTURE_2D, 0); + } + + void SetData(Vector2i size, void* pData) override + { + m_format = TextureFormat::RGBA8; + m_size = size; + m_data = pData; + glBindTexture(GL_TEXTURE_2D, m_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_data); + glBindTexture(GL_TEXTURE_2D, 0); + + UpdateFilterState(); + UpdateWrap(); + } + void UpdateFilterState() + { + glBindTexture(GL_TEXTURE_2D, m_texture); + if (!m_mipmaps) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter ? GL_LINEAR : GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_filter ? GL_LINEAR : GL_NEAREST); + } + else + { + if (m_mipFilter) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR); + else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_filter ? GL_LINEAR : GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_filter ? GL_LINEAR : GL_NEAREST); + } +#ifndef EMBEDDED + if (GL_TEXTURE_MAX_ANISOTROPY_EXT) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, m_anisotropic); + } +#endif + glBindTexture(GL_TEXTURE_2D, 0); + } + void SetFilter(bool enabled, bool mipFiltering, float anisotropic) override + { + m_mipFilter = mipFiltering; + m_filter = enabled; + m_anisotropic = anisotropic; + assert(m_anisotropic >= 1.0f && m_anisotropic <= 16.0f); + UpdateFilterState(); + } + void SetMipmaps(bool enabled) override + { + if (enabled) + { + glBindTexture(GL_TEXTURE_2D, m_texture); + glGenerateMipmap(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + } + m_mipmaps = enabled; + UpdateFilterState(); + } + const Vector2i& GetSize() const override + { + return m_size; + } + void Bind(uint32 index) override + { + glActiveTexture(GL_TEXTURE0 + index); + glBindTexture(GL_TEXTURE_2D, m_texture); + } + uint32 Handle() override + { + return m_texture; + } + + void SetWrap(TextureWrap u, TextureWrap v) override + { + m_wmode[0] = u; + m_wmode[1] = v; + UpdateWrap(); + } + + void UpdateWrap() + { + uint32 wmode[] = { + GL_REPEAT, + GL_MIRRORED_REPEAT, + GL_CLAMP_TO_EDGE, + }; + + glBindTexture(GL_TEXTURE_2D, m_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wmode[(size_t)m_wmode[0]]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wmode[(size_t)m_wmode[1]]); + glBindTexture(GL_TEXTURE_2D, 0); + } + + TextureFormat GetFormat() const override + { + return m_format; + } + }; + + Texture TextureRes::Create(OpenGL* gl) + { + Texture_Impl* pImpl = new Texture_Impl(gl); + if (pImpl->Init()) + { + return GetResourceManager().Register(pImpl); + } + else + { + delete pImpl; + pImpl = nullptr; + } + return Texture(); + } + Texture TextureRes::Create(OpenGL* gl, Image image) + { + if (!image) + return Texture(); + Texture_Impl* pImpl = new Texture_Impl(gl); + if (pImpl->Init()) + { + pImpl->SetData(image->GetSize(), image->GetBits()); + return GetResourceManager().Register(pImpl); + } + else + { + delete pImpl; + return Texture(); + } + } + + Texture TextureRes::CreateFromFrameBuffer(class OpenGL* gl, const Vector2i& resolution) + { + Texture_Impl* pImpl = new Texture_Impl(gl); + if (pImpl->Init()) + { + pImpl->Init(resolution, TextureFormat::RGBA8); + pImpl->SetWrap(TextureWrap::Clamp, TextureWrap::Clamp); + pImpl->SetFromFrameBuffer(); + return GetResourceManager().Register(pImpl); + } + else + { + delete pImpl; + pImpl = nullptr; + } + return Texture(); + } + + float TextureRes::CalculateHeight(float width) + { + Vector2 size = GetSize(); + float aspect = size.y / size.x; + return aspect * width; + } + + float TextureRes::CalculateWidth(float height) + { + Vector2 size = GetSize(); + float aspect = size.x / size.y; + return aspect * height; + } } diff --git a/Graphics/src/VertexFormat.cpp b/Graphics/src/VertexFormat.cpp index 9455cdb39..3a9e4b998 100644 --- a/Graphics/src/VertexFormat.cpp +++ b/Graphics/src/VertexFormat.cpp @@ -3,8 +3,8 @@ namespace Graphics { - template<> - void VertexFormats::AddFormats<0, void>(VertexFormatList& dsc) - { - } -} \ No newline at end of file + template <> + void VertexFormats::AddFormats<0, void>(VertexFormatList& dsc) + { + } +} diff --git a/Graphics/src/Window.cpp b/Graphics/src/Window.cpp index d4e07efbb..af9242e31 100644 --- a/Graphics/src/Window.cpp +++ b/Graphics/src/Window.cpp @@ -6,824 +6,825 @@ static void GetDisplayBounds(Vector& bounds) { - const int displayNum = SDL_GetNumVideoDisplays(); - if (displayNum <= 0) return; - - for (int monitorId = 0; monitorId < displayNum; ++monitorId) - { - SDL_Rect rect; - if (SDL_GetDisplayBounds(monitorId, &rect) < 0) break; - - bounds.emplace_back(Shared::Recti{ {rect.x, rect.y}, {rect.w, rect.h} }); - } + const int displayNum = SDL_GetNumVideoDisplays(); + if (displayNum <= 0) + return; + + for (int monitorId = 0; monitorId < displayNum; ++monitorId) + { + SDL_Rect rect; + if (SDL_GetDisplayBounds(monitorId, &rect) < 0) + break; + + bounds.emplace_back(Shared::Recti{ {rect.x, rect.y}, {rect.w, rect.h} }); + } } namespace Graphics { - /* SDL Instance singleton */ - class SDL - { - protected: - SDL() - { - SDL_SetMainReady(); - int r = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK); - if (r != 0) - { - Logf("SDL_Init Failed: %s", Logger::Severity::Error, SDL_GetError()); - assert(false); - } - } - - public: - ~SDL() - { - SDL_Quit(); - } - static SDL &Main() - { - static SDL sdl; - return sdl; - } - }; - - class Window_Impl - { - public: - // Handle to outer class to send delegates - Window &outer; - - public: - Window_Impl(Window &outer, Vector2i size, uint8 sampleCount) : outer(outer) - { - ProfilerScope $("Creating Window"); - SDL::Main(); - - m_clntSize = size; + /* SDL Instance singleton */ + class SDL + { + protected: + SDL() + { + SDL_SetMainReady(); + int r = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK); + if (r != 0) + { + Logf("SDL_Init Failed: %s", Logger::Severity::Error, SDL_GetError()); + assert(false); + } + } + + public: + ~SDL() + { + SDL_Quit(); + } + static SDL& Main() + { + static SDL sdl; + return sdl; + } + }; + + class Window_Impl + { + public: + // Handle to outer class to send delegates + Window& outer; + + public: + Window_Impl(Window& outer, Vector2i size, uint8 sampleCount) : outer(outer) + { + ProfilerScope $("Creating Window"); + SDL::Main(); + + m_clntSize = size; #ifdef _DEBUG - m_caption = L"USC-Game Debug"; + m_caption = L"USC-Game Debug"; #else - m_caption = L"USC-Game"; + m_caption = L"USC-Game"; #endif - String titleUtf8 = Utility::ConvertToUTF8(m_caption); - - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, sampleCount); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 2); - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); - - m_window = SDL_CreateWindow(*titleUtf8, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - m_clntSize.x, m_clntSize.y, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); - assert(m_window); - - uint32 numJoysticks = SDL_NumJoysticks(); - if (numJoysticks == 0) - { - Log("No joysticks found", Logger::Severity::Warning); - } - else - { - Logf("Listing %d Joysticks:", Logger::Severity::Info, numJoysticks); - for (uint32 i = 0; i < numJoysticks; i++) - { - SDL_Joystick *joystick = SDL_JoystickOpen(i); - if (!joystick) - { - Logf("[%d] ", Logger::Severity::Warning, i); - continue; - } - String deviceName = SDL_JoystickName(joystick); - - Logf("[%d] \"%s\" (%d buttons, %d axes, %d hats)", Logger::Severity::Info, - i, deviceName, SDL_JoystickNumButtons(joystick), SDL_JoystickNumAxes(joystick), SDL_JoystickNumHats(joystick)); - - SDL_JoystickClose(joystick); - } - } - } - - ~Window_Impl() - { - // Release gamepads - for (auto it : m_gamepads) - { - it.second.reset(); - } - - SDL_DestroyWindow(m_window); - } - - void SetWindowPos(const Vector2i &pos) - { - SDL_SetWindowPosition(m_window, pos.x, pos.y); - } - - void SetWindowPosToCenter(int32 monitorId) - { - SDL_SetWindowPosition(m_window, SDL_WINDOWPOS_CENTERED_DISPLAY(monitorId), SDL_WINDOWPOS_CENTERED_DISPLAY(monitorId)); - } - - void ShowMessageBox(const String& title, const String& message, int severity) - { - uint32 flags; - switch (severity) - { - case 0: - flags = SDL_MESSAGEBOX_ERROR; - break; - case 1: - flags = SDL_MESSAGEBOX_WARNING; - break; - default: - flags = SDL_MESSAGEBOX_INFORMATION; - } - SDL_ShowSimpleMessageBox(flags, title.c_str(), message.c_str(), m_window); - } - - bool ShowYesNoMessage(const String& title, const String& message) - { - const SDL_MessageBoxButtonData buttons[] = - { - {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, "no"}, - {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "yes"}, - }; - const SDL_MessageBoxData messageboxdata = - { - SDL_MESSAGEBOX_INFORMATION, - nullptr, - *title, - *message, - SDL_arraysize(buttons), - buttons, - nullptr}; - int buttonid; - if (SDL_ShowMessageBox(&messageboxdata, &buttonid) < 0) - { - Logf("Could not display message box for '%s'", Logger::Severity::Info, *message); - return false; - } - return buttonid == 1; - } - - Vector2i GetWindowPos() const - { - Vector2i res; - SDL_GetWindowPosition(m_window, &res.x, &res.y); - return res; - } - - void SetWindowSize(const Vector2i &size) - { - SDL_SetWindowSize(m_window, size.x, size.y); - } - - Vector2i GetWindowSize() const - { - Vector2i res; - SDL_GetWindowSize(m_window, &res.x, &res.y); - return res; - } - - void SetVSync(int8 setting) - { - if (SDL_GL_SetSwapInterval(setting) == -1) - Logf("Failed to set VSync: %s", Logger::Severity::Error, SDL_GetError()); - } - - void SetWindowStyle(WindowStyle style) - { - } - - /* input handling */ - void HandleKeyEvent(const SDL_Keysym &keySym, uint8 newState, int32 repeat, int32 delta) - { - const SDL_Scancode code = keySym.scancode; - auto m = static_cast(keySym.mod); - - m_modKeys = ModifierKeys::None; - - if ((m & KMOD_ALT) != 0) - (uint8 &)m_modKeys |= (uint8)ModifierKeys::Alt; - if ((m & KMOD_CTRL) != 0) - (uint8 &)m_modKeys |= (uint8)ModifierKeys::Ctrl; - if ((m & KMOD_SHIFT) != 0) - (uint8 &)m_modKeys |= (uint8)ModifierKeys::Shift; - - uint8 ¤tState = m_keyStates[code]; - - if (currentState != newState) - { - currentState = newState; - if (newState == 1) - { - outer.OnKeyPressed.Call(code, delta); - } - else - { - outer.OnKeyReleased.Call(code, delta); - } - } - if (currentState == 1) - { - outer.OnKeyRepeat.Call(code); - } - } - - /* Window show hide, positioning, etc.*/ - void Show() const - { - SDL_ShowWindow(m_window); - } - void Hide() const - { - SDL_HideWindow(m_window); - } - void SetCaption(const WString &cap) - { - m_caption = L"Window"; - String titleUtf8 = Utility::ConvertToUTF8(m_caption); - SDL_SetWindowTitle(m_window, *titleUtf8); - } - - void SetCursor(const Ref& image, Vector2i hotspot) - { + String titleUtf8 = Utility::ConvertToUTF8(m_caption); + + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, sampleCount); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 2); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + + m_window = SDL_CreateWindow(*titleUtf8, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + m_clntSize.x, m_clntSize.y, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + assert(m_window); + + uint32 numJoysticks = SDL_NumJoysticks(); + if (numJoysticks == 0) + { + Log("No joysticks found", Logger::Severity::Warning); + } + else + { + Logf("Listing %d Joysticks:", Logger::Severity::Info, numJoysticks); + for (uint32 i = 0; i < numJoysticks; i++) + { + SDL_Joystick* joystick = SDL_JoystickOpen(i); + if (!joystick) + { + Logf("[%d] ", Logger::Severity::Warning, i); + continue; + } + String deviceName = SDL_JoystickName(joystick); + + Logf("[%d] \"%s\" (%d buttons, %d axes, %d hats)", Logger::Severity::Info, + i, deviceName, SDL_JoystickNumButtons(joystick), SDL_JoystickNumAxes(joystick), SDL_JoystickNumHats(joystick)); + + SDL_JoystickClose(joystick); + } + } + } + + ~Window_Impl() + { + // Release gamepads + for (auto it : m_gamepads) + { + it.second.reset(); + } + + SDL_DestroyWindow(m_window); + } + + void SetWindowPos(const Vector2i& pos) + { + SDL_SetWindowPosition(m_window, pos.x, pos.y); + } + + void SetWindowPosToCenter(int32 monitorId) + { + SDL_SetWindowPosition(m_window, SDL_WINDOWPOS_CENTERED_DISPLAY(monitorId), SDL_WINDOWPOS_CENTERED_DISPLAY(monitorId)); + } + + void ShowMessageBox(const String& title, const String& message, int severity) + { + uint32 flags; + switch (severity) + { + case 0: + flags = SDL_MESSAGEBOX_ERROR; + break; + case 1: + flags = SDL_MESSAGEBOX_WARNING; + break; + default: + flags = SDL_MESSAGEBOX_INFORMATION; + } + SDL_ShowSimpleMessageBox(flags, title.c_str(), message.c_str(), m_window); + } + + bool ShowYesNoMessage(const String& title, const String& message) + { + const SDL_MessageBoxButtonData buttons[] = + { + {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, "no"}, + {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "yes"}, + }; + const SDL_MessageBoxData messageboxdata = + { + SDL_MESSAGEBOX_INFORMATION, + nullptr, + *title, + *message, + SDL_arraysize(buttons), + buttons, + nullptr }; + int buttonid; + if (SDL_ShowMessageBox(&messageboxdata, &buttonid) < 0) + { + Logf("Could not display message box for '%s'", Logger::Severity::Info, *message); + return false; + } + return buttonid == 1; + } + + Vector2i GetWindowPos() const + { + Vector2i res; + SDL_GetWindowPosition(m_window, &res.x, &res.y); + return res; + } + + void SetWindowSize(const Vector2i& size) + { + SDL_SetWindowSize(m_window, size.x, size.y); + } + + Vector2i GetWindowSize() const + { + Vector2i res; + SDL_GetWindowSize(m_window, &res.x, &res.y); + return res; + } + + void SetVSync(int8 setting) + { + if (SDL_GL_SetSwapInterval(setting) == -1) + Logf("Failed to set VSync: %s", Logger::Severity::Error, SDL_GetError()); + } + + void SetWindowStyle(WindowStyle style) + { + } + + /* input handling */ + void HandleKeyEvent(const SDL_Keysym& keySym, uint8 newState, int32 repeat, int32 delta) + { + const SDL_Scancode code = keySym.scancode; + auto m = static_cast(keySym.mod); + + m_modKeys = ModifierKeys::None; + + if ((m & KMOD_ALT) != 0) + (uint8&)m_modKeys |= (uint8)ModifierKeys::Alt; + if ((m & KMOD_CTRL) != 0) + (uint8&)m_modKeys |= (uint8)ModifierKeys::Ctrl; + if ((m & KMOD_SHIFT) != 0) + (uint8&)m_modKeys |= (uint8)ModifierKeys::Shift; + + uint8& currentState = m_keyStates[code]; + + if (currentState != newState) + { + currentState = newState; + if (newState == 1) + { + outer.OnKeyPressed.Call(code, delta); + } + else + { + outer.OnKeyReleased.Call(code, delta); + } + } + if (currentState == 1) + { + outer.OnKeyRepeat.Call(code); + } + } + + /* Window show hide, positioning, etc.*/ + void Show() const + { + SDL_ShowWindow(m_window); + } + void Hide() const + { + SDL_HideWindow(m_window); + } + void SetCaption(const WString& cap) + { + m_caption = L"Window"; + String titleUtf8 = Utility::ConvertToUTF8(m_caption); + SDL_SetWindowTitle(m_window, *titleUtf8); + } + + void SetCursor(const Ref& image, Vector2i hotspot) + { #ifdef _WIN32 - if (currentCursor) - { - SDL_FreeCursor(currentCursor); - currentCursor = nullptr; - } - if (image) - { - Vector2i size = image->GetSize(); - void *bits = image->GetBits(); - SDL_Surface *surf = SDL_CreateRGBSurfaceFrom(bits, size.x, size.y, 32, size.x * 4, - 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); - if (surf) - { - currentCursor = SDL_CreateColorCursor(surf, hotspot.x, hotspot.y); - } - } - SDL_SetCursor(currentCursor); + if (currentCursor) + { + SDL_FreeCursor(currentCursor); + currentCursor = nullptr; + } + if (image) + { + Vector2i size = image->GetSize(); + void* bits = image->GetBits(); + SDL_Surface* surf = SDL_CreateRGBSurfaceFrom(bits, size.x, size.y, 32, size.x * 4, + 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); + if (surf) + { + currentCursor = SDL_CreateColorCursor(surf, hotspot.x, hotspot.y); + } + } + SDL_SetCursor(currentCursor); #endif - /// NOTE: Cursor transparency is broken on linux - } - - // Update loop - Timer t; - bool Update() - { - static SDL_Event events[SIZE_EVENTS]; - int eventCount; - - SDL_PumpEvents(); - Uint32 tick = SDL_GetTicks(); - do - { - eventCount = SDL_PeepEvents(&events[0], SIZE_EVENTS, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT); - // Don't use range loop since it could loop through previously processed events from the previous loop - for (int i = 0; i < eventCount; ++i) - { - auto evt = events[i]; - int32 delta = tick - evt.common.timestamp; - if (evt.type == SDL_EventType::SDL_KEYDOWN) - { - HandleKeyEvent(evt.key.keysym, 1, evt.key.repeat, delta); - } - else if (evt.type == SDL_EventType::SDL_KEYUP) - { - HandleKeyEvent(evt.key.keysym, 0, 0, delta); - } - else if (evt.type == SDL_EventType::SDL_JOYBUTTONDOWN) - { - Gamepad_Impl **gp = m_joystickMap.Find(evt.jbutton.which); - if (gp) - gp[0]->HandleInputEvent(evt.jbutton.button, true, delta); - } - else if (evt.type == SDL_EventType::SDL_JOYBUTTONUP) - { - Gamepad_Impl **gp = m_joystickMap.Find(evt.jbutton.which); - if (gp) - gp[0]->HandleInputEvent(evt.jbutton.button, false, delta); - } - else if (evt.type == SDL_EventType::SDL_JOYAXISMOTION) - { - Gamepad_Impl **gp = m_joystickMap.Find(evt.jaxis.which); - if (gp) - gp[0]->HandleAxisEvent(evt.jaxis.axis, evt.jaxis.value); - } - else if (evt.type == SDL_EventType::SDL_JOYHATMOTION) - { - Gamepad_Impl **gp = m_joystickMap.Find(evt.jhat.which); - if (gp) - gp[0]->HandleHatEvent(evt.jhat.hat, evt.jhat.value); - } - else if (evt.type == SDL_EventType::SDL_MOUSEBUTTONDOWN) - { - switch (evt.button.button) - { - case SDL_BUTTON_LEFT: - outer.OnMousePressed.Call(MouseButton::Left); - break; - case SDL_BUTTON_MIDDLE: - outer.OnMousePressed.Call(MouseButton::Middle); - break; - case SDL_BUTTON_RIGHT: - outer.OnMousePressed.Call(MouseButton::Right); - break; - } - } - else if (evt.type == SDL_EventType::SDL_MOUSEBUTTONUP) - { - switch (evt.button.button) - { - case SDL_BUTTON_LEFT: - outer.OnMouseReleased.Call(MouseButton::Left); - break; - case SDL_BUTTON_MIDDLE: - outer.OnMouseReleased.Call(MouseButton::Middle); - break; - case SDL_BUTTON_RIGHT: - outer.OnMouseReleased.Call(MouseButton::Right); - break; - } - } - else if (evt.type == SDL_EventType::SDL_MOUSEWHEEL) - { - if (evt.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) - { - outer.OnMouseScroll.Call(evt.wheel.y); - } - else - { - outer.OnMouseScroll.Call(-evt.wheel.y); - } - } - else if (evt.type == SDL_EventType::SDL_MOUSEMOTION) - { - outer.OnMouseMotion.Call(evt.motion.xrel, evt.motion.yrel); - } - else if (evt.type == SDL_EventType::SDL_QUIT) - { - m_closed = true; - } - else if (evt.type == SDL_EventType::SDL_WINDOWEVENT) - { - if (evt.window.windowID == SDL_GetWindowID(m_window)) - { - if (evt.window.event == SDL_WindowEventID::SDL_WINDOWEVENT_SIZE_CHANGED) - { - Vector2i newSize(evt.window.data1, evt.window.data2); - outer.OnResized.Call(newSize); - } - else if (evt.window.event == SDL_WindowEventID::SDL_WINDOWEVENT_FOCUS_GAINED) - { - outer.OnFocusChanged.Call(true); - } - else if (evt.window.event == SDL_WindowEventID::SDL_WINDOWEVENT_FOCUS_LOST) - { - outer.OnFocusChanged.Call(false); - } - else if (evt.window.event == SDL_WindowEventID::SDL_WINDOWEVENT_MOVED) - { - Vector2i newPos(evt.window.data1, evt.window.data2); - outer.OnMoved.Call(newPos); - } - } - } - else if (evt.type == SDL_EventType::SDL_TEXTINPUT) - { - outer.OnTextInput.Call(evt.text.text); - } - else if (evt.type == SDL_EventType::SDL_TEXTEDITING) - { - SDL_Rect scr; - SDL_GetWindowPosition(m_window, &scr.x, &scr.y); - SDL_GetWindowSize(m_window, &scr.w, &scr.h); - SDL_SetTextInputRect(&scr); - - m_textComposition.composition = evt.edit.text; - m_textComposition.cursor = evt.edit.start; - m_textComposition.selectionLength = evt.edit.length; - outer.OnTextComposition.Call(m_textComposition); - } - else if (evt.type == SDL_EventType::SDL_DROPFILE) - { - const char* file = evt.drop.file; - if (file != nullptr) - { - outer.OnFileDropped.Call(evt.drop.file); - SDL_free(evt.drop.file); - } - } - outer.OnAnyEvent.Call(evt); - } - } - while (eventCount == SIZE_EVENTS); - - return !m_closed; - } - - void SetWindowed(const Vector2i& pos, const Vector2i& size) - { - SDL_SetWindowFullscreen(m_window, 0); - SDL_RestoreWindow(m_window); - - SetWindowSize(size); - - SDL_SetWindowResizable(m_window, SDL_TRUE); - SDL_SetWindowBordered(m_window, SDL_TRUE); - - SetWindowPos(pos); - - m_fullscreen = false; - } - - void SetWindowedFullscreen(int32 monitorId) - { - if (monitorId == -1) - { - monitorId = SDL_GetWindowDisplayIndex(m_window); - } - - SDL_DisplayMode dm; - SDL_GetDesktopDisplayMode(monitorId, &dm); - - SDL_Rect bounds; - SDL_GetDisplayBounds(monitorId, &bounds); - - SDL_RestoreWindow(m_window); - SDL_SetWindowSize(m_window, dm.w, dm.h); - SDL_SetWindowPosition(m_window, bounds.x, bounds.y); - SDL_SetWindowResizable(m_window, SDL_FALSE); - - m_fullscreen = true; - } - - void SetFullscreen(int32 monitorId, const Vector2i& res) - { - if (monitorId == -1) - { - monitorId = SDL_GetWindowDisplayIndex(m_window); - } - - SDL_DisplayMode dm; - SDL_GetDesktopDisplayMode(monitorId, &dm); - - if (res.x != -1) - { - dm.w = res.x; - } - - if (res.y != -1) - { - dm.h = res.y; - } - - // move to correct display - SetWindowPosToCenter(monitorId); - - SDL_SetWindowDisplayMode(m_window, &dm); - SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN); - - m_fullscreen = true; - } - - void SetPosAndShape(const Window::PosAndShape& posAndShape, bool ensureInBound) - { - int32 monitorId = posAndShape.monitorId; - if (monitorId == -1) - { - monitorId = SDL_GetWindowDisplayIndex(m_window); - } - - if (ensureInBound) - { - // Adjust the monitor to use, if the monitor does not exist. - SDL_DisplayMode dm; - if (SDL_GetDesktopDisplayMode(monitorId, &dm) < 0) - { - Logf("Monitor %d is not available; using 0 instead", Logger::Severity::Warning, monitorId); - monitorId = 0; - } - } - - switch (posAndShape.mode) - { - case Window::PosAndShape::Mode::Windowed: - { - Vector2i windowPos = posAndShape.windowPos; - Vector2i windowSize = posAndShape.windowSize; - - SetWindowed(windowPos, windowSize); - - // Adjust window position and size, if the window can't be fit into the display region. - if (ensureInBound) - { - int borderTop = 0, borderLeft = 0, borderBottom = 0, borderRight = 0; - SDL_GetWindowBordersSize(m_window, &borderTop, &borderLeft, &borderBottom, &borderRight); - - Shared::Recti windowRect(windowPos - Vector2i{ borderLeft, borderTop }, windowSize + Vector2i{ borderLeft+borderRight, borderTop+borderBottom }); - - Vector bounds; - GetDisplayBounds(bounds); - - if (bounds.empty()) - { - break; - } - - Logf("Adjusting window size for %u windows...", Logger::Severity::Info, bounds.size()); - - if (monitorId >= static_cast(bounds.size())) - { - monitorId = 0; - } - - bool foundContained = bounds[monitorId].Contains(windowRect); - if (foundContained) - { - break; - } - - bool foundLargeEnough = bounds[monitorId].NotSmallerThan(windowRect.size); - if (!foundLargeEnough) - { - - for (int i = 0; i < static_cast(bounds.size()); ++i) - { - if (bounds[i].Contains(windowRect)) - { - foundContained = true; - break; - } - - if (bounds[i].NotSmallerThan(windowRect.size)) - { - foundLargeEnough = true; - monitorId = i; - break; - } - } - } - - if (foundContained) - { - break; - } - - // Optimally, this should be set to the largest rectangle inside `bounds intersect windowRect`. - Shared::Recti adjustedRect = bounds[monitorId]; - if (foundLargeEnough) - { - adjustedRect.size = windowRect.size; - } - - adjustedRect.pos = bounds[monitorId].pos + (bounds[monitorId].size - adjustedRect.size) / 2; - - windowSize = adjustedRect.size - Vector2i{ borderLeft+borderRight, borderTop+borderBottom }; - - SetWindowSize(windowSize); - SetWindowPosToCenter(monitorId); - } - } - break; - case Window::PosAndShape::Mode::WindowedFullscreen: - SetWindowedFullscreen(monitorId); - break; - case Window::PosAndShape::Mode::Fullscreen: - SetFullscreen(monitorId, posAndShape.fullscreenSize); - break; - } - } - - inline bool IsFullscreen() const { return m_fullscreen; } - - SDL_Window *m_window; - - SDL_Cursor *currentCursor = nullptr; - - // Window Input State - Map m_keyStates; - ModifierKeys m_modKeys = ModifierKeys::None; - - // Gamepad input - Map> m_gamepads; - Map m_joystickMap; - - // Text input / IME stuff - TextComposition m_textComposition; - - // Various window state - bool m_active = true; - bool m_closed = false; - - bool m_fullscreen = false; - - uint32 m_style; - Vector2i m_clntSize; - WString m_caption; - }; - - Window::Window(Vector2i size, uint8 samplecount) - { - m_impl = new Window_Impl(*this, size, samplecount); - } - Window::~Window() - { - delete m_impl; - } - void Window::Show() - { - m_impl->Show(); - } - void Window::Hide() - { - m_impl->Hide(); - } - bool Window::Update() - { - return m_impl->Update(); - } - void *Window::Handle() - { - return m_impl->m_window; - } - void Window::SetCaption(const WString &cap) - { - m_impl->SetCaption(cap); - } - void Window::Close() - { - m_impl->m_closed = true; - } - - Vector2i Window::GetMousePos() - { - Vector2i res; - SDL_GetMouseState(&res.x, &res.y); - return res; - } - void Window::SetCursor(const Ref& image, Vector2i hotspot /*= Vector2i(0,0)*/) - { - m_impl->SetCursor(image, hotspot); - } - void Window::SetCursorVisible(bool visible) - { - SDL_ShowCursor(visible); - } - - void Window::SetWindowStyle(WindowStyle style) - { - m_impl->SetWindowStyle(style); - } - - Vector2i Window::GetWindowPos() const - { - return m_impl->GetWindowPos(); - } - - Vector2i Window::GetWindowSize() const - { - return m_impl->GetWindowSize(); - } - - void Window::SetVSync(int8 setting) - { - m_impl->SetVSync(setting); - } - - void Window::SetPosAndShape(const PosAndShape& posAndShape, bool ensureInBound) - { - m_impl->SetPosAndShape(posAndShape, ensureInBound); - } - - bool Window::IsFullscreen() const - { - return m_impl->IsFullscreen(); - } - - int Window::GetDisplayIndex() const - { - return SDL_GetWindowDisplayIndex(m_impl->m_window); - } - - bool Window::IsKeyPressed(SDL_Scancode key) const - { - return m_impl->m_keyStates[key] > 0; - } - - Graphics::ModifierKeys Window::GetModifierKeys() const - { - return m_impl->m_modKeys; - } - - bool Window::IsActive() const - { - return SDL_GetWindowFlags(m_impl->m_window) & SDL_WindowFlags::SDL_WINDOW_INPUT_FOCUS; - } - - void Window::StartTextInput() - { - SDL_StartTextInput(); - } - void Window::StopTextInput() - { - SDL_StopTextInput(); - } - const Graphics::TextComposition &Window::GetTextComposition() const - { - return m_impl->m_textComposition; - } - - void Window::ShowMessageBox(const String& title, const String& message, int severity) - { - m_impl->ShowMessageBox(title, message, severity); - } - - bool Window::ShowYesNoMessage(const String& title, const String& message) - { - return m_impl->ShowYesNoMessage(title, message); - } - - String Window::GetClipboard() const - { - char *utf8Clipboard = SDL_GetClipboardText(); - String ret(utf8Clipboard); - SDL_free(utf8Clipboard); - - return ret; - } - - int32 Window::GetNumGamepads() const - { - return SDL_NumJoysticks(); - } - Vector Window::GetGamepadDeviceNames() const - { - Vector ret; - uint32 numJoysticks = SDL_NumJoysticks(); - for (uint32 i = 0; i < numJoysticks; i++) - { - SDL_Joystick *joystick = SDL_JoystickOpen(i); - if (!joystick) - { - continue; - } - String deviceName = SDL_JoystickName(joystick); - ret.Add(deviceName); - - SDL_JoystickClose(joystick); - } - return ret; - } - - Ref Window::OpenGamepad(int32 deviceIndex) - { - Ref *openGamepad = m_impl->m_gamepads.Find(deviceIndex); - if (openGamepad) - return Utility::CastRef(*openGamepad); - Ref newGamepad; - - auto *gamepadImpl = new Gamepad_Impl(); - // Try to initialize new device - if (gamepadImpl->Init(this, deviceIndex)) - { - newGamepad = Ref(gamepadImpl); - - // Receive joystick events - SDL_JoystickEventState(SDL_ENABLE); - } - else - { - delete gamepadImpl; - } - if (newGamepad) - { - m_impl->m_gamepads.Add(deviceIndex, newGamepad); - m_impl->m_joystickMap.Add(SDL_JoystickInstanceID(gamepadImpl->m_joystick), gamepadImpl); - } - return Utility::CastRef(newGamepad); - } - - void Window::SetMousePos(const Vector2i &pos) - { - SDL_WarpMouseInWindow(m_impl->m_window, pos.x, pos.y); - } - - void Window::SetRelativeMouseMode(bool enabled) - { - if (SDL_SetRelativeMouseMode(enabled ? SDL_TRUE : SDL_FALSE) != 0) - Logf("SetRelativeMouseMode failed: %s", Logger::Severity::Warning, SDL_GetError()); - } - - bool Window::GetRelativeMouseMode() - { - return SDL_GetRelativeMouseMode() == SDL_TRUE; - } + /// NOTE: Cursor transparency is broken on linux + } + + // Update loop + Timer t; + bool Update() + { + static SDL_Event events[SIZE_EVENTS]; + int eventCount; + + SDL_PumpEvents(); + Uint32 tick = SDL_GetTicks(); + do + { + eventCount = SDL_PeepEvents(&events[0], SIZE_EVENTS, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT); + // Don't use range loop since it could loop through previously processed events from the previous loop + for (int i = 0; i < eventCount; ++i) + { + auto evt = events[i]; + int32 delta = tick - evt.common.timestamp; + if (evt.type == SDL_EventType::SDL_KEYDOWN) + { + HandleKeyEvent(evt.key.keysym, 1, evt.key.repeat, delta); + } + else if (evt.type == SDL_EventType::SDL_KEYUP) + { + HandleKeyEvent(evt.key.keysym, 0, 0, delta); + } + else if (evt.type == SDL_EventType::SDL_JOYBUTTONDOWN) + { + Gamepad_Impl** gp = m_joystickMap.Find(evt.jbutton.which); + if (gp) + gp[0]->HandleInputEvent(evt.jbutton.button, true, delta); + } + else if (evt.type == SDL_EventType::SDL_JOYBUTTONUP) + { + Gamepad_Impl** gp = m_joystickMap.Find(evt.jbutton.which); + if (gp) + gp[0]->HandleInputEvent(evt.jbutton.button, false, delta); + } + else if (evt.type == SDL_EventType::SDL_JOYAXISMOTION) + { + Gamepad_Impl** gp = m_joystickMap.Find(evt.jaxis.which); + if (gp) + gp[0]->HandleAxisEvent(evt.jaxis.axis, evt.jaxis.value); + } + else if (evt.type == SDL_EventType::SDL_JOYHATMOTION) + { + Gamepad_Impl** gp = m_joystickMap.Find(evt.jhat.which); + if (gp) + gp[0]->HandleHatEvent(evt.jhat.hat, evt.jhat.value); + } + else if (evt.type == SDL_EventType::SDL_MOUSEBUTTONDOWN) + { + switch (evt.button.button) + { + case SDL_BUTTON_LEFT: + outer.OnMousePressed.Call(MouseButton::Left); + break; + case SDL_BUTTON_MIDDLE: + outer.OnMousePressed.Call(MouseButton::Middle); + break; + case SDL_BUTTON_RIGHT: + outer.OnMousePressed.Call(MouseButton::Right); + break; + } + } + else if (evt.type == SDL_EventType::SDL_MOUSEBUTTONUP) + { + switch (evt.button.button) + { + case SDL_BUTTON_LEFT: + outer.OnMouseReleased.Call(MouseButton::Left); + break; + case SDL_BUTTON_MIDDLE: + outer.OnMouseReleased.Call(MouseButton::Middle); + break; + case SDL_BUTTON_RIGHT: + outer.OnMouseReleased.Call(MouseButton::Right); + break; + } + } + else if (evt.type == SDL_EventType::SDL_MOUSEWHEEL) + { + if (evt.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) + { + outer.OnMouseScroll.Call(evt.wheel.y); + } + else + { + outer.OnMouseScroll.Call(-evt.wheel.y); + } + } + else if (evt.type == SDL_EventType::SDL_MOUSEMOTION) + { + outer.OnMouseMotion.Call(evt.motion.xrel, evt.motion.yrel); + } + else if (evt.type == SDL_EventType::SDL_QUIT) + { + m_closed = true; + } + else if (evt.type == SDL_EventType::SDL_WINDOWEVENT) + { + if (evt.window.windowID == SDL_GetWindowID(m_window)) + { + if (evt.window.event == SDL_WindowEventID::SDL_WINDOWEVENT_SIZE_CHANGED) + { + Vector2i newSize(evt.window.data1, evt.window.data2); + outer.OnResized.Call(newSize); + } + else if (evt.window.event == SDL_WindowEventID::SDL_WINDOWEVENT_FOCUS_GAINED) + { + outer.OnFocusChanged.Call(true); + } + else if (evt.window.event == SDL_WindowEventID::SDL_WINDOWEVENT_FOCUS_LOST) + { + outer.OnFocusChanged.Call(false); + } + else if (evt.window.event == SDL_WindowEventID::SDL_WINDOWEVENT_MOVED) + { + Vector2i newPos(evt.window.data1, evt.window.data2); + outer.OnMoved.Call(newPos); + } + } + } + else if (evt.type == SDL_EventType::SDL_TEXTINPUT) + { + outer.OnTextInput.Call(evt.text.text); + } + else if (evt.type == SDL_EventType::SDL_TEXTEDITING) + { + SDL_Rect scr; + SDL_GetWindowPosition(m_window, &scr.x, &scr.y); + SDL_GetWindowSize(m_window, &scr.w, &scr.h); + SDL_SetTextInputRect(&scr); + + m_textComposition.composition = evt.edit.text; + m_textComposition.cursor = evt.edit.start; + m_textComposition.selectionLength = evt.edit.length; + outer.OnTextComposition.Call(m_textComposition); + } + else if (evt.type == SDL_EventType::SDL_DROPFILE) + { + const char* file = evt.drop.file; + if (file != nullptr) + { + outer.OnFileDropped.Call(evt.drop.file); + SDL_free(evt.drop.file); + } + } + outer.OnAnyEvent.Call(evt); + } + } while (eventCount == SIZE_EVENTS); + + return !m_closed; + } + + void SetWindowed(const Vector2i& pos, const Vector2i& size) + { + SDL_SetWindowFullscreen(m_window, 0); + SDL_RestoreWindow(m_window); + + SetWindowSize(size); + + SDL_SetWindowResizable(m_window, SDL_TRUE); + SDL_SetWindowBordered(m_window, SDL_TRUE); + + SetWindowPos(pos); + + m_fullscreen = false; + } + + void SetWindowedFullscreen(int32 monitorId) + { + if (monitorId == -1) + { + monitorId = SDL_GetWindowDisplayIndex(m_window); + } + + SDL_DisplayMode dm; + SDL_GetDesktopDisplayMode(monitorId, &dm); + + SDL_Rect bounds; + SDL_GetDisplayBounds(monitorId, &bounds); + + SDL_RestoreWindow(m_window); + SDL_SetWindowSize(m_window, dm.w, dm.h); + SDL_SetWindowPosition(m_window, bounds.x, bounds.y); + SDL_SetWindowResizable(m_window, SDL_FALSE); + + m_fullscreen = true; + } + + void SetFullscreen(int32 monitorId, const Vector2i& res) + { + if (monitorId == -1) + { + monitorId = SDL_GetWindowDisplayIndex(m_window); + } + + SDL_DisplayMode dm; + SDL_GetDesktopDisplayMode(monitorId, &dm); + + if (res.x != -1) + { + dm.w = res.x; + } + + if (res.y != -1) + { + dm.h = res.y; + } + + // move to correct display + SetWindowPosToCenter(monitorId); + + SDL_SetWindowDisplayMode(m_window, &dm); + SDL_SetWindowFullscreen(m_window, SDL_WINDOW_FULLSCREEN); + + m_fullscreen = true; + } + + void SetPosAndShape(const Window::PosAndShape& posAndShape, bool ensureInBound) + { + int32 monitorId = posAndShape.monitorId; + if (monitorId == -1) + { + monitorId = SDL_GetWindowDisplayIndex(m_window); + } + + if (ensureInBound) + { + // Adjust the monitor to use, if the monitor does not exist. + SDL_DisplayMode dm; + if (SDL_GetDesktopDisplayMode(monitorId, &dm) < 0) + { + Logf("Monitor %d is not available; using 0 instead", Logger::Severity::Warning, monitorId); + monitorId = 0; + } + } + + switch (posAndShape.mode) + { + case Window::PosAndShape::Mode::Windowed: + { + Vector2i windowPos = posAndShape.windowPos; + Vector2i windowSize = posAndShape.windowSize; + + SetWindowed(windowPos, windowSize); + + // Adjust window position and size, if the window can't be fit into the display region. + if (ensureInBound) + { + int borderTop = 0, borderLeft = 0, borderBottom = 0, borderRight = 0; + SDL_GetWindowBordersSize(m_window, &borderTop, &borderLeft, &borderBottom, &borderRight); + + Shared::Recti windowRect(windowPos - Vector2i{ borderLeft, borderTop }, windowSize + Vector2i{ borderLeft + borderRight, borderTop + borderBottom }); + + Vector bounds; + GetDisplayBounds(bounds); + + if (bounds.empty()) + { + break; + } + + Logf("Adjusting window size for %u windows...", Logger::Severity::Info, bounds.size()); + + if (monitorId >= static_cast(bounds.size())) + { + monitorId = 0; + } + + bool foundContained = bounds[monitorId].Contains(windowRect); + if (foundContained) + { + break; + } + + bool foundLargeEnough = bounds[monitorId].NotSmallerThan(windowRect.size); + if (!foundLargeEnough) + { + + for (int i = 0; i < static_cast(bounds.size()); ++i) + { + if (bounds[i].Contains(windowRect)) + { + foundContained = true; + break; + } + + if (bounds[i].NotSmallerThan(windowRect.size)) + { + foundLargeEnough = true; + monitorId = i; + break; + } + } + } + + if (foundContained) + { + break; + } + + // Optimally, this should be set to the largest rectangle inside `bounds intersect windowRect`. + Shared::Recti adjustedRect = bounds[monitorId]; + if (foundLargeEnough) + { + adjustedRect.size = windowRect.size; + } + + adjustedRect.pos = bounds[monitorId].pos + (bounds[monitorId].size - adjustedRect.size) / 2; + + windowSize = adjustedRect.size - Vector2i{ borderLeft + borderRight, borderTop + borderBottom }; + + SetWindowSize(windowSize); + SetWindowPosToCenter(monitorId); + } + } + break; + case Window::PosAndShape::Mode::WindowedFullscreen: + SetWindowedFullscreen(monitorId); + break; + case Window::PosAndShape::Mode::Fullscreen: + SetFullscreen(monitorId, posAndShape.fullscreenSize); + break; + } + } + + inline bool IsFullscreen() const { return m_fullscreen; } + + SDL_Window* m_window; + + SDL_Cursor* currentCursor = nullptr; + + // Window Input State + Map m_keyStates; + ModifierKeys m_modKeys = ModifierKeys::None; + + // Gamepad input + Map> m_gamepads; + Map m_joystickMap; + + // Text input / IME stuff + TextComposition m_textComposition; + + // Various window state + bool m_active = true; + bool m_closed = false; + + bool m_fullscreen = false; + + uint32 m_style; + Vector2i m_clntSize; + WString m_caption; + }; + + Window::Window(Vector2i size, uint8 samplecount) + { + m_impl = new Window_Impl(*this, size, samplecount); + } + Window::~Window() + { + delete m_impl; + } + void Window::Show() + { + m_impl->Show(); + } + void Window::Hide() + { + m_impl->Hide(); + } + bool Window::Update() + { + return m_impl->Update(); + } + void* Window::Handle() + { + return m_impl->m_window; + } + void Window::SetCaption(const WString& cap) + { + m_impl->SetCaption(cap); + } + void Window::Close() + { + m_impl->m_closed = true; + } + + Vector2i Window::GetMousePos() + { + Vector2i res; + SDL_GetMouseState(&res.x, &res.y); + return res; + } + void Window::SetCursor(const Ref& image, Vector2i hotspot /*= Vector2i(0,0)*/) + { + m_impl->SetCursor(image, hotspot); + } + void Window::SetCursorVisible(bool visible) + { + SDL_ShowCursor(visible); + } + + void Window::SetWindowStyle(WindowStyle style) + { + m_impl->SetWindowStyle(style); + } + + Vector2i Window::GetWindowPos() const + { + return m_impl->GetWindowPos(); + } + + Vector2i Window::GetWindowSize() const + { + return m_impl->GetWindowSize(); + } + + void Window::SetVSync(int8 setting) + { + m_impl->SetVSync(setting); + } + + void Window::SetPosAndShape(const PosAndShape& posAndShape, bool ensureInBound) + { + m_impl->SetPosAndShape(posAndShape, ensureInBound); + } + + bool Window::IsFullscreen() const + { + return m_impl->IsFullscreen(); + } + + int Window::GetDisplayIndex() const + { + return SDL_GetWindowDisplayIndex(m_impl->m_window); + } + + bool Window::IsKeyPressed(SDL_Scancode key) const + { + return m_impl->m_keyStates[key] > 0; + } + + Graphics::ModifierKeys Window::GetModifierKeys() const + { + return m_impl->m_modKeys; + } + + bool Window::IsActive() const + { + return SDL_GetWindowFlags(m_impl->m_window) & SDL_WindowFlags::SDL_WINDOW_INPUT_FOCUS; + } + + void Window::StartTextInput() + { + SDL_StartTextInput(); + } + void Window::StopTextInput() + { + SDL_StopTextInput(); + } + const Graphics::TextComposition& Window::GetTextComposition() const + { + return m_impl->m_textComposition; + } + + void Window::ShowMessageBox(const String& title, const String& message, int severity) + { + m_impl->ShowMessageBox(title, message, severity); + } + + bool Window::ShowYesNoMessage(const String& title, const String& message) + { + return m_impl->ShowYesNoMessage(title, message); + } + + String Window::GetClipboard() const + { + char* utf8Clipboard = SDL_GetClipboardText(); + String ret(utf8Clipboard); + SDL_free(utf8Clipboard); + + return ret; + } + + int32 Window::GetNumGamepads() const + { + return SDL_NumJoysticks(); + } + Vector Window::GetGamepadDeviceNames() const + { + Vector ret; + uint32 numJoysticks = SDL_NumJoysticks(); + for (uint32 i = 0; i < numJoysticks; i++) + { + SDL_Joystick* joystick = SDL_JoystickOpen(i); + if (!joystick) + { + continue; + } + String deviceName = SDL_JoystickName(joystick); + ret.Add(deviceName); + + SDL_JoystickClose(joystick); + } + return ret; + } + + Ref Window::OpenGamepad(int32 deviceIndex) + { + Ref* openGamepad = m_impl->m_gamepads.Find(deviceIndex); + if (openGamepad) + return Utility::CastRef(*openGamepad); + Ref newGamepad; + + auto* gamepadImpl = new Gamepad_Impl(); + // Try to initialize new device + if (gamepadImpl->Init(this, deviceIndex)) + { + newGamepad = Ref(gamepadImpl); + + // Receive joystick events + SDL_JoystickEventState(SDL_ENABLE); + } + else + { + delete gamepadImpl; + } + if (newGamepad) + { + m_impl->m_gamepads.Add(deviceIndex, newGamepad); + m_impl->m_joystickMap.Add(SDL_JoystickInstanceID(gamepadImpl->m_joystick), gamepadImpl); + } + return Utility::CastRef(newGamepad); + } + + void Window::SetMousePos(const Vector2i& pos) + { + SDL_WarpMouseInWindow(m_impl->m_window, pos.x, pos.y); + } + + void Window::SetRelativeMouseMode(bool enabled) + { + if (SDL_SetRelativeMouseMode(enabled ? SDL_TRUE : SDL_FALSE) != 0) + Logf("SetRelativeMouseMode failed: %s", Logger::Severity::Warning, SDL_GetError()); + } + + bool Window::GetRelativeMouseMode() + { + return SDL_GetRelativeMouseMode() == SDL_TRUE; + } } // namespace Graphics namespace Graphics { - ImplementBitflagEnum(ModifierKeys); + ImplementBitflagEnum(ModifierKeys); } diff --git a/Graphics/stdafx.cpp b/Graphics/stdafx.cpp index a1837abf4..79afb0243 100644 --- a/Graphics/stdafx.cpp +++ b/Graphics/stdafx.cpp @@ -1,2 +1,2 @@ /* Used to generate precompiled header file for Main project*/ -#include "stdafx.h" \ No newline at end of file +#include "stdafx.h" diff --git a/Main/CMakeLists.txt b/Main/CMakeLists.txt index d563f56d3..74216bada 100644 --- a/Main/CMakeLists.txt +++ b/Main/CMakeLists.txt @@ -54,7 +54,6 @@ target_compile_features(usc-game PUBLIC cxx_std_17) target_compile_definitions(usc-game PRIVATE VERSION_MINOR=${PROJECT_VERSION_MINOR}) target_compile_definitions(usc-game PRIVATE VERSION_MAJOR=${PROJECT_VERSION_MAJOR}) target_compile_definitions(usc-game PRIVATE VERSION_PATCH=${PROJECT_VERSION_PATCH}) -find_package (Git) if(GIT_FOUND) target_compile_definitions(usc-game PRIVATE GIT_COMMIT=${GIT_DATE_HASH}) execute_process( diff --git a/Main/include/Application.hpp b/Main/include/Application.hpp index 524f985b7..e55729133 100644 --- a/Main/include/Application.hpp +++ b/Main/include/Application.hpp @@ -21,189 +21,189 @@ class SharedTexture; class Application { public: - Application(); - ~Application(); + Application(); + ~Application(); - struct CachedJacketImage - { - float lastUsage; - int texture; - bool loaded = false; - Job loadingJob; - }; + struct CachedJacketImage + { + float lastUsage; + int texture; + bool loaded = false; + Job loadingJob; + }; - void ApplySettings(); - // Runs the application - int32 Run(); + void ApplySettings(); + // Runs the application + int32 Run(); - void SetCommandLine(int32 argc, char** argv); - void SetCommandLine(const char* cmdLine); + void SetCommandLine(int32 argc, char** argv); + void SetCommandLine(const char* cmdLine); - class Game* LaunchMap(const String& mapPath); - class Game* LaunchReplay(const String& replayPath, MapDatabase** database = nullptr); - void Shutdown(); + class Game* LaunchMap(const String& mapPath); + class Game* LaunchReplay(const String& replayPath, MapDatabase** database = nullptr); + void Shutdown(); - void AddTickable(class IApplicationTickable* tickable, class IApplicationTickable* insertBefore = nullptr); - void RemoveTickable(class IApplicationTickable* tickable, bool noDelete = false); + void AddTickable(class IApplicationTickable* tickable, class IApplicationTickable* insertBefore = nullptr); + void RemoveTickable(class IApplicationTickable* tickable, bool noDelete = false); - // Current running map path (full file path) - String GetCurrentMapPath(); + // Current running map path (full file path) + String GetCurrentMapPath(); - // Current loaded skin; - String GetCurrentSkin(); + // Current loaded skin; + String GetCurrentSkin(); - // Retrieves application command line parameters - const Vector& GetAppCommandLine() const; + // Retrieves application command line parameters + const Vector& GetAppCommandLine() const; - // Gets a basic template for a render state, with all the application variables initialized - RenderState GetRenderStateBase() const; - RenderQueue* GetRenderQueueBase(); + // Gets a basic template for a render state, with all the application variables initialized + RenderState GetRenderStateBase() const; + RenderQueue* GetRenderQueueBase(); #ifdef LoadImage #undef LoadImage #endif - Image LoadImage(const String& name); - Graphics::Image LoadImageExternal(const String & name); - Texture LoadTexture(const String& name); - Texture LoadTexture(const String & name, const bool& external); - Material LoadMaterial(const String& name); - Material LoadMaterial(const String& name, const String& path); - Sample LoadSample(const String& name, const bool& external = false); - Graphics::Font LoadFont(const String& name, const bool& external = false); - int LoadImageJob(const String& path, Vector2i size, int placeholder, const bool& web = false); - void SetScriptPath(lua_State* L); - - - // Called when a pcall fails, returns true if the script was reloaded - bool ScriptError(const String& name, lua_State* L); - - lua_State* LoadScript(const String& name, bool noError = false); - bool ReloadScript(const String& name, lua_State* L); - - void WarnGauge(); - int FastText(String text, float x, float y, int size, int align, const Color& color = Color::White); - float GetAppTime() const { return m_appTime; } - float GetRenderFPS() const; - Material GetFontMaterial() const; - Material GetGuiTexMaterial() const; - Material GetGuiFillMaterial() const; - Transform GetGUIProjection() const; - Transform GetCurrentGUITransform() const; - Rect GetCurrentGUIScissor() const; - void StoreNamedSample(String name, Sample sample); - void PlayNamedSample(String name, bool loop); - void StopNamedSample(String name); - // -1 if no sample exists, 0 if stopped, 1 if playing - int IsNamedSamplePlaying(String name); - void ReloadSkin(); - bool ReloadConfig(const String& profile = ""); - void DisposeLua(lua_State* state); - void DiscordError(int errorCode, const char* message); - void DiscordPresenceMenu(String name); - void DiscordPresenceMulti(String secret, int partySize, int partyMax, String id); - void DiscordPresenceSong(const struct BeatmapSettings& song, int64 startTime, int64 endTime); - void JoinMultiFromInvite(String secret); - void SetUpdateAvailable(const String& version, const String& url, const String& download); - void RunUpdater(); - void CheckForUpdate(); - void ForceRender(); - void SetLuaBindings(struct lua_State* state); - void RenderTickables(); - struct NVGcontext* GetVGContext(); - - //if empty: no update avaiable - //else: index 0 = url, index 1 = version - Vector GetUpdateAvailable(); - - AutoplayInfo* autoplayInfo = nullptr; - Map> sharedTextures; - + Image LoadImage(const String& name); + Graphics::Image LoadImageExternal(const String& name); + Texture LoadTexture(const String& name); + Texture LoadTexture(const String& name, const bool& external); + Material LoadMaterial(const String& name); + Material LoadMaterial(const String& name, const String& path); + Sample LoadSample(const String& name, const bool& external = false); + Graphics::Font LoadFont(const String& name, const bool& external = false); + int LoadImageJob(const String& path, Vector2i size, int placeholder, const bool& web = false); + void SetScriptPath(lua_State* L); + + + // Called when a pcall fails, returns true if the script was reloaded + bool ScriptError(const String& name, lua_State* L); + + lua_State* LoadScript(const String& name, bool noError = false); + bool ReloadScript(const String& name, lua_State* L); + + void WarnGauge(); + int FastText(String text, float x, float y, int size, int align, const Color& color = Color::White); + float GetAppTime() const { return m_appTime; } + float GetRenderFPS() const; + Material GetFontMaterial() const; + Material GetGuiTexMaterial() const; + Material GetGuiFillMaterial() const; + Transform GetGUIProjection() const; + Transform GetCurrentGUITransform() const; + Rect GetCurrentGUIScissor() const; + void StoreNamedSample(String name, Sample sample); + void PlayNamedSample(String name, bool loop); + void StopNamedSample(String name); + // -1 if no sample exists, 0 if stopped, 1 if playing + int IsNamedSamplePlaying(String name); + void ReloadSkin(); + bool ReloadConfig(const String& profile = ""); + void DisposeLua(lua_State* state); + void DiscordError(int errorCode, const char* message); + void DiscordPresenceMenu(String name); + void DiscordPresenceMulti(String secret, int partySize, int partyMax, String id); + void DiscordPresenceSong(const struct BeatmapSettings& song, int64 startTime, int64 endTime); + void JoinMultiFromInvite(String secret); + void SetUpdateAvailable(const String& version, const String& url, const String& download); + void RunUpdater(); + void CheckForUpdate(); + void ForceRender(); + void SetLuaBindings(struct lua_State* state); + void RenderTickables(); + struct NVGcontext* GetVGContext(); + + //if empty: no update avaiable + //else: index 0 = url, index 1 = version + Vector GetUpdateAvailable(); + + AutoplayInfo* autoplayInfo = nullptr; + Map> sharedTextures; + private: - bool m_LoadConfig(String profileName = ""); - void m_UpdateConfigVersion(); - void m_SaveConfig(); - void m_InitDiscord(); - bool m_Init(); - void m_MainLoop(); - void m_Tick(); - void m_Cleanup(); - void m_OnKeyPressed(SDL_Scancode code, int32 delta); - void m_OnKeyReleased(SDL_Scancode code, int32 delta); - void m_UpdateWindowPosAndShape(); - void m_UpdateWindowPosAndShape(int32 monitorId, bool fullscreen, bool ensureInBound); - void m_OnWindowResized(const Vector2i& newSize); - void m_OnWindowMoved(const Vector2i& newPos); - void m_OnFocusChanged(bool focused); - void m_unpackSkins(); - void m_loadResponsiveInputSetting(); - - RenderState m_renderStateBase; - RenderQueue m_renderQueueBase; - Vector m_commandLine; - Map m_fonts; - Map m_samples; - Material m_fontMaterial; - Material m_fillMaterial; - Material m_guiTex; - Map m_jacketImages; - String m_lastMapPath; - Thread m_updateThread; - Thread m_fontBakeThread; - class Beatmap* m_currentMap = nullptr; - SkinHttp m_skinHttp; - SkinIR m_skinIR; - Timer m_frameTimer; - uint32 m_targetRenderTime; - - float m_deltaTime; - float m_fpsTargetSleepMult = 1.0f; - float m_appTime; - bool m_allowMapConversion; - bool m_hasUpdate = false; - bool m_showFps = false; - String m_updateUrl; - String m_updateDownload; - String m_updateVersion; - String m_currentVersion; - String m_skin; - bool m_needSkinReload = false; - Timer m_jobTimer; - //gauge colors, 0 = normal fail, 1 = normal clear, 2 = hard lower, 3 = hard upper - Color m_gaugeColors[4] = { Colori(0, 204, 255), Colori(255, 102, 255), Colori(200, 50, 0), Colori(255, 100, 0) }; - - String m_multiRoomSecret; - String m_multiRoomId; - int m_multiRoomSize = 0; - int m_multiRoomCount = 0; - bool m_gaugeRemovedWarn = true; - bool m_responsiveInputs = true; - int m_responsiveInputsSleep = 1; - - Thread m_renderThread; + bool m_LoadConfig(String profileName = ""); + void m_UpdateConfigVersion(); + void m_SaveConfig(); + void m_InitDiscord(); + bool m_Init(); + void m_MainLoop(); + void m_Tick(); + void m_Cleanup(); + void m_OnKeyPressed(SDL_Scancode code, int32 delta); + void m_OnKeyReleased(SDL_Scancode code, int32 delta); + void m_UpdateWindowPosAndShape(); + void m_UpdateWindowPosAndShape(int32 monitorId, bool fullscreen, bool ensureInBound); + void m_OnWindowResized(const Vector2i& newSize); + void m_OnWindowMoved(const Vector2i& newPos); + void m_OnFocusChanged(bool focused); + void m_unpackSkins(); + void m_loadResponsiveInputSetting(); + + RenderState m_renderStateBase; + RenderQueue m_renderQueueBase; + Vector m_commandLine; + Map m_fonts; + Map m_samples; + Material m_fontMaterial; + Material m_fillMaterial; + Material m_guiTex; + Map m_jacketImages; + String m_lastMapPath; + Thread m_updateThread; + Thread m_fontBakeThread; + class Beatmap* m_currentMap = nullptr; + SkinHttp m_skinHttp; + SkinIR m_skinIR; + Timer m_frameTimer; + uint32 m_targetRenderTime; + + float m_deltaTime; + float m_fpsTargetSleepMult = 1.0f; + float m_appTime; + bool m_allowMapConversion; + bool m_hasUpdate = false; + bool m_showFps = false; + String m_updateUrl; + String m_updateDownload; + String m_updateVersion; + String m_currentVersion; + String m_skin; + bool m_needSkinReload = false; + Timer m_jobTimer; + //gauge colors, 0 = normal fail, 1 = normal clear, 2 = hard lower, 3 = hard upper + Color m_gaugeColors[4] = { Colori(0, 204, 255), Colori(255, 102, 255), Colori(200, 50, 0), Colori(255, 100, 0) }; + + String m_multiRoomSecret; + String m_multiRoomId; + int m_multiRoomSize = 0; + int m_multiRoomCount = 0; + bool m_gaugeRemovedWarn = true; + bool m_responsiveInputs = true; + int m_responsiveInputsSleep = 1; + + Thread m_renderThread; }; class JacketLoadingJob : public JobBase { public: - virtual bool Run(); - virtual void Finalize(); - - Image loadedImage; - String imagePath; - int w = 0, h = 0; - bool web = false; - Application::CachedJacketImage* target; + virtual bool Run(); + virtual void Finalize(); + + Image loadedImage; + String imagePath; + int w = 0, h = 0; + bool web = false; + Application::CachedJacketImage* target; }; void __discordJoinGame(const char* joins); class SharedTexture { public: - SharedTexture() = default; - ~SharedTexture(); - bool Valid(); - int nvgTexture = 0; - Texture texture; -}; \ No newline at end of file + SharedTexture() = default; + ~SharedTexture(); + bool Valid(); + int nvgTexture = 0; + Texture texture; +}; diff --git a/Main/include/ApplicationTickable.hpp b/Main/include/ApplicationTickable.hpp index 97b1a9308..5dd061ce6 100644 --- a/Main/include/ApplicationTickable.hpp +++ b/Main/include/ApplicationTickable.hpp @@ -3,34 +3,34 @@ class IApplicationTickable { public: - virtual ~IApplicationTickable() = default; - // Run to call initialization code - virtual bool DoInit(); - // Override for initialization - virtual bool Init() { return true; } - // Tick for tickable - virtual void Tick(float deltaTime) {}; - virtual void Render(float deltaTime) {}; - virtual void ForceRender(float deltaTime) { Render(deltaTime); }; - virtual void OnKeyPressed(SDL_Scancode code, int32 delta) {}; - virtual void OnKeyReleased(SDL_Scancode code, int32 delta) {}; - // Called when focus of this item is lost - virtual void OnSuspend() {}; - // Called when focus to this item is restored - virtual void OnRestore() {}; + virtual ~IApplicationTickable() = default; + // Run to call initialization code + virtual bool DoInit(); + // Override for initialization + virtual bool Init() { return true; } + // Tick for tickable + virtual void Tick(float deltaTime) {}; + virtual void Render(float deltaTime) {}; + virtual void ForceRender(float deltaTime) { Render(deltaTime); }; + virtual void OnKeyPressed(SDL_Scancode code, int32 delta) {}; + virtual void OnKeyReleased(SDL_Scancode code, int32 delta) {}; + // Called when focus of this item is lost + virtual void OnSuspend() {}; + // Called when focus to this item is restored + virtual void OnRestore() {}; - // Return true to override application ticking behaviour - virtual bool GetTickRate(int32& rate) { return false; }; + // Return true to override application ticking behaviour + virtual bool GetTickRate(int32& rate) { return false; }; - bool IsSuspended() const { return m_suspended; } - bool IsSuccessfullyInitialized() const { return m_successfullyInitialized; } + bool IsSuspended() const { return m_suspended; } + bool IsSuccessfullyInitialized() const { return m_successfullyInitialized; } private: - void m_Suspend(); - void m_Restore(); + void m_Suspend(); + void m_Restore(); - bool m_successfullyInitialized = false; - bool m_initialized = false; - bool m_suspended = true; - friend class Application; + bool m_successfullyInitialized = false; + bool m_initialized = false; + bool m_suspended = true; + friend class Application; }; diff --git a/Main/include/AsyncAssetLoader.hpp b/Main/include/AsyncAssetLoader.hpp index 0bbe4c946..befb4bf1e 100644 --- a/Main/include/AsyncAssetLoader.hpp +++ b/Main/include/AsyncAssetLoader.hpp @@ -2,28 +2,28 @@ #include "AsyncLoadable.hpp" /* - Loads assets and IAsyncLoadables - Acts like a queue that stores loading commands + Loads assets and IAsyncLoadables + Acts like a queue that stores loading commands */ class AsyncAssetLoader : public Unique { public: - AsyncAssetLoader(); - ~AsyncAssetLoader(); + AsyncAssetLoader(); + ~AsyncAssetLoader(); - /// NOTE: the caller is responsible for keeping the passed in variable valid the this object is destroyed or finished with the loading - // Add a texture to be loaded - void AddTexture(Texture& out, const String& path); - // Add a mesh to be loaded - void AddMesh(Mesh& out, const String& path); - // Add a mesh to be loaded - void AddMaterial(Material& out, const String& path); - // Add a loadable to be loaded, additionaly with a name so it can be identified in logs if it fails loading - void AddLoadable(IAsyncLoadable& loadable, const String& id = "unknown"); + /// NOTE: the caller is responsible for keeping the passed in variable valid the this object is destroyed or finished with the loading + // Add a texture to be loaded + void AddTexture(Texture& out, const String& path); + // Add a mesh to be loaded + void AddMesh(Mesh& out, const String& path); + // Add a mesh to be loaded + void AddMaterial(Material& out, const String& path); + // Add a loadable to be loaded, additionaly with a name so it can be identified in logs if it fails loading + void AddLoadable(IAsyncLoadable& loadable, const String& id = "unknown"); - bool Load(); - bool Finalize(); + bool Load(); + bool Finalize(); private: - class AsyncAssetLoader_Impl* m_impl; -}; \ No newline at end of file + class AsyncAssetLoader_Impl* m_impl; +}; diff --git a/Main/include/AsyncLoadable.hpp b/Main/include/AsyncLoadable.hpp index bd676b0e5..914c7472b 100644 --- a/Main/include/AsyncLoadable.hpp +++ b/Main/include/AsyncLoadable.hpp @@ -2,25 +2,25 @@ #include "ApplicationTickable.hpp" /* - Used for assets that can be loaded asynchronously - the interface provides a Load and Finalize function - the Load function is called on another thread than the main thread - the Finalize function is called on the main thread once the load function completed (only if succeeded) + Used for assets that can be loaded asynchronously + the interface provides a Load and Finalize function + the Load function is called on another thread than the main thread + the Finalize function is called on the main thread once the load function completed (only if succeeded) */ class IAsyncLoadable { public: - virtual ~IAsyncLoadable() = default; - // Loads this object - // returns success - virtual bool AsyncLoad() = 0; - // Finalize loading of this object on the main thread - // for example, any OpenGL stuff - // returns success - virtual bool AsyncFinalize() = 0; + virtual ~IAsyncLoadable() = default; + // Loads this object + // returns success + virtual bool AsyncLoad() = 0; + // Finalize loading of this object on the main thread + // for example, any OpenGL stuff + // returns success + virtual bool AsyncFinalize() = 0; }; // Both an application tickable and async loadable class IAsyncLoadableApplicationTickable : public IAsyncLoadable, public IApplicationTickable { -}; \ No newline at end of file +}; diff --git a/Main/include/Audio/AudioPlayback.hpp b/Main/include/Audio/AudioPlayback.hpp index 039f667a0..d74833a3b 100644 --- a/Main/include/Audio/AudioPlayback.hpp +++ b/Main/include/Audio/AudioPlayback.hpp @@ -4,124 +4,124 @@ #include