diff --git a/C++ProjectTemplate b/C++ProjectTemplate index 9d73bb6..aa8ddc0 100755 Binary files a/C++ProjectTemplate and b/C++ProjectTemplate differ diff --git a/assets/terrainmap.png b/assets/terrainmap.png new file mode 100644 index 0000000..c457ddd Binary files /dev/null and b/assets/terrainmap.png differ diff --git a/ideas b/ideas new file mode 100644 index 0000000..09f2ad6 --- /dev/null +++ b/ideas @@ -0,0 +1,71 @@ +8 Sep 2022 - 8 Jan 2023 + + +4 Months of Development Time +120 Days of Development Time +15+ Hours of Gameplay + +4 Different Games (4 Seasons) + +January of 2024 The Last Game of the Series will be completed. + + +1st Game: +-Full featured Turn-based RPG Engine + +Takeaways from Seasons of Loneliness: + + Cutscene / Game State Management + + On-the-fly cutscene control flags, temporary cutscene object creation, timers + + Object Management + + Message Display Systems + + Turn-based Combat Control Systems + + One-button input style + + Music & Sound + + Personalized Touch + + Growth System + + Strategic Elements + + No "Needless" Encounters + - Map System + - Event/Timeline System + - Player Drawing being tied to the camera + + +Season I: Winters of Loneliness +=============================== +We will name important characters at the beginning of the game. + +Game starts in Eagleland. Ness and Paula are out late at night and left Anna at home. + +When they don't show up and it's past 1AM, Anna attempts to communicate to them using psikokinetic powers. +When the call does not yield any response, Anna heads out to investigate... + +After exploring for awhile, she stumbles upon a strange weather field. Upon curiously touching it it drains all her PP and she is left unable to use abilities and helpless. + +An encounter begins against a Starman Deluxe, and a surprise visit from Buzz Buzz Mk II helps Anna out. Until Buzz Buzz Mk II eventually gets swatted by Starman Deluxe leaving her at the brink of getting kidnapped. However, something happens... + +Miraculously, a space satellite (?) falls out of the sky, crashes at the site of the battle and +a character pops out to join Anna and protect her using weather powers. + +These new powers are alien to the world, and while defenses have been developed for psykokinetic powers, there are no such defenses against weather itself. The character is able to leverage this to their advantage to win the fight. + +Afterwards, Anna thanks PLAYER and heads on her merry way, but the PLAYER stops her, asking her what she is doing and what if she gets in trouble again... + +Essentially as a bodyguard, the PLAYER decides to stick around and help Anna find both Ness and Paula. And so their journey begins... + +(You will have to help Buzz Buzz Mk II to learn about their "destiny".) + + +Weather power uses are no longer bound by finding nodes, but by using magic points (which regenerates over time). + +Discovering new nodes now unlocks that power, and potentially certain nodes will unlock powers for other characters. + + +"Climate System" +Wet - Determines how wet the area is. While wet, seeds can grow and healing is enhanced. +Dry - During dry climates, healing is reduced. + +Heat - Increases the power of fire/hot weather powers while causing trees to burn, causing raging fires and explosion damage. +Cold - Causes enemies to be slowed, prioritizing PC characters to go first. Prevents seed growth. + +"Town Climate" +Does not get affected as quickly by weather affects during battles, but field effects will dramatically affect them as it will be concentrated. Changing the weather in areas may trigger new paths or close off certain ones. \ No newline at end of file diff --git a/main.cpp b/main.cpp index 4141020..639ad23 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,9 @@ #define OLC_PGE_APPLICATION #include "pixelGameEngine.h" +#define OLC_PGEX_SPLASHSCREEN +#include "splash.h" +#define OLC_SOUNDWAVE +#include "soundwaveEngine.h" using namespace std; @@ -8,66 +12,45 @@ class Example : public olc::PixelGameEngine public: Example() { - sAppName = "Example"; + sAppName = "Season I: Winters of Loneliness"; } public: - vector data; - int TILE_WIDTH=16; - int TILE_HEIGHT=16; - - int tileOffsetX=0; - int tileOffsetY=0; - int TV_WIDTH=TILE_WIDTH*8; - int TV_HEIGHT=TILE_HEIGHT*7; - int MAP_WIDTH=256; - int MAP_HEIGHT=240; - - int TV_POSX=256/4; - int TV_POSY=240/4; + int frameCount=0; + float elapsedTime=0; + const float TARGET_RATE = 1/60.0; bool OnUserCreate() override { SetPixelMode(olc::Pixel::ALPHA); ConsoleCaptureStdOut(true); // Called once at the start, so create things here - for (int i=0;i<256*240;i++) { - data.push_back(rand()%255); - } return true; } bool OnUserUpdate(float fElapsedTime) override { - - if (GetKey(olc::RIGHT).bPressed) { - tileOffsetX=std::clamp(tileOffsetX+1,0,MAP_WIDTH); - } - if (GetKey(olc::LEFT).bPressed) { - tileOffsetX=std::clamp(tileOffsetX-1,0,MAP_WIDTH); - } - if (GetKey(olc::UP).bPressed) { - tileOffsetY=std::clamp(tileOffsetY-1,0,MAP_HEIGHT); - } - if (GetKey(olc::DOWN).bPressed) { - tileOffsetY=std::clamp(tileOffsetY+1,0,MAP_HEIGHT); - } - for (int x=0;xTARGET_RATE) { + elapsedTime-=TARGET_RATE; + updateGame(); } + drawGame(); return true; } + + void updateGame(){ + + }; + void drawGame(){ + + }; }; int main() { Example demo; - if (demo.Construct(256, 240, 4, 4)) + if (demo.Construct(256, 224, 4, 4)) demo.Start(); return 0; diff --git a/pixelGameEngine.h b/pixelGameEngine.h index 37ffb75..3e3b890 100644 --- a/pixelGameEngine.h +++ b/pixelGameEngine.h @@ -928,6 +928,8 @@ namespace olc virtual bool OnUserUpdate(float fElapsedTime); // Called once on application termination, so you can be one clean coder virtual bool OnUserDestroy(); + virtual void GetAnyKey(); + virtual void GetAnyKeyPress(); // Called when a text entry is confirmed with "enter" key virtual void OnTextEntryComplete(const std::string& sText); @@ -3290,6 +3292,9 @@ namespace olc bool PixelGameEngine::OnUserDestroy() { return true; } + void PixelGameEngine::GetAnyKey(){}; + void PixelGameEngine::GetAnyKeyPress(){}; + void PixelGameEngine::OnTextEntryComplete(const std::string& sText) { UNUSED(sText); } bool PixelGameEngine::OnConsoleCommand(const std::string& sCommand) { UNUSED(sCommand); return false; } @@ -3437,6 +3442,7 @@ namespace olc // Compare hardware input states from previous frame auto ScanHardware = [&](HWButton* pKeys, bool* pStateOld, bool* pStateNew, uint32_t nKeyCount) { + bool pressed=false; for (uint32_t i = 0; i < nKeyCount; i++) { pKeys[i].bPressed = false; @@ -3445,6 +3451,7 @@ namespace olc { if (pStateNew[i]) { + pressed=true; pKeys[i].bPressed = !pKeys[i].bHeld; pKeys[i].bHeld = true; } @@ -3453,9 +3460,11 @@ namespace olc pKeys[i].bReleased = true; pKeys[i].bHeld = false; } + GetAnyKey(); } pStateOld[i] = pStateNew[i]; } + if (pressed) {GetAnyKeyPress();} }; ScanHardware(pKeyboardState, pKeyOldState, pKeyNewState, 256); diff --git a/soundwaveEngine.h b/soundwaveEngine.h new file mode 100644 index 0000000..e482440 --- /dev/null +++ b/soundwaveEngine.h @@ -0,0 +1,1963 @@ +/* + +-------------------------------------------------------------+ + | OneLoneCoder Sound Wave Engine v0.02 | + | "You wanted noise? Well is this loud enough?" - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + olc::SoundWaveEngine is a single file, cross platform audio + interface for lightweight applications that just need a bit of + easy audio manipulation. + + It's origins started in the olcNoiseMaker file that accompanied + javidx9's "Code-It-Yourself: Synthesizer" series. It was refactored + and absorbed into the "olcConsoleGameEngine.h" file, and then + refactored again into olcPGEX_Sound.h, that was an extension to + the awesome "olcPixelGameEngine.h" file. + + Alas, it went underused and began to rot, with many myths circulating + that "it doesnt work" and "it shouldn't be used". These untruths + made javidx9 feel sorry for the poor file, and he decided to breathe + some new life into it, in anticipation of new videos! + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2022 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met : + + 1. Redistributions or derivations of source code must retain the above + copyright notice, this list of conditionsand the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce + the above copyright notice.This list of conditions and the following + disclaimer must be reproduced in the documentation and /or other + materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + + Thanks + ~~~~~~ + Gorbit99, Dragoneye, Puol + + Authors + ~~~~~~~ + slavka, MaGetzUb, cstd, Moros1138 & javidx9 + + (c)OneLoneCoder 2019, 2020, 2021, 2022 +*/ + + +/* + Using & Installing On Microsoft Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Microsoft Visual Studio + ~~~~~~~~~~~~~~~~~~~~~~~ + 1) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project. + 2) That's it! + + + Code::Blocks + ~~~~~~~~~~~~ + 1) Make sure your compiler toolchain is NOT the default one installed with Code::Blocks. That + one is old, out of date, and a general mess. Instead, use MSYS2 to install a recent and + decent GCC toolchain, then configure Code::Blocks to use it + + Guide for installing recent GCC for Windows: + https://www.msys2.org/ + Guide for configuring code::blocks: + https://solarianprogrammer.com/2019/11/05/install-gcc-windows/ + https://solarianprogrammer.com/2019/11/16/install-codeblocks-gcc-windows-build-c-cpp-fortran-programs/ + + 2) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project. + 3) Add these libraries to "Linker Options": user32 winmm + 4) Set this "Compiler Option": -std=c++17 +*/ + +/* + Using & Installing On Linux + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + GNU Compiler Collection (GCC) + ~~~~~~~~~~~~~~~~~~~~~~~ + 1) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project. + 2) Build with the following command: + + g++ olcSoundWaveEngineExample.cpp -o olcSoundWaveEngineExample -lpulse -lpulse-simple -std=c++17 + + 3) That's it! + +*/ + +/* + Using in multiple-file projects + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + If you intend to use olcSoundWaveEngine across multiple files, it's important to only have one + instance of the implementation. This is done using the compiler preprocessor definition: OLC_SOUNDWAVE + + This is defined typically before the header is included in teh translation unit you wish the implementation + to be associated with. To avoid things getting messy I recommend you create a file "olcSoundWaveEngine.cpp" + and that file includes ONLY the following code: + + #define OLC_SOUNDWAVE + #include "olcSoundWaveEngine.h" +*/ + +/* + 0.01: olcPGEX_Sound.h reworked + +Changed timekeeping to double, added accuracy fix - Thanks scripticuk + +Concept of audio drivers and interface + +All internal timing now double precision + +All internal sampling now single precsion + +Loading form WAV files + +LERPed sampling from all buffers + +Multi-channel audio support + 0.02: +Support multi-channel wave files + +Support for 24-bit wave files + +Wave files are now sample rate invariant + +Linux PulseAudio Updated + +Linux ALSA Updated + +WinMM Updated + +CMake Compatibility + =Fix wave format durations preventing playback + =Various bug fixes +*/ + +#pragma once +#ifndef OLC_SOUNDWAVE_H +#define OLC_SOUNDWAVE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Compiler/System Sensitivity +#if !defined(SOUNDWAVE_USING_WINMM) && !defined(SOUNDWAVE_USING_WASAPI) && \ + !defined(SOUNDWAVE_USING_XAUDIO) && !defined(SOUNDWAVE_USING_OPENAL) && \ + !defined(SOUNDWAVE_USING_ALSA) && !defined(SOUNDWAVE_USING_SDLMIXER) && \ + !defined(SOUNDWAVE_USING_PULSE) \ + + #if defined(_WIN32) + #define SOUNDWAVE_USING_WINMM + #endif + #if defined(__linux__) + #define SOUNDWAVE_USING_PULSE + #endif + #if defined(__APPLE__) + #define SOUNDWAVE_USING_SDLMIXER + #endif + #if defined(__EMSCRIPTEN__) + #define SOUNDWAVE_USING_SDLMIXER + #endif + +#endif + +namespace olc::sound +{ + + namespace wave + { + // Physically represents a .WAV file, but the data is stored + // as normalised floating point values + template + class File + { + public: + File() = default; + + File(const size_t nChannels, const size_t nSampleSize, const size_t nSampleRate, const size_t nSamples) + { + m_nChannels = nChannels; + m_nSampleSize = nSampleSize; + m_nSamples = nSamples; + m_nSampleRate = nSampleRate; + m_dDuration = double(m_nSamples) / double(m_nSampleRate); + m_dDurationInSamples = double(m_nSamples); + + m_pRawData = std::make_unique(m_nSamples * m_nChannels); + } + + public: + T* data() const + { + return m_pRawData.get(); + } + + size_t samples() const + { + return m_nSamples; + } + + size_t channels() const + { + return m_nChannels; + } + + size_t samplesize() const + { + return m_nSampleSize; + } + + size_t samplerate() const + { + return m_nSampleRate; + } + + double duration() const + { + return m_dDuration; + } + + double durationInSamples() const + { + return m_dDurationInSamples; + } + + bool LoadFile(const std::string& sFilename) + { + std::ifstream ifs(sFilename, std::ios::binary); + if (!ifs.is_open()) + return false; + + struct WaveFormatHeader + { + uint16_t wFormatTag; /* format type */ + uint16_t nChannels; /* number of channels (i.e. mono, stereo...) */ + uint32_t nSamplesPerSec; /* sample rate */ + uint32_t nAvgBytesPerSec; /* for buffer estimation */ + uint16_t nBlockAlign; /* block size of data */ + uint16_t wBitsPerSample; /* number of bits per sample of mono data */ + }; + + WaveFormatHeader header{ 0 }; + + m_pRawData.reset(); + + char dump[4]; + ifs.read(dump, sizeof(uint8_t) * 4); // Read "RIFF" + if (strncmp(dump, "RIFF", 4) != 0) return false; + + ifs.read(dump, sizeof(uint8_t) * 4); // Not Interested + ifs.read(dump, sizeof(uint8_t) * 4); // Read "WAVE" + if (strncmp(dump, "WAVE", 4) != 0) return false; + + // Read Wave description chunk + ifs.read(dump, sizeof(uint8_t) * 4); // Read "fmt " + ifs.read(dump, sizeof(uint8_t) * 4); // Not Interested + ifs.read((char*)&header, sizeof(WaveFormatHeader)); // Read Wave Format Structure chunk + + // Search for audio data chunk + int32_t nChunksize = 0; + ifs.read(dump, sizeof(uint8_t) * 4); // Read chunk header + ifs.read((char*)&nChunksize, sizeof(uint32_t)); // Read chunk size + + while (strncmp(dump, "data", 4) != 0) + { + // Not audio data, so just skip it + ifs.seekg(nChunksize, std::ios::cur); + ifs.read(dump, sizeof(uint8_t) * 4); // Read next chunk header + ifs.read((char*)&nChunksize, sizeof(uint32_t)); // Read next chunk size + } + + // Finally got to data, so read it all in and convert to float samples + m_nSampleSize = header.wBitsPerSample >> 3; + m_nSamples = nChunksize / (header.nChannels * m_nSampleSize); + m_nChannels = header.nChannels; + m_nSampleRate = header.nSamplesPerSec; + m_pRawData = std::make_unique(m_nSamples * m_nChannels); + m_dDuration = double(m_nSamples) / double(m_nSampleRate); + m_dDurationInSamples = double(m_nSamples); + + T* pSample = m_pRawData.get(); + + // Read in audio data and normalise + for (long i = 0; i < m_nSamples; i++) + { + for (int c = 0; c < m_nChannels; c++) + { + switch (m_nSampleSize) + { + case 1: + { + int8_t s = 0; + ifs.read((char*)&s, sizeof(int8_t)); + *pSample = T(s) / T(std::numeric_limits::max()); + } + break; + + case 2: + { + int16_t s = 0; + ifs.read((char*)&s, sizeof(int16_t)); + *pSample = T(s) / T(std::numeric_limits::max()); + } + break; + + case 3: // 24-bit + { + int32_t s = 0; + ifs.read((char*)&s, 3); + if (s & (1 << 23)) s |= 0xFF000000; + *pSample = T(s) / T(std::pow(2, 23)-1); + } + break; + + case 4: + { + int32_t s = 0; + ifs.read((char*)&s, sizeof(int32_t)); + *pSample = T(s) / T(std::numeric_limits::max()); + } + break; + } + + pSample++; + } + } + return true; + } + + bool SaveFile(const std::string& sFilename) + { + return false; + } + + + protected: + std::unique_ptr m_pRawData; + size_t m_nSamples = 0; + size_t m_nChannels = 0; + size_t m_nSampleRate = 0; + size_t m_nSampleSize = 0; + double m_dDuration = 0.0; + double m_dDurationInSamples = 0.0; + }; + + template + class View + { + public: + View() = default; + + View(const T* pData, const size_t nSamples) + { + SetData(pData, nSamples); + } + + public: + void SetData(T const* pData, const size_t nSamples, const size_t nStride = 1, const size_t nOffset = 0) + { + m_pData = pData; + m_nSamples = nSamples; + m_nStride = nStride; + m_nOffset = nOffset; + } + + double GetSample(const double dSample) const + { + double d1 = std::floor(dSample); + size_t p1 = static_cast(d1); + size_t p2 = p1 + 1; + + double t = dSample - d1; + double a = GetValue(p1); + double b = GetValue(p2); + + return a + t * (b - a); // std::lerp in C++20 + } + + std::pair GetRange(const double dSample1, const double dSample2) const + { + if (dSample1 < 0 || dSample2 < 0) + return { 0,0 }; + + if (dSample1 > m_nSamples && dSample2 > m_nSamples) + return { 0,0 }; + + double dMin, dMax; + + double d = GetSample(dSample1); + dMin = dMax = d; + + size_t n1 = static_cast(std::ceil(dSample1)); + size_t n2 = static_cast(std::floor(dSample2)); + for (size_t n = n1; n < n2; n++) + { + d = GetValue(n); + dMin = std::min(dMin, d); + dMax = std::max(dMax, d); + } + + d = GetSample(dSample2); + dMin = std::min(dMin, d); + dMax = std::max(dMax, d); + + return { dMin, dMax }; + } + + T GetValue(const size_t nSample) const + { + if (nSample >= m_nSamples) + return 0; + else + return m_pData[m_nOffset + nSample * m_nStride]; + } + + private: + const T* m_pData = nullptr; + size_t m_nSamples = 0; + size_t m_nStride = 1; + size_t m_nOffset = 0; + }; + } + + template + class Wave_generic + { + public: + Wave_generic() = default; + Wave_generic(std::string sWavFile) { LoadAudioWaveform(sWavFile); } + Wave_generic(std::istream& sStream) { LoadAudioWaveform(sStream); } + Wave_generic(const char* pData, const size_t nBytes) { LoadAudioWaveform(pData, nBytes); } + + Wave_generic(const size_t nChannels, const size_t nSampleSize, const size_t nSampleRate, const size_t nSamples) + { + vChannelView.clear(); + file = wave::File(nChannels, nSampleSize, nSampleRate, nSamples); + vChannelView.resize(file.channels()); + for (uint32_t c = 0; c < file.channels(); c++) + vChannelView[c].SetData(file.data(), file.samples(), file.channels(), c); + } + + bool LoadAudioWaveform(std::string sWavFile) + { + vChannelView.clear(); + + if (file.LoadFile(sWavFile)) + { + // Setup views for each channel + vChannelView.resize(file.channels()); + for (uint32_t c = 0; c < file.channels(); c++) + vChannelView[c].SetData(file.data(), file.samples(), file.channels(), c); + + return true; + } + + return false; + } + + + + bool LoadAudioWaveform(std::istream& sStream) { return false; } + bool LoadAudioWaveform(const char* pData, const size_t nBytes) { return false; } + + std::vector> vChannelView; + wave::File file; + }; + + typedef Wave_generic Wave; + + struct WaveInstance + { + Wave* pWave = nullptr; + double dInstanceTime = 0.0; + double dDuration = 0.0; + double dSpeedModifier = 1.0; + bool bFinished = false; + bool bLoop = false; + bool bFlagForStop = false; + }; + + typedef std::list::iterator PlayingWave; + + namespace driver + { + class Base; + } + + // Container class for Basic Sound Manipulation + class WaveEngine + { + + public: + WaveEngine(); + virtual ~WaveEngine(); + + // Configure Audio Hardware + bool InitialiseAudio(uint32_t nSampleRate = 44100, uint32_t nChannels = 1, uint32_t nBlocks = 8, uint32_t nBlockSamples = 512); + + // Release Audio Hardware + bool DestroyAudio(); + + // Call to get the names of all the devices capable of audio output - DACs. An entry + // from the returned collection can be specified as the device to use in UseOutputDevice() + std::vector GetOutputDevices(); + + // Specify a device for audio output prior to calling InitialiseAudio() + void UseOutputDevice(const std::string& sDeviceOut); + + // Call to get the names of all the devices capable of audio input - ADCs. An entry + // from the returned collection can be specified as the device to use in UseInputDevice() + std::vector GetInputDevices(); + + // Specify a device for audio input prior to calling InitialiseAudio() + void UseInputDevice(const std::string& sDeviceOut); + + + void SetCallBack_NewSample(std::function func); + void SetCallBack_SynthFunction(std::function func); + void SetCallBack_FilterFunction(std::function func); + + public: + void SetOutputVolume(const float fVolume); + + + + PlayingWave PlayWaveform(Wave* pWave, bool bLoop = false, double dSpeed = 1.0); + void StopWaveform(const PlayingWave& w); + void StopAll(); + + private: + uint32_t FillOutputBuffer(std::vector& vBuffer, const uint32_t nBufferOffset, const uint32_t nRequiredSamples); + + private: + std::unique_ptr m_driver; + std::function m_funcNewSample; + std::function m_funcUserSynth; + std::function m_funcUserFilter; + + + private: + uint32_t m_nSampleRate = 44100; + uint32_t m_nChannels = 1; + uint32_t m_nBlocks = 8; + uint32_t m_nBlockSamples = 512; + double m_dSamplePerTime = 44100.0; + double m_dTimePerSample = 1.0 / 44100; + double m_dGlobalTime = 0.0; + float m_fOutputVolume = 1.0; + + std::string m_sInputDevice; + std::string m_sOutputDevice; + + private: + std::list m_listWaves; + + public: + uint32_t GetSampleRate() const; + uint32_t GetChannels() const; + uint32_t GetBlocks() const; + uint32_t GetBlockSampleCount() const; + double GetTimePerSample() const; + + + // Friends, for access to FillOutputBuffer from Drivers + friend class driver::Base; + + }; + + namespace driver + { + // DRIVER DEVELOPERS ONLY!!! + // + // This interface allows SoundWave to exchange data with OS audio systems. It + // is not intended of use by regular users. + class Base + { + public: + Base(WaveEngine* pHost); + virtual ~Base(); + + public: + // [IMPLEMENT] Opens a connection to the hardware device, returns true if success + virtual bool Open(const std::string& sOutputDevice, const std::string& sInputDevice); + // [IMPLEMENT] Starts a process that repeatedly requests audio, returns true if success + virtual bool Start(); + // [IMPLEMENT] Stops a process form requesting audio + virtual void Stop(); + // [IMPLEMENT] Closes any connections to hardware devices + virtual void Close(); + + virtual std::vector EnumerateOutputDevices(); + virtual std::vector EnumerateInputDevices(); + + protected: + // [IMPLEMENT IF REQUIRED] Called by driver to exchange data with SoundWave System. Your + // implementation will call this function providing a "DAC" buffer to be filled by + // SoundWave from a buffer of floats filled by the user. + void ProcessOutputBlock(std::vector& vFloatBuffer, std::vector& vDACBuffer); + + // [IMPLEMENT IF REQUIRED] Called by driver to exchange data with SoundWave System. + void GetFullOutputBlock(std::vector& vFloatBuffer); + + // Handle to SoundWave, to interrogate optons, and get user data + WaveEngine* m_pHost = nullptr; + }; + } + + + namespace synth + { + class Property + { + public: + double value = 0.0f; + + public: + Property() = default; + Property(double f); + + public: + Property& operator =(const double f); + }; + + + class Trigger + { + + }; + + + class Module + { + public: + virtual void Update(uint32_t nChannel, double dTime, double dTimeStep) = 0; + }; + + + class ModularSynth + { + public: + ModularSynth(); + + public: + bool AddModule(Module* pModule); + bool RemoveModule(Module* pModule); + bool AddPatch(Property* pInput, Property* pOutput); + bool RemovePatch(Property* pInput, Property* pOutput); + + + public: + void UpdatePatches(); + void Update(uint32_t nChannel, double dTime, double dTimeStep); + + protected: + std::vector m_vModules; + std::vector> m_vPatches; + }; + + + namespace modules + { + class Oscillator : public Module + { + public: + enum class Type + { + Sine, + Saw, + Square, + Triangle, + PWM, + Wave, + Noise, + }; + + public: + // Primary frequency of oscillation + Property frequency = 0.0f; + // Primary amplitude of output + Property amplitude = 1.0f; + // LFO input if required + Property lfo_input = 0.0f; + // Primary Output + Property output; + // Tweakable Parameter + Property parameter = 0.0; + + Type waveform = Type::Sine; + + Wave* pWave = nullptr; + + private: + double phase_acc = 0.0f; + double max_frequency = 20000.0; + uint32_t random_seed = 0xB00B1E5; + + double rndDouble(double min, double max); + uint32_t rnd(); + + + public: + virtual void Update(uint32_t nChannel, double dTime, double dTimeStep) override; + + }; + } + } + + +} + +#if defined(SOUNDWAVE_USING_WINMM) +#define _WIN32_LEAN_AND_MEAN +#include +#undef min +#undef max + +namespace olc::sound::driver +{ + class WinMM : public Base + { + public: + WinMM(WaveEngine* pHost); + ~WinMM(); + + protected: + bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override; + bool Start() override; + void Stop() override; + void Close() override; + + private: + void DriverLoop(); + void FreeAudioBlock(); + static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2); + HWAVEOUT m_hwDevice = nullptr; + std::thread m_thDriverLoop; + std::atomic m_bDriverLoopActive{ false }; + std::unique_ptr[]> m_pvBlockMemory; + std::unique_ptr m_pWaveHeaders; + std::atomic m_nBlockFree = 0; + std::condition_variable m_cvBlockNotZero; + std::mutex m_muxBlockNotZero; + uint32_t m_nBlockCurrent = 0; + }; +} +#endif // SOUNDWAVE_USING_WINMM + +#if defined(SOUNDWAVE_USING_SDLMIXER) + +#if defined(__EMSCRIPTEN__) +#include +#else +#include +#endif + +namespace olc::sound::driver +{ + class SDLMixer final : public Base + { + public: + explicit SDLMixer(WaveEngine* pHost); + ~SDLMixer() final; + + protected: + bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) final; + bool Start() final; + void Stop() final; + void Close() final; + + private: + void FillChunkBuffer(const std::vector& userData) const; + + static void SDLMixerCallback(int channel); + + private: + bool m_keepRunning = false; + Uint16 m_haveFormat = AUDIO_F32SYS; + std::vector audioBuffer; + Mix_Chunk audioChunk; + + static SDLMixer* instance; + }; +} + +#endif // SOUNDWAVE_USING_SDLMIXER + +#if defined(SOUNDWAVE_USING_ALSA) +#include +#include +#include + +namespace olc::sound::driver +{ + // Not thread-safe + template + class RingBuffer + { + public: + RingBuffer() + { } + + void Resize(unsigned int bufnum = 0, unsigned int buflen = 0) + { + m_vBuffers.resize(bufnum); + for (auto &vBuffer : m_vBuffers) + vBuffer.resize(buflen); + } + + std::vector& GetFreeBuffer() + { + assert(!IsFull()); + + std::vector& result = m_vBuffers[m_nTail]; + m_nTail = Next(m_nTail); + return result; + } + + std::vector& GetFullBuffer() + { + assert(!IsEmpty()); + + std::vector& result = m_vBuffers[m_nHead]; + m_nHead = Next(m_nHead); + return result; + } + + bool IsEmpty() + { + return m_nHead == m_nTail; + } + + bool IsFull() + { + return m_nHead == Next(m_nTail); + } + + private: + unsigned int Next(unsigned int current) + { + return (current + 1) % m_vBuffers.size(); + } + + std::vector> m_vBuffers; + unsigned int m_nHead = 0; + unsigned int m_nTail = 0; + }; + + class ALSA : public Base + { + public: + ALSA(WaveEngine* pHost); + ~ALSA(); + + protected: + bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override; + bool Start() override; + void Stop() override; + void Close() override; + + private: + void DriverLoop(); + + snd_pcm_t *m_pPCM; + RingBuffer m_rBuffers; + std::atomic m_bDriverLoopActive{ false }; + std::thread m_thDriverLoop; + }; +} +#endif // SOUNDWAVE_USING_ALSA + +#if defined(SOUNDWAVE_USING_PULSE) +#include + +namespace olc::sound::driver +{ + class PulseAudio : public Base + { + public: + PulseAudio(WaveEngine* pHost); + ~PulseAudio(); + + protected: + bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override; + bool Start() override; + void Stop() override; + void Close() override; + + private: + void DriverLoop(); + + pa_simple *m_pPA; + std::atomic m_bDriverLoopActive{ false }; + std::thread m_thDriverLoop; + }; +} +#endif // SOUNDWAVE_USING_PULSE + +#ifdef OLC_SOUNDWAVE +#undef OLC_SOUNDWAVE + +namespace olc::sound +{ + WaveEngine::WaveEngine() + { + m_sInputDevice = "NONE"; + m_sOutputDevice = "DEFAULT"; + +#if defined(SOUNDWAVE_USING_WINMM) + m_driver = std::make_unique(this); +#endif + +#if defined(SOUNDWAVE_USING_WASAPI) + m_driver = std::make_unique(this); +#endif + +#if defined(SOUNDWAVE_USING_XAUDIO) + m_driver = std::make_unique(this); +#endif + +#if defined(SOUNDWAVE_USING_OPENAL) + m_driver = std::make_unique(this); +#endif + +#if defined(SOUNDWAVE_USING_ALSA) + m_driver = std::make_unique(this); +#endif + +#if defined(SOUNDWAVE_USING_SDLMIXER) + m_driver = std::make_unique(this); +#endif + +#if defined(SOUNDWAVE_USING_PULSE) + m_driver = std::make_unique(this); +#endif + } + + WaveEngine::~WaveEngine() + { + DestroyAudio(); + } + + std::vector WaveEngine::GetOutputDevices() + { + return { "XXX" }; + } + + + void WaveEngine::UseOutputDevice(const std::string& sDeviceOut) + { + m_sOutputDevice = sDeviceOut; + } + + std::vector WaveEngine::GetInputDevices() + { + return { "XXX" }; + } + + void WaveEngine::UseInputDevice(const std::string& sDeviceIn) + { + m_sInputDevice = sDeviceIn; + } + + bool WaveEngine::InitialiseAudio(uint32_t nSampleRate, uint32_t nChannels, uint32_t nBlocks, uint32_t nBlockSamples) + { + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlocks = nBlocks; + m_nBlockSamples = nBlockSamples; + m_dSamplePerTime = double(nSampleRate); + m_dTimePerSample = 1.0 / double(nSampleRate); + m_driver->Open(m_sOutputDevice, m_sInputDevice); + m_driver->Start(); + return false; + } + + + bool WaveEngine::DestroyAudio() + { + StopAll(); + m_driver->Stop(); + m_driver->Close(); + return false; + } + + void WaveEngine::SetCallBack_NewSample(std::function func) + { + m_funcNewSample = func; + } + + void WaveEngine::SetCallBack_SynthFunction(std::function func) + { + m_funcUserSynth = func; + } + + void WaveEngine::SetCallBack_FilterFunction(std::function func) + { + m_funcUserFilter = func; + } + + PlayingWave WaveEngine::PlayWaveform(Wave* pWave, bool bLoop, double dSpeed) + { + WaveInstance wi; + wi.bLoop = bLoop; + wi.pWave = pWave; + wi.dSpeedModifier = dSpeed * double(pWave->file.samplerate()) / m_dSamplePerTime; + wi.dDuration = pWave->file.duration() / dSpeed; + wi.dInstanceTime = m_dGlobalTime; + m_listWaves.push_back(wi); + return std::prev(m_listWaves.end()); + } + + void WaveEngine::StopWaveform(const PlayingWave& w) + { + w->bFlagForStop = true; + } + + void WaveEngine::StopAll() + { + for (auto& wave : m_listWaves) + { + wave.bFlagForStop = true; + } + } + + void WaveEngine::SetOutputVolume(const float fVolume) + { + m_fOutputVolume = std::clamp(fVolume, 0.0f, 1.0f); + } + + uint32_t WaveEngine::FillOutputBuffer(std::vector& vBuffer, const uint32_t nBufferOffset, const uint32_t nRequiredSamples) + { + for (uint32_t nSample = 0; nSample < nRequiredSamples; nSample++) + { + double dSampleTime = m_dGlobalTime + nSample * m_dTimePerSample; + + if (m_funcNewSample) + m_funcNewSample(dSampleTime); + + for (uint32_t nChannel = 0; nChannel < m_nChannels; nChannel++) + { + // Construct the sample + float fSample = 0.0f; + + // 1) Sample any active waves + for (auto& wave : m_listWaves) + { + // Is wave instance flagged for stopping? + if (wave.bFlagForStop) + { + wave.bFinished = true; + } + else + { + // Calculate offset into wave instance + double dTimeOffset = dSampleTime - wave.dInstanceTime; + + // If offset is larger than wave then... + if (dTimeOffset >= wave.dDuration) + { + if (wave.bLoop) + { + // ...if looping, reset the wave instance + wave.dInstanceTime = dSampleTime; + } + else + { + // ...if not looping, flag wave instance as dead + wave.bFinished = true; + } + } + else + { + // OR, sample the waveform from the correct channel + fSample += float(wave.pWave->vChannelView[nChannel % wave.pWave->file.channels()].GetSample(dTimeOffset * m_dSamplePerTime * wave.dSpeedModifier)); + } + } + } + + // Remove waveform instances that have finished + m_listWaves.remove_if([](const WaveInstance& wi) {return wi.bFinished; }); + + + // 2) If user is synthesizing, request sample + if (m_funcUserSynth) + fSample += m_funcUserSynth(nChannel, dSampleTime); + + // 3) Apply global filters + + + // 4) If user is filtering, allow manipulation of output + if (m_funcUserFilter) + fSample = m_funcUserFilter(nChannel, dSampleTime, fSample); + + // Place sample in buffer + vBuffer[nBufferOffset + nSample * m_nChannels + nChannel] = fSample * m_fOutputVolume; + } + } + + // UPdate global time, accounting for error (thanks scripticuk) + m_dGlobalTime += nRequiredSamples * m_dTimePerSample; + return nRequiredSamples; + } + + + uint32_t WaveEngine::GetSampleRate() const + { + return m_nSampleRate; + } + + uint32_t WaveEngine::GetChannels() const + { + return m_nChannels; + } + + uint32_t WaveEngine::GetBlocks() const + { + return m_nBlocks; + } + + uint32_t WaveEngine::GetBlockSampleCount() const + { + return m_nBlockSamples; + } + + double WaveEngine::GetTimePerSample() const + { + return m_dTimePerSample; + } + + namespace driver + { + Base::Base(olc::sound::WaveEngine* pHost) : m_pHost(pHost) + {} + + Base::~Base() + {} + + bool Base::Open(const std::string& sOutputDevice, const std::string& sInputDevice) + { + return false; + } + + bool Base::Start() + { + return false; + } + + void Base::Stop() + { + } + + void Base::Close() + { + } + + std::vector Base::EnumerateOutputDevices() + { + return { "DEFAULT" }; + } + + std::vector Base::EnumerateInputDevices() + { + return { "NONE" }; + } + + void Base::ProcessOutputBlock(std::vector& vFloatBuffer, std::vector& vDACBuffer) + { + constexpr float fMaxSample = float(std::numeric_limits::max()); + constexpr float fMinSample = float(std::numeric_limits::min()); + + // So... why not use vFloatBuffer.size()? Well with this implementation + // we can, but i suspect there may be some platforms that request a + // specific number of samples per "loop" rather than this block architecture + uint32_t nSamplesToProcess = m_pHost->GetBlockSampleCount(); + uint32_t nSampleOffset = 0; + while (nSamplesToProcess > 0) + { + uint32_t nSamplesGathered = m_pHost->FillOutputBuffer(vFloatBuffer, nSampleOffset, nSamplesToProcess); + + // Vector is in float32 format, so convert to hardware required format + for (uint32_t n = 0; n < nSamplesGathered; n++) + { + for (uint32_t c = 0; c < m_pHost->GetChannels(); c++) + { + size_t nSampleID = nSampleOffset + (n * m_pHost->GetChannels() + c); + vDACBuffer[nSampleID] = short(std::clamp(vFloatBuffer[nSampleID] * fMaxSample, fMinSample, fMaxSample)); + } + } + + nSampleOffset += nSamplesGathered; + nSamplesToProcess -= nSamplesGathered; + } + } + + void Base::GetFullOutputBlock(std::vector& vFloatBuffer) + { + uint32_t nSamplesToProcess = m_pHost->GetBlockSampleCount(); + uint32_t nSampleOffset = 0; + while (nSamplesToProcess > 0) + { + uint32_t nSamplesGathered = m_pHost->FillOutputBuffer(vFloatBuffer, nSampleOffset, nSamplesToProcess); + + nSampleOffset += nSamplesGathered; + nSamplesToProcess -= nSamplesGathered; + } + } + } + + namespace synth + { + Property::Property(double f) + { + value = std::clamp(f, -1.0, 1.0); + } + + Property& Property::operator =(const double f) + { + value = std::clamp(f, -1.0, 1.0); + return *this; + } + + + ModularSynth::ModularSynth() + { + + } + + bool ModularSynth::AddModule(Module* pModule) + { + // Check if module already added + if (std::find(m_vModules.begin(), m_vModules.end(), pModule) == std::end(m_vModules)) + { + m_vModules.push_back(pModule); + return true; + } + + return false; + } + + bool ModularSynth::RemoveModule(Module* pModule) + { + if (std::find(m_vModules.begin(), m_vModules.end(), pModule) == std::end(m_vModules)) + { + m_vModules.erase(std::remove(m_vModules.begin(), m_vModules.end(), pModule), m_vModules.end()); + return true; + } + + return false; + } + + bool ModularSynth::AddPatch(Property* pInput, Property* pOutput) + { + // Does patch exist? + std::pair newPatch = std::pair(pInput, pOutput); + + if (std::find(m_vPatches.begin(), m_vPatches.end(), newPatch) == std::end(m_vPatches)) + { + // Patch doesnt exist, now check if either are null + if (pInput != nullptr && pOutput != nullptr) + { + m_vPatches.push_back(newPatch); + return true; + } + } + + return false; + } + + bool ModularSynth::RemovePatch(Property* pInput, Property* pOutput) + { + std::pair newPatch = std::pair(pInput, pOutput); + + if (std::find(m_vPatches.begin(), m_vPatches.end(), newPatch) == std::end(m_vPatches)) + { + m_vPatches.erase(std::remove(m_vPatches.begin(), m_vPatches.end(), newPatch), m_vPatches.end()); + return true; + } + + return false; + } + + void ModularSynth::UpdatePatches() + { + // Update patches + for (auto& patch : m_vPatches) + { + patch.second->value = patch.first->value; + } + } + + + void ModularSynth::Update(uint32_t nChannel, double dTime, double dTimeStep) + { + // Now update synth + for (auto& pModule : m_vModules) + { + pModule->Update(nChannel, dTime, dTimeStep); + } + } + + + namespace modules + { + void Oscillator::Update(uint32_t nChannel, double dTime, double dTimeStep) + { + // We use phase accumulation to combat change in parameter glitches + double w = frequency.value * max_frequency * dTimeStep; + phase_acc += w + lfo_input.value * frequency.value; + if (phase_acc >= 2.0) phase_acc -= 2.0; + + switch (waveform) + { + case Type::Sine: + output = amplitude.value * sin(phase_acc * 2.0 * 3.14159); + break; + + case Type::Saw: + output = amplitude.value * (phase_acc - 1.0) * 2.0; + break; + + case Type::Square: + output = amplitude.value * (phase_acc >= 1.0) ? 1.0 : -1.0; + break; + + case Type::Triangle: + output = amplitude.value * (phase_acc < 1.0) ? (phase_acc * 0.5) : (1.0 - phase_acc * 0.5); + break; + + case Type::PWM: + output = amplitude.value * (phase_acc >= (parameter.value + 1.0)) ? 1.0 : -1.0; + break; + + case Type::Wave: + if(pWave != nullptr) + output = amplitude.value * pWave->vChannelView[nChannel].GetSample(phase_acc * 0.5 * pWave->file.durationInSamples()); + break; + + case Type::Noise: + output = amplitude.value * rndDouble(-1.0, 1.0); + break; + + } + } + + double Oscillator::rndDouble(double min, double max) + { + return ((double)rnd() / (double)(0x7FFFFFFF)) * (max - min) + min; + } + + uint32_t Oscillator::rnd() + { + random_seed += 0xe120fc15; + uint64_t tmp; + tmp = (uint64_t)random_seed * 0x4a39b70d; + uint32_t m1 = uint32_t(((tmp >> 32) ^ tmp) & 0xFFFFFFFF); + tmp = (uint64_t)m1 * 0x12fad5c9; + uint32_t m2 = uint32_t(((tmp >> 32) ^ tmp) & 0xFFFFFFFF); + return m2; + } + } + } +} + + + +#if defined(SOUNDWAVE_USING_WINMM) +// WinMM Driver Implementation +namespace olc::sound::driver +{ + #pragma comment(lib, "winmm.lib") + + WinMM::WinMM(WaveEngine* pHost) : Base(pHost) + { } + + WinMM::~WinMM() + { + Stop(); + Close(); + } + + bool WinMM::Open(const std::string& sOutputDevice, const std::string& sInputDevice) + { + // Device is available + WAVEFORMATEX waveFormat; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nSamplesPerSec = m_pHost->GetSampleRate(); + waveFormat.wBitsPerSample = sizeof(short) * 8; + waveFormat.nChannels = m_pHost->GetChannels(); + waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + // Open Device if valid + if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)WinMM::waveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK) + return false; + + // Allocate array of wave header objects, one per block + m_pWaveHeaders = std::make_unique(m_pHost->GetBlocks()); + + // Allocate block memory - I dont like vector of vectors, so going with this mess instead + // My std::vector's content will change, but their size never will - they are basically array now + m_pvBlockMemory = std::make_unique[]>(m_pHost->GetBlocks()); + for (size_t i = 0; i < m_pHost->GetBlocks(); i++) + m_pvBlockMemory[i].resize(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0); + + // Link headers to block memory - clever, so we only move headers about + // rather than memory... + for (unsigned int n = 0; n < m_pHost->GetBlocks(); n++) + { + m_pWaveHeaders[n].dwBufferLength = DWORD(m_pvBlockMemory[0].size() * sizeof(short)); + m_pWaveHeaders[n].lpData = (LPSTR)(m_pvBlockMemory[n].data()); + } + + // To begin with, all blocks are free + m_nBlockFree = m_pHost->GetBlocks(); + return true; + } + + bool WinMM::Start() + { + // Prepare driver thread for activity + m_bDriverLoopActive = true; + // and get it going! + m_thDriverLoop = std::thread(&WinMM::DriverLoop, this); + return true; + } + + void WinMM::Stop() + { + // Signal the driver loop to exit + m_bDriverLoopActive = false; + + // Wait for driver thread to exit gracefully + if (m_thDriverLoop.joinable()) + m_thDriverLoop.join(); + } + + void WinMM::Close() + { + waveOutClose(m_hwDevice); + } + + // Static Callback wrapper - specific instance is specified + void CALLBACK WinMM::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2) + { + // All sorts of messages may be pinged here, but we're only interested + // in audio block is finished... + if (uMsg != WOM_DONE) return; + + // ...which has happened so allow driver object to free resource + WinMM* driver = (WinMM*)dwInstance; + driver->FreeAudioBlock(); + } + + void WinMM::FreeAudioBlock() + { + // Audio subsystem is done with the block it was using, thus + // making it available again + m_nBlockFree++; + + // Signal to driver loop that a block is now available. It + // could have been suspended waiting for one + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + } + + void WinMM::DriverLoop() + { + // We will be using this vector to transfer to the host for filling, with + // user sound data (float32, -1.0 --> +1.0) + std::vector vFloatBuffer(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f); + + // While the system is active, start requesting audio data + while (m_bDriverLoopActive) + { + // Are there any blocks available to fill? ... + if (m_nBlockFree == 0) + { + // ...no, So wait until one is available + std::unique_lock lm(m_muxBlockNotZero); + while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly + { + // This thread will suspend until this CV is signalled + // from FreeAudioBlock. + m_cvBlockNotZero.wait(lm); + } + } + + // ...yes, so use next one, by indicating one fewer + // block is available + m_nBlockFree--; + + // Prepare block for processing, by oddly, marking it as unprepared :P + if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) + { + waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + } + + // Give the userland the opportunity to fill the buffer. Note that the driver + // doesnt give a hoot about timing. Thats up to the SoundWave interface to + // maintain + + // Userland will populate a float buffer, that gets cleanly converted to + // a buffer of shorts for DAC + ProcessOutputBlock(vFloatBuffer, m_pvBlockMemory[m_nBlockCurrent]); + + // Send block to sound device + waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + m_nBlockCurrent++; + m_nBlockCurrent %= m_pHost->GetBlocks(); + } + } +} // WinMM Driver Implementation +#endif +#if defined(SOUNDWAVE_USING_SDLMIXER) + +namespace olc::sound::driver +{ + +SDLMixer* SDLMixer::instance = nullptr; + +SDLMixer::SDLMixer(olc::sound::WaveEngine* pHost) + : Base(pHost) +{ + instance = this; +} + +SDLMixer::~SDLMixer() +{ + Stop(); + Close(); +} + +bool SDLMixer::Open(const std::string& sOutputDevice, const std::string& sInputDevice) +{ + auto errc = Mix_OpenAudioDevice(static_cast(m_pHost->GetSampleRate()), + AUDIO_F32, + static_cast(m_pHost->GetChannels()), + static_cast(m_pHost->GetBlockSampleCount()), + sOutputDevice == "DEFAULT" ? nullptr : sOutputDevice.c_str(), + SDL_AUDIO_ALLOW_FORMAT_CHANGE); + + // Query the actual format of the audio device, as we have allowed it to be changed. + if (errc || !Mix_QuerySpec(nullptr, &m_haveFormat, nullptr)) + { + std::cerr << "Failed to open audio device '" << sOutputDevice << "'" << std::endl; + return false; + } + + // Compute the Mix_Chunk buffer's size according to the format of the audio device + Uint32 bufferSize = 0; + switch (m_haveFormat) + { + case AUDIO_F32: + case AUDIO_S32: + bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 4; + break; + case AUDIO_S16: + case AUDIO_U16: + bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 2; + break; + case AUDIO_S8: + case AUDIO_U8: + bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 1; + break; + default: + std::cerr << "Audio format of device '" << sOutputDevice << "' is not supported" << std::endl; + return false; + } + + // Allocate the buffer once. The size will never change after this + audioBuffer.resize(bufferSize); + audioChunk = { + 0, // 0, as the chunk does not own the array + audioBuffer.data(), // Pointer to data array + bufferSize, // Size in bytes + 128 // Volume; max by default as it's not controlled by the driver. + }; + + return true; +} + +template +void ConvertFloatTo(const std::vector& fromArr, Int* toArr) +{ + static auto minVal = static_cast(std::numeric_limits::min()); + static auto maxVal = static_cast(std::numeric_limits::max()); + for (size_t i = 0; i != fromArr.size(); ++i) + { + toArr[i] = static_cast(std::clamp(fromArr[i] * maxVal, minVal, maxVal)); + } +} + +void SDLMixer::FillChunkBuffer(const std::vector& userData) const +{ + // Since the audio device might have changed the format we need to provide, + // we convert the wave data from the user to that format. + switch (m_haveFormat) + { + case AUDIO_F32: + memcpy(audioChunk.abuf, userData.data(), audioChunk.alen); + break; + case AUDIO_S32: + ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf)); + break; + case AUDIO_S16: + ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf)); + break; + case AUDIO_U16: + ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf)); + break; + case AUDIO_S8: + ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf)); + break; + case AUDIO_U8: + ConvertFloatTo(userData, audioChunk.abuf); + break; + } +} + +void SDLMixer::SDLMixerCallback(int channel) +{ + static std::vector userData(instance->m_pHost->GetBlockSampleCount() * instance->m_pHost->GetChannels()); + + if (channel != 0) + { + std::cerr << "Unexpected channel number" << std::endl; + } + + // Don't add another chunk if we should not keep running + if (!instance->m_keepRunning) + return; + + instance->GetFullOutputBlock(userData); + instance->FillChunkBuffer(userData); + + if (Mix_PlayChannel(0, &instance->audioChunk, 0) == -1) + { + std::cerr << "Error while playing Chunk" << std::endl; + } +} + +bool SDLMixer::Start() +{ + m_keepRunning = true; + + // Kickoff the audio driver + SDLMixerCallback(0); + + // SDLMixer handles all other calls to reinsert user data + Mix_ChannelFinished(SDLMixerCallback); + return true; +} + +void SDLMixer::Stop() +{ + m_keepRunning = false; + + // Stop might be called multiple times, so we check whether the device is already closed + if (Mix_QuerySpec(nullptr, nullptr, nullptr)) + { + for (int i = 0; i != m_pHost->GetChannels(); ++i) + { + if (Mix_Playing(i)) + Mix_HaltChannel(i); + } + } +} + +void SDLMixer::Close() +{ + Mix_CloseAudio(); +} +} + +#endif // SOUNDWAVE_USING_SDLMIXER +#if defined(SOUNDWAVE_USING_ALSA) +// ALSA Driver Implementation +namespace olc::sound::driver +{ + ALSA::ALSA(WaveEngine* pHost) : Base(pHost) + { } + + ALSA::~ALSA() + { + Stop(); + Close(); + } + + bool ALSA::Open(const std::string& sOutputDevice, const std::string& sInputDevice) + { + // Open PCM stream + int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + + // Clear global cache. + // This won't affect users who don't want to create multiple instances of this driver, + // but it will prevent valgrind from whining about "possibly lost" memory. + // If the user's ALSA setup uses a PulseAudio plugin, then valgrind will still compain + // about some "still reachable" data used by that plugin. TODO? + snd_config_update_free_global(); + + if (rc < 0) + return false; + + // Prepare the parameter structure and set default parameters + snd_pcm_hw_params_t *params; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(m_pPCM, params); + + // Set other parameters + snd_pcm_hw_params_set_access(m_pPCM, params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_FLOAT); + snd_pcm_hw_params_set_rate(m_pPCM, params, m_pHost->GetSampleRate(), 0); + snd_pcm_hw_params_set_channels(m_pPCM, params, m_pHost->GetChannels()); + snd_pcm_hw_params_set_period_size(m_pPCM, params, m_pHost->GetBlockSampleCount(), 0); + snd_pcm_hw_params_set_periods(m_pPCM, params, m_pHost->GetBlocks(), 0); + + // Save these parameters + rc = snd_pcm_hw_params(m_pPCM, params); + if (rc < 0) + return false; + + return true; + } + + bool ALSA::Start() + { + // Unsure if really needed, helped prevent underrun on my setup + std::vector vSilence(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f); + snd_pcm_start(m_pPCM); + for (unsigned int i = 0; i < m_pHost->GetBlocks(); i++) + snd_pcm_writei(m_pPCM, vSilence.data(), m_pHost->GetBlockSampleCount()); + + m_rBuffers.Resize(m_pHost->GetBlocks(), m_pHost->GetBlockSampleCount() * m_pHost->GetChannels()); + + snd_pcm_start(m_pPCM); + m_bDriverLoopActive = true; + m_thDriverLoop = std::thread(&ALSA::DriverLoop, this); + + return true; + } + + void ALSA::Stop() + { + // Signal the driver loop to exit + m_bDriverLoopActive = false; + + // Wait for driver thread to exit gracefully + if (m_thDriverLoop.joinable()) + m_thDriverLoop.join(); + + if (m_pPCM != nullptr) + snd_pcm_drop(m_pPCM); + } + + void ALSA::Close() + { + if (m_pPCM != nullptr) + { + snd_pcm_close(m_pPCM); + m_pPCM = nullptr; + } + // Clear the global cache again for good measure + snd_config_update_free_global(); + } + + void ALSA::DriverLoop() + { + const uint32_t nFrames = m_pHost->GetBlockSampleCount(); + + int err; + std::vector vFDs; + + int nFDs = snd_pcm_poll_descriptors_count(m_pPCM); + if (nFDs < 0) + { + std::cerr << "snd_pcm_poll_descriptors_count returned " << nFDs << "\n"; + std::cerr << "disabling polling\n"; + nFDs = 0; + } + else + { + vFDs.resize(nFDs); + + err = snd_pcm_poll_descriptors(m_pPCM, vFDs.data(), vFDs.size()); + if (err < 0) + { + std::cerr << "snd_pcm_poll_descriptors returned " << err << "\n"; + std::cerr << "disabling polling\n"; + vFDs = {}; + } + } + + // While the system is active, start requesting audio data + while (m_bDriverLoopActive) + { + if (!m_rBuffers.IsFull()) + { + // Grab some audio data + auto& vFreeBuffer = m_rBuffers.GetFreeBuffer(); + GetFullOutputBlock(vFreeBuffer); + } + + // Wait a bit if our buffer is full + auto avail = snd_pcm_avail_update(m_pPCM); + while (m_rBuffers.IsFull() && avail < nFrames) + { + if (vFDs.size() == 0) break; + + err = poll(vFDs.data(), vFDs.size(), -1); + if (err < 0) + std::cerr << "poll returned " << err << "\n"; + + unsigned short revents; + err = snd_pcm_poll_descriptors_revents(m_pPCM, vFDs.data(), vFDs.size(), &revents); + if (err < 0) + std::cerr << "snd_pcm_poll_descriptors_revents returned " << err << "\n"; + + if (revents & POLLERR) + std::cerr << "POLLERR\n"; + + avail = snd_pcm_avail_update(m_pPCM); + } + + // Write whatever we can + while (!m_rBuffers.IsEmpty() && avail >= nFrames) + { + auto vFullBuffer = m_rBuffers.GetFullBuffer(); + uint32_t nWritten = 0; + + while (nWritten < nFrames) + { + auto err = snd_pcm_writei(m_pPCM, vFullBuffer.data() + nWritten, nFrames - nWritten); + if (err > 0) + nWritten += err; + else + { + std::cerr << "snd_pcm_writei returned " << err << "\n"; + break; + } + } + avail = snd_pcm_avail_update(m_pPCM); + } + } + } +} // ALSA Driver Implementation +#endif +#if defined(SOUNDWAVE_USING_PULSE) +// PULSE Driver Implementation +#include +#include + +namespace olc::sound::driver +{ + PulseAudio::PulseAudio(WaveEngine* pHost) : Base(pHost) + { } + + PulseAudio::~PulseAudio() + { + Stop(); + Close(); + } + + bool PulseAudio::Open(const std::string& sOutputDevice, const std::string& sInputDevice) + { + pa_sample_spec ss { + PA_SAMPLE_FLOAT32, m_pHost->GetSampleRate(), (uint8_t)m_pHost->GetChannels() + }; + + m_pPA = pa_simple_new(NULL, "olcSoundWaveEngine", PA_STREAM_PLAYBACK, NULL, + "Output Stream", &ss, NULL, NULL, NULL); + + if (m_pPA == NULL) + return false; + + return true; + } + + bool PulseAudio::Start() + { + m_bDriverLoopActive = true; + m_thDriverLoop = std::thread(&PulseAudio::DriverLoop, this); + + return true; + } + + void PulseAudio::Stop() + { + // Signal the driver loop to exit + m_bDriverLoopActive = false; + + // Wait for driver thread to exit gracefully + if (m_thDriverLoop.joinable()) + m_thDriverLoop.join(); + } + + void PulseAudio::Close() + { + if (m_pPA != nullptr) + { + pa_simple_free(m_pPA); + m_pPA = nullptr; + } + } + + void PulseAudio::DriverLoop() + { + // We will be using this vector to transfer to the host for filling, with + // user sound data (float32, -1.0 --> +1.0) + std::vector vFloatBuffer(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f); + + // While the system is active, start requesting audio data + while (m_bDriverLoopActive) + { + // Grab audio data from user + GetFullOutputBlock(vFloatBuffer); + + // Fill PulseAudio data buffer + int error; + if (pa_simple_write(m_pPA, vFloatBuffer.data(), + vFloatBuffer.size() * sizeof(float), &error) < 0) + { + std::cerr << "Failed to feed data to PulseAudio: " << pa_strerror(error) << "\n"; + } + } + } +} // PulseAudio Driver Implementation +#endif + +#endif // OLC_SOUNDWAVE IMPLEMENTATION +#endif // OLC_SOUNDWAVE_H + diff --git a/splash.h b/splash.h new file mode 100644 index 0000000..71c19f1 --- /dev/null +++ b/splash.h @@ -0,0 +1,154 @@ +#pragma once + +#include "pixelGameEngine.h" + +namespace olc +{ + class SplashScreen : public olc::PGEX + { + public: + SplashScreen(); + + protected: + virtual void OnAfterUserCreate() override; + virtual bool OnBeforeUserUpdate(float& fElapsedTime) override; + + private: + olc::Renderable spr; + std::vector> vBoom; + olc::vf2d vScale; + olc::vf2d vPosition; + float fParticleTime = 0.0f; + float fAspect = 0.0f; + bool bComplete = false; + }; + + +} + +#ifdef OLC_PGEX_SPLASHSCREEN +#undef OLC_PGEX_SPLASHSCREEN + +namespace olc +{ + SplashScreen::SplashScreen() : olc::PGEX(true) + { + } + + void SplashScreen::OnAfterUserCreate() + { + const char logo[] = + "000000000000000000000000000000000000000000000000000000000000000000005" + "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED1EE" + "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED5EEE" + "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE@E@000" + "0000000000000000000000000000000000000000000000000000000000001E1D:ZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ1D5BZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ5@E:P0002Z0" + "02ZZX000000000000ZP0000000000000000000000000000ZX000Z002XE1DX?o`o:Poo" + "800SooaE5@E1ED5BX?ol5E@E0E1ED?oo5@E1ED5DE1D5E@ZQEEBPEE2QD5BSooclZ?olQ" + "AB?oo5DEEDEEDE:SooaEEAE5DEEDoolEADEEDEAE5AEEBZ5EE:5EE:5@E:?oo?bXoob55" + "8o3lEAEEAD5ADZ?oo5@E5EEAD5Cl01E5AD5AE5DE5@E:X01DXEEDXE1DXo3lo:Sl0800S" + "ooaE1ED5EE5BXo00EEDEEE5EE?oo5EE5EE5DEEDEEDZQEEBQD5BQD5BSl?cl0?`0ZZZ?o" + "o5D5E@EEDE03loaEEAEEDEEDoolEED5EDEAEEAEEBZ5EE:5@E:5@E:?oo?oloob008o00" + "EAEEAD01EE?co5EE5EEAD03l01DE@05AE5AE5@0:XE000EEDXE1DXooloocoo8DDSlZQE" + "5EE5EE5EDoolE1DE4E5EE?oo5AE5EE5DE5DEEDZQEEAAEEBQD5BPoo3oo3olQAB?bZ5DE" + "1D5EDEE@ooaD5AD1D5EDoolE1DEE@EAD5@EEBZ5EE51ED:5@E:P000000020080:X0000" + "00000000000000000000000000000000000:X0000002XE1DZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZQD5@ZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZX5@E@00000000000000000000000" + "00000000000000000000000000000000000000001E1EEEEEEEEEEEEEEEEEEEEEEEEEE" + "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED5EEEEEEEEEEEEEEEEEEEEEEEEEEE" + "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE@5EEEEEEEEEEEEEEEEEEEEEEEEEEEE" + "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED0000000000000000000000000000000" + "0000000000000000000000000000000000000"; + + spr.Create(203, 24); + int px = 0, py = 0; + for (size_t b = 0; b < 1624; b += 4) + { + uint32_t sym1 = (uint32_t)logo[b + 0] - 48; + uint32_t sym2 = (uint32_t)logo[b + 1] - 48; + uint32_t sym3 = (uint32_t)logo[b + 2] - 48; + uint32_t sym4 = (uint32_t)logo[b + 3] - 48; + uint32_t r = sym1 << 18 | sym2 << 12 | sym3 << 6 | sym4; + + for (int i = 0; i < 12; i++) + { + olc::Pixel p = olc::RED; + switch ((r & 0xC00000) >> 22) + { + case 0: p = olc::Pixel(0, 0, 0, 255); break; + case 1: p = olc::Pixel(255, 255, 255, 255); break; + case 2: p = olc::Pixel(255, 120, 26, 255); break; + case 3: p = olc::Pixel(79, 193, 255, 255); break; + } + spr.Sprite()->SetPixel(px, py, p); + if (++px == 203) { py++; px = 0; } + r <<= 2; + } + } + + spr.Decal()->Update(); + vBoom.resize(spr.Sprite()->width * spr.Sprite()->height); + vScale = { float(pge->ScreenWidth()) / 500.0f, float(pge->ScreenWidth()) / 500.0f }; + fAspect = float(pge->ScreenWidth()) / float(pge->ScreenHeight()); + vPosition = olc::vf2d( + (250 - spr.Sprite()->width) / 2.0f, + (250 - spr.Sprite()->height) / 2.0f / fAspect); + for (int y = 0; y < spr.Sprite()->height; y++) + for (int x = 0; x < spr.Sprite()->width; x++) + vBoom[y * spr.Sprite()->width + x] = std::make_pair( + vPosition + olc::vf2d(x, y), + olc::vf2d( + (float(rand()) / float(RAND_MAX)) * 10.0f - 5.0f, + (float(rand()) / float(RAND_MAX)) * 10.0f - 5.0f) + ); + } + + bool SplashScreen::OnBeforeUserUpdate(float& fElapsedTime) + { + if (bComplete) return false; + + fParticleTime += fElapsedTime; + + for (int y = 0; y < spr.Sprite()->height; y++) + for (int x = 0; x < spr.Sprite()->width; x++) + { + + + if (fParticleTime < 1.0f) + { + + } + else if (fParticleTime < 2.0f) + { + vBoom[y * spr.Sprite()->width + x].first = + olc::vf2d( + (250 - spr.Sprite()->width) / 2.0f + float(x), + (250 - spr.Sprite()->height) / 2.0f / fAspect + float(y) + ) + + olc::vf2d( + (float(rand()) / float(RAND_MAX)) * 0.5f - 0.25f, + (float(rand()) / float(RAND_MAX)) * 0.5f - 0.25f); + } + else if(fParticleTime < 5.0f) + { + vBoom[y * spr.Sprite()->width + x].first += vBoom[y * spr.Sprite()->width + x].second * fElapsedTime * 20.0f; + } + else + { + bComplete = true; + } + + pge->DrawPartialDecal(vScale * vBoom[y * spr.Sprite()->width + x].first * 2.0f, spr.Decal(), olc::vf2d(x, y), { 1, 1 }, vScale * 2.0f, olc::PixelF(1.0f, 1.0f, 1.0f, std::min(1.0f, std::max(4.0f - fParticleTime, 0.0f)))); + } + + olc::vi2d vSize = pge->GetTextSizeProp("Copyright OneLoneCoder.com 2022"); + pge->DrawStringPropDecal(olc::vf2d(float(pge->ScreenWidth()/2) - vSize.x/2, float(pge->ScreenHeight()) - vSize.y * 3.0f), "Copyright OneLoneCoder.com 2022", olc::PixelF(1.0f, 1.0f, 1.0f, 0.5f), olc::vf2d(1.0, 2.0f)); + return true; + } + +} + +#endif \ No newline at end of file