Merge pull request #22 from gorbit99/patch-3

Added sound and audio
master
Javidx9 6 years ago committed by GitHub
commit a81f98983e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 388
      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
@ -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;

Loading…
Cancel
Save