|
|
|
@ -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 |
|
|
|
@ -27,13 +24,13 @@ 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 |
|
|
|
|
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 <fstream> |
|
|
|
|
|
|
|
|
|
#pragma once |
|
|
|
|
|
|
|
|
|
#ifndef UNICODE |
|
|
|
@ -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]; |
|
|
|
@ -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 |
|
|
|
|
{ |
|
|
|
@ -463,8 +465,8 @@ public: |
|
|
|
|
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; |
|
|
|
@ -541,14 +543,14 @@ public: |
|
|
|
|
xe = x1; |
|
|
|
|
} |
|
|
|
|
Draw(x, y, c, col); |
|
|
|
|
for (i = 0; x<xe; i++) |
|
|
|
|
for (i = 0; x < xe; i++) |
|
|
|
|
{ |
|
|
|
|
x = x + 1; |
|
|
|
|
if (px<0) |
|
|
|
|
if (px < 0) |
|
|
|
|
px = px + 2 * dy1; |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
if ((dx<0 && dy<0) || (dx>0 && 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; y<ye; i++) |
|
|
|
|
for (i = 0; y < ye; i++) |
|
|
|
|
{ |
|
|
|
|
y = y + 1; |
|
|
|
|
if (py <= 0) |
|
|
|
|
py = py + 2 * dx1; |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
if ((dx<0 && dy<0) || (dx>0 && dy>0)) |
|
|
|
|
if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) |
|
|
|
|
x = x + 1; |
|
|
|
|
else |
|
|
|
|
x = x - 1; |
|
|
|
@ -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 |
|
|
|
@ -816,6 +822,60 @@ private: |
|
|
|
|
} |
|
|
|
|
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) |
|
|
|
|
{ |
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
case SDL_MOUSEBUTTONUP: |
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
switch (e.button.button) |
|
|
|
|
{ |
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -842,6 +902,28 @@ private: |
|
|
|
|
m_keyOldState[k] = m_keyNewState[k]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 Mouse Input - Check for window events
|
|
|
|
|
/* INPUT_RECORD inBuf[32];
|
|
|
|
|
DWORD events = 0; |
|
|
|
@ -913,7 +995,7 @@ private: |
|
|
|
|
|
|
|
|
|
m_mouseOldState[m] = m_mouseNewState[m]; |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
// Handle Frame Update
|
|
|
|
|
if (!OnUserUpdate(fElapsedTime)) |
|
|
|
@ -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); |
|
|
|
@ -1004,7 +1086,6 @@ public: |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected: |
|
|
|
|
|
|
|
|
|
struct sKeyState |
|
|
|
@ -1087,6 +1168,8 @@ protected: |
|
|
|
|
|
|
|
|
|
} m_keys; |
|
|
|
|
|
|
|
|
|
sKeyState m_mouse[5]; |
|
|
|
|
|
|
|
|
|
int m_mousePosX; |
|
|
|
|
int m_mousePosY; |
|
|
|
|
|
|
|
|
@ -1099,7 +1182,7 @@ public: |
|
|
|
|
|
|
|
|
|
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; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1116,6 +1199,11 @@ 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)); |
|
|
|
@ -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<bool> m_bAtomActive; |
|
|
|
|
static std::condition_variable m_cvGameFinished; |
|
|
|
|
static std::mutex m_muxGame; |
|
|
|
@ -1166,10 +1255,253 @@ 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<olcAudioSample> 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<sCurrentlyPlayingSample> 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<olcConsoleGameEngine *>(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; |
|
|
|
|
|
|
|
|
|
//thx J. Random Programmer
|
|
|
|
|
std::atomic<float> m_fGlobalTime = {0.0f}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
std::atomic<bool> olcConsoleGameEngine::m_bAtomActive(false); |
|
|
|
|
std::condition_variable olcConsoleGameEngine::m_cvGameFinished; |
|
|
|
|
std::mutex olcConsoleGameEngine::m_muxGame; |
|
|
|
|
|
|
|
|
|