diff --git a/Makefile b/Makefile index c2f129209..0d48d053c 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CC=g++ CFLAGS=-c -Wall -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads -I/usr/include/freetype2 -I/usr/include/SDL -I/usr/include -D_RPI_ -LDFLAGS=-L/opt/vc/lib -lbcm_host -lEGL -lGLESv2 -lfreetype -lSDL -lboost_system -lboost_filesystem -lfreeimage -SRCSOURCES=main.cpp Renderer.cpp Renderer_init.cpp Font.cpp Renderer_draw_gl.cpp GuiComponent.cpp InputManager.cpp SystemData.cpp GameData.cpp FolderData.cpp XMLReader.cpp MathExp.cpp components/GuiGameList.cpp components/GuiInputConfig.cpp components/GuiImage.cpp components/GuiMenu.cpp components/GuiTheme.cpp components/GuiFastSelect.cpp components/GuiBox.cpp pugiXML/pugixml.cpp +LDFLAGS=-L/opt/vc/lib -lbcm_host -lEGL -lGLESv2 -lfreetype -lSDL -lboost_system -lboost_filesystem -lfreeimage -lSDL_mixer +SRCSOURCES=main.cpp Renderer.cpp Renderer_init.cpp Font.cpp Renderer_draw_gl.cpp GuiComponent.cpp InputManager.cpp SystemData.cpp GameData.cpp FolderData.cpp XMLReader.cpp MathExp.cpp components/GuiGameList.cpp components/GuiInputConfig.cpp components/GuiImage.cpp components/GuiMenu.cpp components/GuiTheme.cpp components/GuiFastSelect.cpp components/GuiBox.cpp AudioManager.cpp Sound.cpp pugiXML/pugixml.cpp SOURCES=$(addprefix src/,$(SRCSOURCES)) OBJECTS=$(SOURCES:.cpp=.o) EXECUTABLE=emulationstation diff --git a/Makefile.x86 b/Makefile.x86 index f8147540c..59b969e35 100644 --- a/Makefile.x86 +++ b/Makefile.x86 @@ -1,7 +1,7 @@ CC=g++ CFLAGS=-c -Wall -I/usr/include/freetype2 -I/usr/include/SDL -I/usr/include -D_DESKTOP_ -g -LDFLAGS=-lGL -lfreetype -lSDL -lboost_system -lboost_filesystem -lfreeimage -SRCSOURCES=main.cpp Renderer.cpp Renderer_init.cpp Font.cpp Renderer_draw_gl.cpp GuiComponent.cpp InputManager.cpp SystemData.cpp GameData.cpp FolderData.cpp XMLReader.cpp MathExp.cpp components/GuiGameList.cpp components/GuiInputConfig.cpp components/GuiImage.cpp components/GuiMenu.cpp components/GuiTheme.cpp components/GuiFastSelect.cpp components/GuiBox.cpp pugiXML/pugixml.cpp +LDFLAGS=-lGL -lfreetype -lSDL -lboost_system -lboost_filesystem -lfreeimage -lSDL_mixer +SRCSOURCES=main.cpp Renderer.cpp Renderer_init.cpp Font.cpp Renderer_draw_gl.cpp GuiComponent.cpp InputManager.cpp SystemData.cpp GameData.cpp FolderData.cpp XMLReader.cpp MathExp.cpp components/GuiGameList.cpp components/GuiInputConfig.cpp components/GuiImage.cpp components/GuiMenu.cpp components/GuiTheme.cpp components/GuiFastSelect.cpp components/GuiBox.cpp Sound.cpp AudioManager.cpp pugiXML/pugixml.cpp SOURCES=$(addprefix src/,$(SRCSOURCES)) OBJECTS=$(SOURCES:.cpp=.o) EXECUTABLE=emulationstation diff --git a/THEMES.md b/THEMES.md index 9e1d17b1b..08353f00f 100644 --- a/THEMES.md +++ b/THEMES.md @@ -98,6 +98,14 @@ The Fast Select box can be themed with these tags: `` - path to the "top left corner" image file. It will be flipped for the top right, bottom right, and bottom left corners. ~ and . are expanded. +Audio +===== + +Themes can also define menu sounds. Sounds should be in the .wav format. + +`` - path to the sound to play when the game list is scrolling. + + List of variables ================= diff --git a/src/AudioManager.cpp b/src/AudioManager.cpp new file mode 100644 index 000000000..bdf6195da --- /dev/null +++ b/src/AudioManager.cpp @@ -0,0 +1,98 @@ +#include "AudioManager.h" + +#include "SDL.h" +#include "SDL_mixer.h" +#include +#include "Sound.h" +#include + +namespace AudioManager +{ + std::vector sSoundVector; + + bool sInitialized = false; + + void init() + { + int result = Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, AUDIO_S16SYS, 2, 1024); + + if(result == -1) + { + std::cerr << "Error initializing AudioManager!\n"; + std::cerr << " " << Mix_GetError() << "\n"; + return; + } + + sInitialized = true; + + for(unsigned int i = 0; i < sSoundVector.size(); i++) + { + sSoundVector.at(i)->init(); + } + } + + void registerSound(Sound* sound) + { + sSoundVector.push_back(sound); + } + + void unregisterSound(Sound* sound) + { + for(unsigned int i = 0; i < sSoundVector.size(); i++) + { + if(sSoundVector.at(i) == sound) + { + sSoundVector.erase(sSoundVector.begin() + i); + return; + } + } + + std::cerr << "AudioManager Error - tried to unregister a sound that wasn't registered!\n"; + } + + void test() + { + Mix_Chunk* sound = NULL; + sound = Mix_LoadWAV("test.wav"); + + if(sound == NULL) + { + std::cerr << "Error loading test sound!\n"; + std::cerr << " " << Mix_GetError() << "\n"; + return; + } + + int channel = -1; + + //third argument is play count, -1 = infinite loop, 0 = once + channel = Mix_PlayChannel(-1, sound, 0); + + if(channel == -1) + { + std::cerr << "Error playing sound!\n"; + std::cerr << " " << Mix_GetError() << "\n"; + return; + } + + while(Mix_Playing(channel) != 0); + Mix_FreeChunk(sound); + + } + + void deinit() + { + for(unsigned int i = 0; i < sSoundVector.size(); i++) + { + sSoundVector.at(i)->deinit(); + } + + Mix_CloseAudio(); + + sInitialized = false; + } + + bool isInitialized() + { + return sInitialized; + } +} diff --git a/src/AudioManager.h b/src/AudioManager.h new file mode 100644 index 000000000..a0ec11475 --- /dev/null +++ b/src/AudioManager.h @@ -0,0 +1,18 @@ +#ifndef _AUDIOMANAGER_H_ +#define _AUDIOMANAGER_H_ + +class Sound; + +namespace AudioManager +{ + void registerSound(Sound* sound); + void unregisterSound(Sound* sound); + + bool isInitialized(); + + void init(); + void test(); + void deinit(); +} + +#endif diff --git a/src/Renderer_draw_gl.cpp b/src/Renderer_draw_gl.cpp index 8ebdb2eba..2dd33d5a2 100644 --- a/src/Renderer_draw_gl.cpp +++ b/src/Renderer_draw_gl.cpp @@ -64,11 +64,12 @@ namespace Renderer { //make sure our font exists if(!boost::filesystem::exists(fontPath)) { - std::cout << "Default font \"" << fontPath << "\" does not exist! Attempting to default to a system font...\n"; + std::cout << "Default font \"" << fontPath << "\" does not exist! Attempting to default to a system font..."; fontPath = "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf"; if(!boost::filesystem::exists(fontPath)) { std::cerr << "System font \"" << fontPath << "\" wasn't found either! Well, you're kind of screwed. Sorry.\n"; + return; } } diff --git a/src/Renderer_init_sdlgl.cpp b/src/Renderer_init_sdlgl.cpp index 3263412bf..c5339ba1c 100644 --- a/src/Renderer_init_sdlgl.cpp +++ b/src/Renderer_init_sdlgl.cpp @@ -20,9 +20,10 @@ namespace Renderer { std::cout << "Creating surface..."; - if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) != 0) + if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO) != 0) { std::cerr << "Error initializing SDL!\n"; + std::cerr << " " << SDL_GetError() << "\n"; return false; } diff --git a/src/Sound.cpp b/src/Sound.cpp new file mode 100644 index 000000000..904699292 --- /dev/null +++ b/src/Sound.cpp @@ -0,0 +1,78 @@ +#include "Sound.h" +#include +#include "AudioManager.h" + +Sound::Sound(std::string path) +{ + mSound = NULL; + + AudioManager::registerSound(this); + + loadFile(path); +} + +Sound::~Sound() +{ + deinit(); + + AudioManager::unregisterSound(this); +} + +void Sound::loadFile(std::string path) +{ + mPath = path; + init(); +} + +void Sound::init() +{ + if(!AudioManager::isInitialized()) + return; + + if(mSound != NULL) + deinit(); + + if(mPath.empty()) + return; + + mSound = Mix_LoadWAV(mPath.c_str()); + + if(mSound == NULL) + { + std::cerr << "Error loading sound \"" << mPath << "\"!\n"; + std::cerr << " " << Mix_GetError() << "\n"; + } +} + +void Sound::deinit() +{ + if(mSound != NULL) + { + Mix_FreeChunk(mSound); + mSound = NULL; + } +} + +void Sound::play() +{ + if(mSound == NULL) + return; + + mChannel = -1; + + mChannel = Mix_PlayChannel(-1, mSound, 0); + if(mChannel == -1) + { + std::cerr << "Error playing sound!\n"; + std::cerr << " " << Mix_GetError() << "\n"; + } +} + +bool Sound::isPlaying() +{ + if(mChannel != -1 && Mix_Playing(mChannel)) + return true; + else + return false; +} + diff --git a/src/Sound.h b/src/Sound.h new file mode 100644 index 000000000..7301626b3 --- /dev/null +++ b/src/Sound.h @@ -0,0 +1,26 @@ +#ifndef _SOUND_H_ +#define _SOUND_H_ + +#include +#include "SDL_mixer.h" + +class Sound +{ +public: + Sound(std::string path = ""); + ~Sound(); + + void init(); + void deinit(); + + void loadFile(std::string path); + + void play(); + bool isPlaying(); +private: + std::string mPath; + int mChannel; + Mix_Chunk* mSound; +}; + +#endif diff --git a/src/SystemData.cpp b/src/SystemData.cpp index 92bbf6881..1c231dd28 100644 --- a/src/SystemData.cpp +++ b/src/SystemData.cpp @@ -6,6 +6,7 @@ #include #include #include "Renderer.h" +#include "AudioManager.h" std::vector SystemData::sSystemVector; @@ -74,6 +75,7 @@ void SystemData::launchGame(GameData* game) //suspend SDL joystick events (these'll pile up even while something else is running) SDL_JoystickEventState(0); + AudioManager::deinit(); Renderer::deinit(); std::string command = mLaunchCommand; @@ -88,6 +90,7 @@ void SystemData::launchGame(GameData* game) std::cout << "...launch terminated!\n"; Renderer::init(0, 0); + AudioManager::init(); //re-enable SDL joystick events SDL_JoystickEventState(1); diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index 0e1e0eca8..5864312cd 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -206,6 +206,7 @@ void GuiGameList::updateTheme() mList->setSelectorColor(mTheme->getSelectorColor()); mList->setSelectedTextColor(mTheme->getSelectedTextColor()); mList->setCentered(mTheme->getListCentered()); + mList->setScrollSound(mTheme->getMenuScrollSound()); if(mDetailed) { diff --git a/src/components/GuiGameList.h b/src/components/GuiGameList.h index 4379749aa..64caa7bed 100644 --- a/src/components/GuiGameList.h +++ b/src/components/GuiGameList.h @@ -11,7 +11,6 @@ #include "../GameData.h" #include "../FolderData.h" - //This is where the magic happens - GuiGameList is the parent of almost every graphical element in ES at the moment. //It has a GuiList child that handles the game list, a GuiTheme that handles the theming system, and a GuiImage for game images. class GuiGameList : GuiComponent diff --git a/src/components/GuiList.cpp b/src/components/GuiList.cpp index 266013137..2d7f79471 100644 --- a/src/components/GuiList.cpp +++ b/src/components/GuiList.cpp @@ -19,7 +19,7 @@ GuiList::GuiList(int offsetX, int offsetY, Renderer::FontSize fontsize mFont = fontsize; mSelectorColor = 0x000000; mSelectedTextColorOverride = -1; - + mScrollSound = NULL; mDrawCentered = true; InputManager::registerComponent(this); @@ -92,13 +92,13 @@ void GuiList::onInput(InputManager::InputButton button, bool keyDown) if(button == InputManager::DOWN) { mScrollDir = 1; - mSelection++; + scroll(); } if(button == InputManager::UP) { mScrollDir = -1; - mSelection--; + scroll(); } }else{ if((button == InputManager::DOWN && mScrollDir > 0) || (button == InputManager::UP && mScrollDir < 0)) @@ -108,12 +108,6 @@ void GuiList::onInput(InputManager::InputButton button, bool keyDown) mScrollDir = 0; } } - - if(mSelection < 0) - mSelection += mRowVector.size(); - - if(mSelection >= (int)mRowVector.size()) - mSelection -= mRowVector.size(); } } @@ -141,16 +135,26 @@ void GuiList::onTick(int deltaTime) { mScrollAccumulator -= SCROLLTIME; - mSelection += mScrollDir; - if(mSelection < 0) - mSelection += mRowVector.size(); - if(mSelection >= (int)mRowVector.size()) - mSelection -= mRowVector.size(); + scroll(); } } } } +template +void GuiList::scroll() +{ + mSelection += mScrollDir; + + if(mSelection < 0) + mSelection += mRowVector.size(); + if(mSelection >= (int)mRowVector.size()) + mSelection -= mRowVector.size(); + + if(mScrollSound) + mScrollSound->play(); +} + //list management stuff template void GuiList::addObject(std::string name, listType obj, int color) @@ -249,3 +253,9 @@ void GuiList::setSelection(int i) { mSelection = i; } + +template +void GuiList::setScrollSound(Sound* sound) +{ + mScrollSound = sound; +} diff --git a/src/components/GuiList.h b/src/components/GuiList.h index b1bd179e0..ab737edae 100644 --- a/src/components/GuiList.h +++ b/src/components/GuiList.h @@ -6,6 +6,7 @@ #include "../InputManager.h" #include #include +#include "../Sound.h" //A graphical list. Supports multiple colors for rows and scrolling. //TODO - add truncation to text rendering if name exceeds a maximum width (a trailing elipses, perhaps). @@ -34,7 +35,7 @@ public: void setSelectorColor(int selectorColor); void setSelectedTextColor(int selectedColor); void setCentered(bool centered); - + void setScrollSound(Sound* sound); void setTextOffsetX(int textoffsetx); int getObjectCount(); @@ -45,6 +46,8 @@ private: static const int SCROLLDELAY = 507; static const int SCROLLTIME = 200; + void scroll(); //helper method, scrolls in whatever direction scrollDir is + int mScrollDir, mScrollAccumulator; bool mScrolling; @@ -63,6 +66,7 @@ private: std::vector mRowVector; int mSelection; + Sound* mScrollSound; }; #include "GuiList.cpp" diff --git a/src/components/GuiTheme.cpp b/src/components/GuiTheme.cpp index 862821733..dc64f68f3 100644 --- a/src/components/GuiTheme.cpp +++ b/src/components/GuiTheme.cpp @@ -24,6 +24,8 @@ int GuiTheme::getSelectedTextColor() { return mListSelectedColor; } GuiBoxData GuiTheme::getBoxData() { return mBoxData; } +Sound* GuiTheme::getMenuScrollSound() { return &mMenuScrollSound; } + GuiTheme::GuiTheme(std::string path) { setDefaults(); @@ -60,6 +62,8 @@ void GuiTheme::setDefaults() mBoxData.verticalPath = ""; mBoxData.verticalTiled = false; mBoxData.cornerPath = ""; + + mMenuScrollSound.loadFile(""); } void GuiTheme::deleteComponents() @@ -127,10 +131,11 @@ void GuiTheme::readXML(std::string path) mGameImageOffsetY = strToFloat(root.child("gameImageOffsetY").text().get(), mGameImageOffsetY); mListTextOffsetX = strToFloat(root.child("listTextOffsetX").text().get(), mListTextOffsetX); + //sounds + mMenuScrollSound.loadFile(root.child("menuScrollSound").text().get()); + //recursively create children for all with proper parenting createComponentChildren(root, this); - - //std::cout << "Finished parsing theme.\n"; } void GuiTheme::createComponentChildren(pugi::xml_node node, GuiComponent* parent) diff --git a/src/components/GuiTheme.h b/src/components/GuiTheme.h index e0159826b..ff243114a 100644 --- a/src/components/GuiTheme.h +++ b/src/components/GuiTheme.h @@ -4,6 +4,7 @@ #include "../GuiComponent.h" #include "../pugiXML/pugixml.hpp" #include "GuiBox.h" +#include "../Sound.h" //This class loads an XML-defined list of GuiComponents. class GuiTheme : public GuiComponent @@ -29,6 +30,8 @@ public: float getGameImageOffsetY(); GuiBoxData getBoxData(); + + Sound* getMenuScrollSound(); private: void setDefaults(); void deleteComponents(); @@ -50,6 +53,8 @@ private: float mListOffsetX, mGameImageOffsetY, mListTextOffsetX; GuiBoxData mBoxData; + + Sound mMenuScrollSound; }; #endif diff --git a/src/main.cpp b/src/main.cpp index 8baea9864..1ce52118d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include #include "components/GuiInputConfig.h" #include +#include "AudioManager.h" #include "platform.h" @@ -71,7 +72,7 @@ int main(int argc, char* argv[]) bool running = true; - //the renderer also takes care of setting up SDL for input + //the renderer also takes care of setting up SDL for input and sound bool renderInit = Renderer::init(width, height); if(!renderInit) { @@ -80,9 +81,12 @@ int main(int argc, char* argv[]) } - SDL_JoystickEventState(SDL_ENABLE); + //initialize audio + AudioManager::init(); + SDL_JoystickEventState(SDL_ENABLE); + //make sure the config directory exists std::string home = getenv("HOME"); std::string configDir = home + "/.emulationstation"; @@ -171,6 +175,7 @@ int main(int argc, char* argv[]) } + AudioManager::deinit(); Renderer::deleteAll(); Renderer::deinit(); SystemData::deleteSystems();