From f0de5fa5f7c88c26418168526ae57c98c1260741 Mon Sep 17 00:00:00 2001 From: gorbit99 Date: Thu, 10 Jan 2019 20:29:52 +0100 Subject: [PATCH 1/2] Added sound and audio Added sound and audio into the engine without using additional dependencies, keeping compatibility as much as possible Also fixed some typos --- olcConsoleGameEngineSDL.h | 571 ++++++++++++++++++++++++++++++-------- 1 file changed, 451 insertions(+), 120 deletions(-) diff --git a/olcConsoleGameEngineSDL.h b/olcConsoleGameEngineSDL.h index 512d568..20541d8 100644 --- a/olcConsoleGameEngineSDL.h +++ b/olcConsoleGameEngineSDL.h @@ -6,9 +6,6 @@ For the *nix and Mac users - like most Linux software, this may not be fully compatible or fully featured, and development will invariably lag behind the rest - sorry. -NOTE: DOES NOT SUPPORT SOUND YET -NOTE: DOES NOT SUUPORT MOUSE INPUT YET - I recommend compiling as follows, of course you need SDL from your vendor g++ thing.cpp -DUNICODE -I/usr/include/SDL2 -lSDL2 -lpthread -std=C++11 @@ -18,7 +15,7 @@ some awesome individuals, this would not be a thing! =========== IMPORTANT!!! PLEASE READ ========================================== -Firstly, Thanks to Jack Clarke (JackOJC on Discord and Github). He had the +Firstly, Thanks to Jack Clarke (JackOJC on Discord and Github). He had the idea of porting over the olcConsoleGameEngine to non-windows operating systems. He developed a prototype which can be found here: @@ -27,14 +24,14 @@ which really proved that it could be possible. I then added functionality to ensure that this header is 99% compatible with my existing code base, so almost all the videos I've made should be compatible -with this version of the olcConsoleGameEngine, without modificatcion. +with this version of the olcConsoleGameEngine, without modification. Finally I'd also like to thank MrBadNews on Discord for testing and finding many bugs, and creating workarounds, and Slavka on Discord for help debug SDL double buffering issues - stupid SDL :P -Please remember I, javidx9, am not a Linux or SDL specialist. Therfore please -report bugs, or ask for help on the Discord Server, as I probably don't use +Please remember I, javidx9, am not a Linux or SDL specialist. Therefore please +report bugs, or ask for help on the Discord Server, as I probably don't use the same non-windows operating systems as you, and each have their own methods for building and installing. @@ -137,6 +134,8 @@ http://www.twitch.tv/javidx9 */ +#include + #pragma once #ifndef UNICODE @@ -202,7 +201,7 @@ Character Set -> Use Unicode. Thanks! For now, Ill try enabling it for you - Jav #define VK_F11 SDL_SCANCODE_F11 + KEY_OFFSET #define VK_F12 SDL_SCANCODE_F12 + KEY_OFFSET -struct CHAR_INFO +struct CHAR_INFO { unsigned short glyph; short colour; @@ -313,7 +312,7 @@ private: public: void SetGlyph(int x, int y, wchar_t c) { - if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + if (x < 0 || x >= nWidth || y < 0 || y >= nHeight) return; else m_Glyphs[y * nWidth + x] = c; @@ -321,7 +320,7 @@ public: void SetColour(int x, int y, short c) { - if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + if (x < 0 || x >= nWidth || y < 0 || y >= nHeight) return; else m_Colours[y * nWidth + x] = c; @@ -329,7 +328,7 @@ public: wchar_t GetGlyph(int x, int y) { - if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + if (x < 0 || x >= nWidth || y < 0 || y >= nHeight) return L' '; else return m_Glyphs[y * nWidth + x]; @@ -337,7 +336,7 @@ public: short GetColour(int x, int y) { - if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + if (x < 0 || x >= nWidth || y < 0 || y >= nHeight) return FG_BLACK; else return m_Colours[y * nWidth + x]; @@ -347,7 +346,7 @@ public: { int sx = (int)(x * (float)nWidth); int sy = (int)(y * (float)nHeight - 1.0f); - if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight) + if (sx < 0 || sx >= nWidth || sy < 0 || sy >= nHeight) return L' '; else return m_Glyphs[sy * nWidth + sx]; @@ -357,7 +356,7 @@ public: { int sx = (int)(x * (float)nWidth); int sy = (int)(y * (float)nHeight - 1.0f); - if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight) + if (sx < 0 || sx >= nWidth || sy < 0 || sy >= nHeight) return FG_BLACK; else return m_Colours[sy * nWidth + sx]; @@ -374,7 +373,7 @@ public: #else wcstombs(buff, sFile.c_str(), 256); #endif - + #ifdef _WIN32 fopen_s(&f, buff, "wb"); #else @@ -430,6 +429,9 @@ public: } }; +int len = 0, done = 0, bits = 0, which = 0, +sample_size = 0, position = 0, rate = 0; +Sint16 *stream[2]; class olcConsoleGameEngine { @@ -439,7 +441,7 @@ public: m_nScreenWidth = 80; m_nScreenHeight = 30; - + memset(m_keyNewState, 0, 512 * sizeof(bool)); memset(m_keyOldState, 0, 512 * sizeof(bool)); memset(m_keys._state, 0, 512 * sizeof(sKeyState)); @@ -456,15 +458,15 @@ public: m_nFontWidth = fontw; m_nFontHeight = fonth; - + // Allocate memory for screen buffer m_bufScreen[0] = new CHAR_INFO[m_nScreenWidth*m_nScreenHeight]; m_bufScreen[1] = new CHAR_INFO[m_nScreenWidth*m_nScreenHeight]; // NOTE(MrBadNewS): set buffers to zero, old way - memset(m_bufScreen[0], 0, m_nScreenWidth*m_nScreenHeight*sizeof(CHAR_INFO)); - memset(m_bufScreen[1], 0, m_nScreenWidth*m_nScreenHeight*sizeof(CHAR_INFO)); + memset(m_bufScreen[0], 0, m_nScreenWidth*m_nScreenHeight * sizeof(CHAR_INFO)); + memset(m_bufScreen[1], 0, m_nScreenWidth*m_nScreenHeight * sizeof(CHAR_INFO)); m_nCurrentBuffer = 0; return 1; @@ -491,7 +493,7 @@ public: void DrawString(int x, int y, std::wstring c, short col = 0x000F) { for (size_t i = 0; i < c.size(); i++) - { + { m_bufScreen[m_nCurrentBuffer][y * m_nScreenWidth + x + i].glyph = c[i]; m_bufScreen[m_nCurrentBuffer][y * m_nScreenWidth + x + i].colour = col; } @@ -541,14 +543,14 @@ public: xe = x1; } Draw(x, y, c, col); - for (i = 0; x0 && dy>0)) + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) y = y + 1; else y = y - 1; @@ -572,14 +574,14 @@ public: ye = y1; } Draw(x, y, c, col); - for (i = 0; y0 && dy>0)) + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) x = x + 1; else x = x - 1; @@ -710,7 +712,7 @@ public: ~olcConsoleGameEngine() { - + } public: @@ -718,7 +720,7 @@ public: { m_bAtomActive = true; - // Star the thread + // Start the thread std::thread t = std::thread(&olcConsoleGameEngine::GameThread, this); // Wait for thread to be exited @@ -736,10 +738,14 @@ public: } private: + void EnableSound() { + m_bEnableSound = true; + } + void GameThread() { // Start SDL - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_AUDIO); char bufAppName[256]; #ifdef _WIN32 @@ -798,30 +804,84 @@ private: { switch (e.type) { - case SDL_QUIT: - m_bAtomActive = false; + case SDL_QUIT: + m_bAtomActive = false; break; - case SDL_KEYDOWN: + case SDL_KEYDOWN: + { + int k = e.key.keysym.scancode; // SDL Scancode + m_keyNewState[k] = true; + } + break; + + case SDL_KEYUP: + { + int k = e.key.keysym.scancode; // SDL Scancode + m_keyNewState[k] = false; + } + break; + + case SDL_MOUSEMOTION: + { + m_mousePosX = e.motion.x / m_nFontWidth; + m_mousePosY = e.motion.y / m_nFontHeight; + } + break; + + case SDL_MOUSEBUTTONDOWN: + { + switch (e.button.button) { - int k = e.key.keysym.scancode; // SDL Scancode - m_keyNewState[k] = true; + case SDL_BUTTON_LEFT: + m_mouseNewState[0] = 1; + break; + case SDL_BUTTON_RIGHT: + m_mouseNewState[1] = 1; + break; + case SDL_BUTTON_MIDDLE: + m_mouseNewState[2] = 1; + break; + case SDL_BUTTON_X1: + m_mouseNewState[3] = 1; + break; + case SDL_BUTTON_X2: + m_mouseNewState[4] = 1; + break; } - break; + } + break; + + case SDL_MOUSEBUTTONUP: + { - case SDL_KEYUP: + switch (e.button.button) { - int k = e.key.keysym.scancode; // SDL Scancode - m_keyNewState[k] = false; + case SDL_BUTTON_LEFT: + m_mouseNewState[0] = 0; + break; + case SDL_BUTTON_RIGHT: + m_mouseNewState[1] = 0; + break; + case SDL_BUTTON_MIDDLE: + m_mouseNewState[2] = 0; + break; + case SDL_BUTTON_X1: + m_mouseNewState[3] = 0; + break; + case SDL_BUTTON_X2: + m_mouseNewState[4] = 0; + break; } - break; + } + break; } } // Handle Keyboard Input for (int k = 0; k < 512; k++) - { + { m_keys._state[k].bPressed = false; m_keys._state[k].bReleased = false; @@ -842,57 +902,7 @@ private: m_keyOldState[k] = m_keyNewState[k]; } - // Handle Mouse Input - Check for window events - /* INPUT_RECORD inBuf[32]; - DWORD events = 0; - GetNumberOfConsoleInputEvents(m_hConsoleIn, &events); - if (events > 0) - ReadConsoleInput(m_hConsoleIn, inBuf, events, &events);*/ - - // Handle events - we only care about mouse clicks and movement - // for now - //for (DWORD i = 0; i < events; i++) - //{ - // switch (inBuf[i].EventType) - // { - // case FOCUS_EVENT: - // { - // m_bConsoleInFocus = inBuf[i].Event.FocusEvent.bSetFocus; - // } - // break; - - // case MOUSE_EVENT: - // { - // switch (inBuf[i].Event.MouseEvent.dwEventFlags) - // { - // case MOUSE_MOVED: - // { - // m_mousePosX = inBuf[i].Event.MouseEvent.dwMousePosition.X; - // m_mousePosY = inBuf[i].Event.MouseEvent.dwMousePosition.Y; - // } - // break; - - // case 0: - // { - // for (int m = 0; m < 5; m++) - // m_mouseNewState[m] = (inBuf[i].Event.MouseEvent.dwButtonState & (1 << m)) > 0; - - // } - // break; - - // default: - // break; - // } - // } - // break; - - // default: - // break; - // // We don't care just at the moment - // } - //} - - /*for (int m = 0; m < 5; m++) + for (int m = 0; m < 5; m++) { m_mouse[m].bPressed = false; m_mouse[m].bReleased = false; @@ -913,9 +923,81 @@ private: m_mouseOldState[m] = m_mouseNewState[m]; } -*/ - // Handle Frame Update + // Handle Mouse Input - Check for window events + /* INPUT_RECORD inBuf[32]; + DWORD events = 0; + GetNumberOfConsoleInputEvents(m_hConsoleIn, &events); + if (events > 0) + ReadConsoleInput(m_hConsoleIn, inBuf, events, &events);*/ + + // Handle events - we only care about mouse clicks and movement + // for now + //for (DWORD i = 0; i < events; i++) + //{ + // switch (inBuf[i].EventType) + // { + // case FOCUS_EVENT: + // { + // m_bConsoleInFocus = inBuf[i].Event.FocusEvent.bSetFocus; + // } + // break; + + // case MOUSE_EVENT: + // { + // switch (inBuf[i].Event.MouseEvent.dwEventFlags) + // { + // case MOUSE_MOVED: + // { + // m_mousePosX = inBuf[i].Event.MouseEvent.dwMousePosition.X; + // m_mousePosY = inBuf[i].Event.MouseEvent.dwMousePosition.Y; + // } + // break; + + // case 0: + // { + // for (int m = 0; m < 5; m++) + // m_mouseNewState[m] = (inBuf[i].Event.MouseEvent.dwButtonState & (1 << m)) > 0; + + // } + // break; + + // default: + // break; + // } + // } + // break; + + // default: + // break; + // // We don't care just at the moment + // } + //} + + /*for (int m = 0; m < 5; m++) + { + m_mouse[m].bPressed = false; + m_mouse[m].bReleased = false; + + if (m_mouseNewState[m] != m_mouseOldState[m]) + { + if (m_mouseNewState[m]) + { + m_mouse[m].bPressed = true; + m_mouse[m].bHeld = true; + } + else + { + m_mouse[m].bReleased = true; + m_mouse[m].bHeld = false; + } + } + + m_mouseOldState[m] = m_mouseNewState[m]; + } + */ + + // Handle Frame Update if (!OnUserUpdate(fElapsedTime)) m_bAtomActive = false; @@ -927,7 +1009,7 @@ private: // Render differences CHAR_INFO *buff_old, *buff_new; buff_new = m_bufScreen[m_nCurrentBuffer]; - buff_old = m_bufScreen[(m_nCurrentBuffer+1) % 2]; + buff_old = m_bufScreen[(m_nCurrentBuffer + 1) % 2]; SDL_SetRenderTarget(m_render, m_screen); @@ -939,13 +1021,13 @@ private: if (buff_new[i].colour != buff_old[i].colour || buff_new[i].glyph != buff_old[i].glyph) { // Draw Cell - int cx = (buff_new[i].glyph-0) % 64; - int cy = (buff_new[i].glyph-0) / 64; + int cx = (buff_new[i].glyph - 0) % 64; + int cy = (buff_new[i].glyph - 0) / 64; // Draw Foreground int cidx = (buff_new[i].colour & 0x00F0) >> 4; //SDL_Rect src_bg = { (cidx+45) * 8, 88, 8, 8 }; - SDL_Rect src_bg = { (cidx+0) * 16, 0, 16, 16 }; + SDL_Rect src_bg = { (cidx + 0) * 16, 0, 16, 16 }; SDL_Rect dst = { x * m_nFontWidth, y * m_nFontHeight, m_nFontWidth, m_nFontHeight }; SDL_SetTextureColorMod(m_fontFile, 255, 255, 255); SDL_SetTextureAlphaMod(m_fontFile, 255); @@ -967,7 +1049,7 @@ private: SDL_SetRenderTarget(m_render, nullptr); SDL_RenderCopy(m_render, m_screen, nullptr, nullptr); SDL_RenderPresent(m_render); - + // Flip buffers m_nCurrentBuffer = (m_nCurrentBuffer + 1) % 2; @@ -1004,7 +1086,6 @@ public: return true; } - protected: struct sKeyState @@ -1027,8 +1108,8 @@ protected: { int scode = (SDL_Scancode)nKeyID - KEY_OFFSET; return _state[scode]; - } - + } + switch (nKeyID) { case L'A': scode = SDL_SCANCODE_A; break; @@ -1068,16 +1149,16 @@ protected: case L'8': scode = SDL_SCANCODE_8; break; case L'9': scode = SDL_SCANCODE_9; break; - /*case L'\'': scode = SDL_SCANCODE_APOSTROPHE; break; - case L'\\': scode = SDL_SCANCODE_BACKSLASH; break; - case L',': scode = SDL_SCANCODE_COMMA; break; - case L'=': scode = SDL_SCANCODE_EQUALS; break; - case L'[': scode = SDL_SCANCODE_LEFTBRACKET; break; - case L']': scode = SDL_SCANCODE_RIGHTBRACKET; break; - case L'-': scode = SDL_SCANCODE_MINUS; break; - case L'.': scode = SDL_SCANCODE_PERIOD; break; - case L';': scode = SDL_SCANCODE_SEMICOLON; break; - case L'/': scode = SDL_SCANCODE_SLASH; break;*/ + /*case L'\'': scode = SDL_SCANCODE_APOSTROPHE; break; + case L'\\': scode = SDL_SCANCODE_BACKSLASH; break; + case L',': scode = SDL_SCANCODE_COMMA; break; + case L'=': scode = SDL_SCANCODE_EQUALS; break; + case L'[': scode = SDL_SCANCODE_LEFTBRACKET; break; + case L']': scode = SDL_SCANCODE_RIGHTBRACKET; break; + case L'-': scode = SDL_SCANCODE_MINUS; break; + case L'.': scode = SDL_SCANCODE_PERIOD; break; + case L';': scode = SDL_SCANCODE_SEMICOLON; break; + case L'/': scode = SDL_SCANCODE_SLASH; break;*/ default: scode = (SDL_Scancode)nKeyID; } @@ -1085,21 +1166,23 @@ protected: return _state[scode]; } - } m_keys; + } m_keys; + + sKeyState m_mouse[5]; int m_mousePosX; int m_mousePosY; public: - sKeyState GetKey(int nKeyID) - { - return m_keys[nKeyID]; + sKeyState GetKey(int nKeyID) + { + return m_keys[nKeyID]; } int GetMouseX() { return m_mousePosX; } int GetMouseY() { return m_mousePosY; } - //sKeyState GetMouse(int nMouseButtonID) { return m_mouse[nMouseButtonID]; } + sKeyState GetMouse(int nMouseButtonID) { return m_mouse[nMouseButtonID]; } bool IsFocused() { return m_bConsoleInFocus; } @@ -1115,14 +1198,19 @@ protected: { // Load image. SDL_Surface* temp = SDL_LoadBMP(fname.c_str()); - + + if (temp == nullptr) { + std::wcout << L"Please download the necessary bmp file too!\n"; + throw 1; + } + // set color key to 255,0,255; this basically makes // it transparent. SDL_SetColorKey(temp, SDL_TRUE, SDL_MapRGB(temp->format, 255, 0, 255)); - + // Convert to texture. - m_fontFile = SDL_CreateTextureFromSurface(m_render, temp); - + m_fontFile = SDL_CreateTextureFromSurface(m_render, temp); + // Cleanup and return. SDL_FreeSurface(temp); } @@ -1156,6 +1244,7 @@ protected: bool m_mouseOldState[5] = { 0 }; bool m_mouseNewState[5] = { 0 }; bool m_bConsoleInFocus = true; + bool m_bEnableSound = false; static std::atomic m_bAtomActive; static std::condition_variable m_cvGameFinished; static std::mutex m_muxGame; @@ -1166,10 +1255,252 @@ private: SDL_Texture *m_screen; SDL_Texture* m_fontFile; +protected: // Audio Engine ===================================================================== + + class olcAudioSample + { + public: + olcAudioSample() + { + + } + + olcAudioSample(std::string sWavFile, olcConsoleGameEngine &cge) + { + uint8_t *wavData; + SDL_AudioSpec fileSpec; + uint32_t streamLen = 0; + if (!SDL_LoadWAV(sWavFile.c_str(), &fileSpec, (uint8_t **)&wavData, &streamLen)) { + std::cout << "Couldn't load audio file!\n" << SDL_GetError() << '\n'; + bSampleValid = false; + return; + } + + SDL_AudioCVT cvt; + if (!SDL_BuildAudioCVT(&cvt, fileSpec.format, fileSpec.channels, fileSpec.freq, + cge.sampleSpec.format, cge.sampleSpec.channels, cge.sampleSpec.freq)) { + std::cout << "Failed to build cvt!\n" << SDL_GetError() << '\n'; + bSampleValid = false; + return; + } + cvt.buf = (uint8_t *)malloc(streamLen * cvt.len_mult); + cvt.len = streamLen; + memcpy(cvt.buf, wavData, streamLen); + SDL_FreeWAV((uint8_t *)wavData); + if (SDL_ConvertAudio(&cvt) == -1) { + std::cout << "Failed to convert audio!\n" << SDL_GetError() << '\n'; + bSampleValid = false; + return; + } + fSample = (float *)cvt.buf; + nSamples = cvt.len_cvt / sizeof(float) / cge.spec.channels; + bSampleValid = true; + } + + ~olcAudioSample() { + SDL_FreeWAV((uint8_t *)fSample); + } + + float *fSample; + uint32_t nSamples = 0; + bool bSampleValid = false; + }; + + // This vector holds all loaded sound samples in memory + std::vector vecAudioSamples; + + // This structure represents a sound that is currently playing. It only + // holds the sound ID and where this instance of it is up to for its + // current playback + struct sCurrentlyPlayingSample + { + int nAudioSampleID = 0; + long nSamplePosition = 0; + bool bFinished = false; + bool bLoop = false; + }; + std::list listActiveSamples; + + // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID + // number is returned if successful, otherwise -1 + unsigned int LoadAudioSample(std::string sWavFile) + { + if (!m_bEnableSound) + return -1; + + olcAudioSample a(sWavFile, *this); + if (a.bSampleValid) + { + vecAudioSamples.push_back(a); + return vecAudioSamples.size(); + } + else + return -1; + } + + // Add sample 'id' to the mixers sounds to play list + void PlaySample(int id, bool bLoop = false) + { + sCurrentlyPlayingSample a; + a.nAudioSampleID = id; + a.nSamplePosition = 0; + a.bFinished = false; + a.bLoop = bLoop; + listActiveSamples.push_back(a); + } + + void StopSample(int id) + { + + } + + // The audio system uses by default a specific wave format + bool CreateAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, + unsigned int nBlocks = 8, unsigned int nBlockSamples = 512) + { + SDL_AudioSpec wanted; + SDL_zero(wanted); + wanted.channels = nChannels; + wanted.format = AUDIO_S16; + wanted.freq = nSampleRate; + wanted.samples = nBlockSamples; + wanted.userdata = this; + wanted.callback = forwardCallback; + SDL_zero(sampleSpec); + sampleSpec.channels = nChannels; + sampleSpec.format = AUDIO_F32; + sampleSpec.freq = nSampleRate; + sampleSpec.userdata = this; + + deviceID = SDL_OpenAudioDevice(NULL, 0, &wanted, &spec, 0); + + if (deviceID == 0) { + std::cout << "Failed to open audio device!\n" << SDL_GetError() << '\n'; + return false; + } + + SDL_PauseAudioDevice(deviceID, 0); + + return true; + } + + // Stop and clean up audio system + bool DestroyAudio() + { + SDL_CloseAudioDevice(deviceID); + return false; + } + + static void forwardCallback(void *userdata, uint8_t *byteStream, int len) { + static_cast(userdata)->AudioThread(userdata, byteStream, len); + } + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void AudioThread(void *userdata, uint8_t *byteStream, int len) + { + m_fGlobalTime = 0.0f; + float fTimeStep = 1.0f / (float)spec.freq; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + // Prepare block for processing + memset(byteStream, 0, len); + + int16_t *buf = (int16_t *)byteStream; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + uint32_t i = 0; + for (unsigned int n = 0; n < len / sizeof(int16_t); n += spec.channels) + { + // User Process + for (unsigned int c = 0; c < spec.channels; c++) + { + int16_t sample = (int16_t)(clip(GetMixerOutput(c, fTimeStep * i, fTimeStep), 1.0) * fMaxSample); + buf[i] = sample; + i++; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + } + + // Overridden by user if they want to generate sound in real-time + virtual float onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep) + { + return 0.0f; + } + + // Overriden by user if they want to manipulate the sound before it is played + virtual float onUserSoundFilter(int nChannel, float fGlobalTime, float fSample) + { + return fSample; + } + + // The Sound Mixer - If the user wants to play many sounds simultaneously, and + // perhaps the same sound overlapping itself, then you need a mixer, which + // takes input from all sound sources for that audio frame. This mixer maintains + // a list of sound locations for all concurrently playing audio samples. Instead + // of duplicating audio data, we simply store the fact that a sound sample is in + // use and an offset into its sample data. As time progresses we update this offset + // until it is beyound the length of the sound sample it is attached to. At this + // point we remove the playing souind from the list. + // + // Additionally, the users application may want to generate sound instead of just + // playing audio clips (think a synthesizer for example) in which case we also + // provide an "onUser..." event to allow the user to return a sound for that point + // in time. + // + // Finally, before the sound is issued to the operating system for performing, the + // user gets one final chance to "filter" the sound, perhaps changing the volume + // or adding funky effects + float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) + { + // Accumulate sample for this channel + float fMixerSample = 0.0f; + + for (auto &s : listActiveSamples) + { + // Calculate sample position + if (nChannel == 0) + s.nSamplePosition += (long)((float)spec.freq * fTimeStep); + + // If sample position is valid add to the mix + if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) + fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * spec.channels) + nChannel]; + else + s.bFinished = true; // Else sound has completed + } + + // If sounds have completed then remove them + listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + + // The users application might be generating sound, so grab that if it exists + fMixerSample += onUserSoundSample(nChannel, fGlobalTime, fTimeStep); + + // Return the sample via an optional user override to filter the sound + return onUserSoundFilter(nChannel, fGlobalTime, fMixerSample); + return fMixerSample; + } + + SDL_AudioDeviceID deviceID; + SDL_AudioSpec spec, sampleSpec; + + std::atomic m_fGlobalTime = 0.0f; }; std::atomic olcConsoleGameEngine::m_bAtomActive(false); std::condition_variable olcConsoleGameEngine::m_cvGameFinished; std::mutex olcConsoleGameEngine::m_muxGame; - From 4800e2fe4f72880d1b0cde716f9fc8730fe54e62 Mon Sep 17 00:00:00 2001 From: gorbit99 Date: Thu, 10 Jan 2019 22:33:28 +0100 Subject: [PATCH 2/2] fixed a bug, that made compiling on gcc impossible std::atomic m_fGlobalTime = 0.0f; => std::atomic m_fGlobalTime = {0.0f}; thx J. Random Programmer --- olcConsoleGameEngineSDL.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/olcConsoleGameEngineSDL.h b/olcConsoleGameEngineSDL.h index 20541d8..0eccdc6 100644 --- a/olcConsoleGameEngineSDL.h +++ b/olcConsoleGameEngineSDL.h @@ -1498,7 +1498,8 @@ protected: // Audio Engine ===================================================== SDL_AudioDeviceID deviceID; SDL_AudioSpec spec, sampleSpec; - std::atomic m_fGlobalTime = 0.0f; + //thx J. Random Programmer + std::atomic m_fGlobalTime = {0.0f}; }; std::atomic olcConsoleGameEngine::m_bAtomActive(false);