Merge mangle into openmw

actorid
Nikolay Kasyanov 13 years ago
commit 48c12e9d5c

3
.gitmodules vendored

@ -1,6 +1,3 @@
[submodule "libs/mangle"]
path = libs/mangle
url = git://github.com/zinnschlag/mangle.git
[submodule "libs/openengine"]
path = libs/openengine
url = git://github.com/zinnschlag/OpenEngine

@ -1 +0,0 @@
Subproject commit 14b2851e72f610ae81dd296598867e6fb0babd2a

@ -0,0 +1,3 @@
upload_docs.sh
docs
*~

File diff suppressed because it is too large Load Diff

@ -0,0 +1,26 @@
Minimal Abstraction Game Layer (Mangle) is licensed under the
'zlib/libpng' license:
----
Copyright (c) 2009 Nicolay Korslund
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.

@ -0,0 +1,129 @@
Welcome to Mangle v0.1
----------------------
Written by: Nicolay Korslund (korslund@gmail.com)
License: zlib/png (see LICENSE.txt)
WWW: http://asm-soft.com/mangle/
Documentation: http://asm-soft.com/mangle/docs
Mangle is the project name for a small set of generic interfaces for
various game middleware libraries, such as sound, input, graphics, and
so on. You can imagine that it stands for "Minimal Abstraction Game
Layer", if you like. It will consist of several more or less
independent modules, one for each of these areas. These may be used
together to build an entire game engine, or they can be used
individually as separate libraries.
However, Mangle does NOT actually implement a game engine, or any new
fundamental functionality. More on that below.
Currently there's modules for sound and streams / archives (virtual
file systems.) More will come in the future (including input, 2D/3D
graphics, GUI, physics, and more.)
Main idea
---------
The idea behind Mangle is to provide a uniform, consistent interface
to other game libraries. The library does not provide ANY
functionality on its own. Instead it connects to a backend
implementation of your choice (or of your making.)
The Sound module, for example, currently has backends for OpenAL
(output only), FFmpeg (input only) and for Audiere. Hopefully we'll
add IrrKlang, FMod, DirectSound, Miles and more in the future. It can
combine libraries to get more complete functionality (like using
OpenAL for output and FFmpeg to decode sound files), and it's also
easy to write your own backend if you're using a different (or
home-brewed) sound system.
Regardless of what backend you use, the front-end interfaces (found
eg. in sound/output.h) is identical, and as a library user you
shouldn't notice much difference at all if you swap one backend for
another at a later point. It should Just Work.
The interfaces themselves are also quite simple. Setting up a sound
stream from FFmpeg or other decoder into OpenAL can be quite hairy -
but with Mangle the hairy parts have already been written for you. You
just plug the parts together.
The goal in the long run is to support a wide variety of game-related
libraries, and as many backend libraries (free and commercial) as
possible, so that you the user will have to write as little code as
possible.
What is it good for
-------------------
The main point of Mangle, as we said above, is that it connects to any
library of your choice "behind the scenes" but provides the same,
super-simple interface front-end for all of them. There can benefit
you in many ways:
- If you want to use a new library that Mangle support. You don't have
to scour the net for tutorials and usage examples, since much of the
common usage code is already included in the implementation classes.
- If you don't want to pollute your code with library-specific code.
The Mangle interfaces can help you keep your code clean, and its
user interface is often simpler than the exteral library one.
- If you want to quickly connect different libraries together, it
really helps if they speak a common language. The Mangle interfaces
are exactly that - a common language between libraries. Do you need
Audiere to load sounds from a weird archive format only implemented
for PhysFS, all channeled through the OGRE resource system? No
problem!
- If you are creating a library that depends on a specific feature
(such as sound), but you don't want to lock your users into any
specific sound library. Mangle works as an abstraction that lets
your users select their own implementation.
- If you want to support multiple backends for your game/app, or want
to make it possible to easily switch backends later. You can select
backends at compile time or even at runtime. For example you might
want to switch to to a commercial sound library at a later stage in
development, or you may want to use a different input library on
console platforms than on PC.
The Mangle implementations are extremely light-weight - often just one
or two cpp/h pairs per module. You can plug them directly into your
program, there's no separate library building step required.
Since the library aims to be very modularly put together, you can
also, in many cases, just copy-and-paste the parts you need and ignore
the rest. Or modify stuff without fearing that the whole 'system' will
come crashing down, because there is no big 'system' to speak of.
Past and future
---------------
Mangle started out as (and still is) a spin-off from OpenMW, another
project I am personally working on ( http://openmw.com/ ). OpenMW is
an attempt to recreate the engine behind the commercial game
Morrowind, using only open source software.
The projects are still tightly interlinked, and they will continue to
be until OpenMW is finished. Most near-future work on Mangle will be
focused chiefly on OpenMW at the moment. However I will gladly include
external contributions and suggestions that are not OpenMW-related if
someone sends them to me.
Conclusion
----------
As you might have guessed, Mangle is more a concept in development
than a finished library right now.
All feedback, ideas, concepts, questions and code are very
welcome. Send them to: korslund@gmail.com
I will put up a forum later as well if there's enough interest.

@ -0,0 +1,29 @@
#ifndef MANGLE_INPUT_OGREINPUTFRAME_H
#define MANGLE_INPUT_OGREINPUTFRAME_H
/*
This Ogre FrameListener calls capture() on an input driver every frame.
*/
#include <OgreFrameListener.h>
#include "../driver.hpp"
namespace Mangle {
namespace Input {
struct OgreInputCapture : Ogre::FrameListener
{
Mangle::Input::Driver &driver;
OgreInputCapture(Mangle::Input::Driver &drv)
: driver(drv) {}
bool frameStarted(const Ogre::FrameEvent &evt)
{
driver.capture();
return true;
}
};
}}
#endif

@ -0,0 +1,69 @@
#ifndef MANGLE_INPUT_DRIVER_H
#define MANGLE_INPUT_DRIVER_H
#include "event.hpp"
namespace Mangle
{
namespace Input
{
/** Input::Driver is the main interface to any input system that
handles keyboard and/or mouse input, along with any other
input source like joysticks.
It is really a generalized event system, and could also be
used for non-input related events. The definition of the event
codes and structures are entirely dependent on the
implementation.
A system-independent key code list will be found in keys.hpp,
and input drivers should privide optional translations to/from
this list for full compatibility.
*/
struct Driver
{
Driver() {}
virtual ~Driver() {}
/** Captures input and produces the relevant events from it. An
event callback must be set with setEvent(), or all events
will be ignored.
*/
virtual void capture() = 0;
/** Check the state of a given key or button. The key/button
definitions depends on the driver.
*/
virtual bool isDown(int index) = 0;
/** Show or hide system mouse cursor
*/
virtual void showMouse(bool show) = 0;
/** Set the event handler for input events. The evt->event()
function is called for each event. The meaning of the index
and *p parameters will be specific to each driver and to
each input system.
*/
void setEvent(EventPtr evt)
{ event = evt; }
/** Instigate an event. Is used internally for all events, but
can also be called from the outside to "fake" events from
this driver.
*/
void makeEvent(Event::Type type, int index, const void *p=NULL)
{
if(event)
event->event(type,index,p);
}
private:
/// Holds the event callback set byt setEvent()
EventPtr event;
};
typedef boost::shared_ptr<Driver> DriverPtr;
}
}
#endif

@ -0,0 +1,46 @@
#ifndef MANGLE_INPUT_EVENT_H
#define MANGLE_INPUT_EVENT_H
#include "../tools/shared_ptr.hpp"
namespace Mangle
{
namespace Input
{
/** Generic callback for input events. The meaning of the
parameters depend on the system producing the events.
*/
struct Event
{
/// Event types
enum Type
{
EV_Unknown = 1, // Unknown event type
EV_KeyDown = 2, // Keyboard button was pressed
EV_KeyUp = 4, // Keyboard button was released
EV_Keyboard = 6, // All keyboard events
EV_MouseMove = 8, // Mouse movement
EV_MouseDown = 16, // Mouse button pressed
EV_MouseUp = 32, // Mouse button released
EV_Mouse = 56, // All mouse events
EV_ALL = 63 // All events
};
/**
Called upon all events. The first parameter give the event
type, the second gives additional data (usually the local
keysym or button index as defined by the driver), and the
pointer points to the full custom event structure provided by
the driver (the type may vary depending on the EventType,
this is defined in the Driver documentation.)
*/
virtual void event(Type type, int index, const void *p) = 0;
virtual ~Event() {}
};
typedef boost::shared_ptr<Event> EventPtr;
}
}
#endif

@ -0,0 +1,47 @@
#ifndef MANGLE_INPUT_EVENTLIST_H
#define MANGLE_INPUT_EVENTLIST_H
#include "../event.hpp"
#include <vector>
namespace Mangle
{
namespace Input
{
/** And Event handler that distributes each event to a list of
other handlers. Supports filtering events by their Type
parameter.
*/
struct EventList : Event
{
struct Filter
{
EventPtr evt;
int flags;
};
std::vector<Filter> list;
void add(EventPtr e, int flags = EV_ALL)
{
Filter f;
f.evt = e;
f.flags = flags;
list.push_back(f);
}
virtual void event(Type type, int index, const void *p)
{
std::vector<Filter>::iterator it;
for(it=list.begin(); it!=list.end(); it++)
{
if(type & it->flags)
it->evt->event(type,index,p);
}
}
};
typedef boost::shared_ptr<EventList> EventListPtr;
}
}
#endif

@ -0,0 +1,148 @@
#include "ois_driver.hpp"
#include <assert.h>
#include <sstream>
#include <OgreRenderWindow.h>
#include <OIS/OIS.h>
#ifdef __APPLE_CC__
#include <Carbon/Carbon.h>
#endif
using namespace Mangle::Input;
using namespace OIS;
struct Mangle::Input::OISListener : OIS::KeyListener, OIS::MouseListener
{
OISDriver &drv;
OISListener(OISDriver &driver)
: drv(driver) {}
bool keyPressed( const OIS::KeyEvent &arg )
{
drv.makeEvent(Event::EV_KeyDown, arg.key, &arg);
return true;
}
bool keyReleased( const OIS::KeyEvent &arg )
{
drv.makeEvent(Event::EV_KeyUp, arg.key, &arg);
return true;
}
bool mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
{
// Mouse button events are handled as key events
// TODO: Translate mouse buttons into pseudo-keysyms
drv.makeEvent(Event::EV_MouseDown, id, &arg);
return true;
}
bool mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
{
// TODO: ditto
drv.makeEvent(Event::EV_MouseUp, id, &arg);
return true;
}
bool mouseMoved( const OIS::MouseEvent &arg )
{
drv.makeEvent(Event::EV_MouseMove, -1, &arg);
return true;
}
};
OISDriver::OISDriver(Ogre::RenderWindow *window, bool exclusive)
{
assert(window);
size_t windowHnd;
window->getCustomAttribute("WINDOW", &windowHnd);
std::ostringstream windowHndStr;
ParamList pl;
windowHndStr << windowHnd;
pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
// Set non-exclusive mouse and keyboard input if the user requested
// it.
if(!exclusive)
{
#if defined OIS_WIN32_PLATFORM
pl.insert(std::make_pair(std::string("w32_mouse"),
std::string("DISCL_FOREGROUND" )));
pl.insert(std::make_pair(std::string("w32_mouse"),
std::string("DISCL_NONEXCLUSIVE")));
pl.insert(std::make_pair(std::string("w32_keyboard"),
std::string("DISCL_FOREGROUND")));
pl.insert(std::make_pair(std::string("w32_keyboard"),
std::string("DISCL_NONEXCLUSIVE")));
#elif defined OIS_LINUX_PLATFORM
pl.insert(std::make_pair(std::string("x11_mouse_grab"),
std::string("false")));
pl.insert(std::make_pair(std::string("x11_mouse_hide"),
std::string("false")));
pl.insert(std::make_pair(std::string("x11_keyboard_grab"),
std::string("false")));
pl.insert(std::make_pair(std::string("XAutoRepeatOn"),
std::string("true")));
#endif
}
#ifdef __APPLE_CC__
// Give the application window focus to receive input events
ProcessSerialNumber psn = { 0, kCurrentProcess };
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
SetFrontProcess(&psn);
#endif
inputMgr = InputManager::createInputSystem( pl );
// Create all devices
keyboard = static_cast<Keyboard*>(inputMgr->createInputObject
( OISKeyboard, true ));
mouse = static_cast<Mouse*>(inputMgr->createInputObject
( OISMouse, true ));
// Set mouse region
const MouseState &ms = mouse->getMouseState();
ms.width = window->getWidth();
ms.height = window->getHeight();
// Set up the input listener
listener = new OISListener(*this);
keyboard-> setEventCallback(listener);
mouse-> setEventCallback(listener);
}
OISDriver::~OISDriver()
{
// Delete the listener object
if(listener)
delete listener;
if(inputMgr == NULL) return;
// Kill the input systems. This will reset input options such as key
// repeat rate.
inputMgr->destroyInputObject(keyboard);
inputMgr->destroyInputObject(mouse);
InputManager::destroyInputSystem(inputMgr);
inputMgr = NULL;
}
void OISDriver::capture()
{
// Capture keyboard and mouse events
keyboard->capture();
mouse->capture();
}
bool OISDriver::isDown(int index)
{
// TODO: Extend to mouse buttons as well
return keyboard->isKeyDown((OIS::KeyCode)index);
}

@ -0,0 +1,48 @@
#ifndef MANGLE_INPUT_OIS_DRIVER_H
#define MANGLE_INPUT_OIS_DRIVER_H
#include "../driver.hpp"
namespace OIS
{
class InputManager;
class Mouse;
class Keyboard;
}
namespace Ogre
{
class RenderWindow;
}
namespace Mangle
{
namespace Input
{
struct OISListener;
/** Input driver for OIS, the input manager typically used with
Ogre.
*/
struct OISDriver : Driver
{
/// If exclusive=true, then we capture mouse and keyboard from
/// the OS.
OISDriver(Ogre::RenderWindow *window, bool exclusive=true);
~OISDriver();
void capture();
bool isDown(int index);
/// Not currently supported.
void showMouse(bool) {}
private:
OIS::InputManager *inputMgr;
OIS::Mouse *mouse;
OIS::Keyboard *keyboard;
OISListener *listener;
};
}
}
#endif

@ -0,0 +1,54 @@
#include "sdl_driver.hpp"
#include <SDL.h>
using namespace Mangle::Input;
void SDLDriver::capture()
{
// Poll for events
SDL_Event evt;
while(SDL_PollEvent(&evt))
{
Event::Type type = Event::EV_Unknown;
int index = -1;
switch(evt.type)
{
// For key events, send the keysym as the index.
case SDL_KEYDOWN:
type = Event::EV_KeyDown;
index = evt.key.keysym.sym;
break;
case SDL_KEYUP:
type = Event::EV_KeyUp;
index = evt.key.keysym.sym;
break;
case SDL_MOUSEMOTION:
type = Event::EV_MouseMove;
break;
// Add more event types later
}
// Pass the event along, using -1 as index for unidentified
// event types.
makeEvent(type, index, &evt);
}
}
bool SDLDriver::isDown(int index)
{
int num;
Uint8 *keys = SDL_GetKeyState(&num);
assert(index >= 0 && index < num);
// The returned array from GetKeyState is indexed by the
// SDLK_KEYNAME enums and is just a list of bools. If the indexed
// value is true, the button is down.
return keys[index];
}
void SDLDriver::showMouse(bool show)
{
SDL_ShowCursor(show?SDL_ENABLE:SDL_DISABLE);
}

@ -0,0 +1,27 @@
#ifndef MANGLE_INPUT_SDL_DRIVER_H
#define MANGLE_INPUT_SDL_DRIVER_H
#include "../driver.hpp"
namespace Mangle
{
namespace Input
{
/** Input driver for SDL. As the input system of SDL is seldomly
used alone (most often along with the video system), it is
assumed that you do your own initialization and cleanup of SDL
before and after using this driver.
The Event.event() calls will be given the proper EV_ type, the
key index (for key up/down events), and a pointer to the full
SDL_Event structure.
*/
struct SDLDriver : Driver
{
void capture();
bool isDown(int index);
void showMouse(bool);
};
}
}
#endif

@ -0,0 +1,2 @@
*_test
ogre.cfg

@ -0,0 +1,15 @@
GCC=g++ -Wall
all: sdl_driver_test ois_driver_test evtlist_test
sdl_driver_test: sdl_driver_test.cpp
$(GCC) $< ../servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL
ois_driver_test: ois_driver_test.cpp
$(GCC) $< ../servers/ois_driver.cpp -o $@ -I/usr/local/include/OGRE/ -lOgreMain -lOIS -lboost_filesystem
evtlist_test: evtlist_test.cpp ../filters/eventlist.hpp ../event.hpp
$(GCC) $< -o $@
clean:
rm *_test

@ -0,0 +1,35 @@
#include <iostream>
#include "../driver.hpp"
#include <unistd.h>
using namespace std;
using namespace Mangle::Input;
Driver *input;
struct MyCB : Event
{
void event(Event::Type type, int i, const void *p)
{
cout << "got event: type=" << type << " index=" << i << endl;
}
};
void mainLoop(int argc, int quitKey)
{
cout << "Hold the Q key to quit:\n";
input->setEvent(EventPtr(new MyCB));
while(!input->isDown(quitKey))
{
input->capture();
usleep(20000);
if(argc == 1)
{
cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n";
break;
}
}
delete input;
cout << "\nBye bye!\n";
}

@ -0,0 +1,45 @@
#include <iostream>
#include "../filters/eventlist.hpp"
using namespace std;
using namespace Mangle::Input;
struct MyEvent : Event
{
int ii;
MyEvent(int i) : ii(i) {}
void event(Event::Type type, int i, const void *p)
{
cout << " #" << ii << " got event: type=" << type << " index=" << i << endl;
}
};
EventList lst;
int iii=1;
void make(int flags)
{
lst.add(EventPtr(new MyEvent(iii++)), flags);
}
void send(Event::Type type)
{
cout << "Sending type " << type << endl;
lst.event(type,0,NULL);
}
int main()
{
make(Event::EV_ALL);
make(Event::EV_KeyDown);
make(Event::EV_KeyUp | Event::EV_MouseDown);
send(Event::EV_Unknown);
send(Event::EV_KeyDown);
send(Event::EV_KeyUp);
send(Event::EV_MouseDown);
cout << "Enough of that\n";
return 0;
}

@ -0,0 +1,51 @@
#include "common.cpp"
#include "../servers/ois_driver.hpp"
#include <Ogre.h>
#include <OIS/OIS.h>
#include <boost/filesystem.hpp>
bool isFile(const char *name)
{
boost::filesystem::path cfg_file_path(name);
return boost::filesystem::exists(cfg_file_path);
}
using namespace Ogre;
using namespace OIS;
Root *root;
RenderWindow *window;
void setupOgre()
{
// Disable logging
new LogManager;
Log *log = LogManager::getSingleton().createLog("");
log->setDebugOutputEnabled(false);
bool useConfig = isFile("ogre.cfg");
// Set up Root
root = new Root("plugins.cfg", "ogre.cfg", "");
// Configure
if(!useConfig)
root->showConfigDialog();
else
root->restoreConfig();
// Initialize OGRE window
window = root->initialise(true, "test", "");
}
int main(int argc, char** argv)
{
setupOgre();
input = new OISDriver(window);
mainLoop(argc, KC_Q);
delete root;
return 0;
}

@ -0,0 +1,12 @@
Sending type 1
#1 got event: type=1 index=0
Sending type 2
#1 got event: type=2 index=0
#2 got event: type=2 index=0
Sending type 4
#1 got event: type=4 index=0
#3 got event: type=4 index=0
Sending type 16
#1 got event: type=16 index=0
#3 got event: type=16 index=0
Enough of that

@ -0,0 +1,5 @@
Hold the Q key to quit:
got event: type=8 index=-1
You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly
Bye bye!

@ -0,0 +1,5 @@
Hold the Q key to quit:
got event: type=1 index=-1
You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly
Bye bye!

@ -0,0 +1,12 @@
# Defines plugins to load
# Define plugin folder
PluginFolder=/usr/local/lib/OGRE/
# Define plugins
Plugin=RenderSystem_GL
Plugin=Plugin_ParticleFX
Plugin=Plugin_OctreeSceneManager
# Plugin=Plugin_CgProgramManager

@ -0,0 +1,16 @@
#include "common.cpp"
#include "../servers/sdl_driver.hpp"
#include <SDL.h>
int main(int argc, char** argv)
{
SDL_Init(SDL_INIT_VIDEO);
SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE);
input = new SDLDriver();
mainLoop(argc, SDLK_q);
SDL_Quit();
return 0;
}

@ -0,0 +1,18 @@
#!/bin/bash
make || exit
mkdir -p output
PROGS=*_test
for a in $PROGS; do
if [ -f "output/$a.out" ]; then
echo "Running $a:"
./$a | diff output/$a.out -
else
echo "Creating $a.out"
./$a > "output/$a.out"
git add "output/$a.out"
fi
done

@ -0,0 +1,63 @@
#ifndef MANGLE_REND2D_DRIVER_H
#define MANGLE_REND2D_DRIVER_H
#include <string>
#include "sprite.hpp"
namespace Mangle
{
namespace Rend2D
{
/**
The driver is the connection to the backend system that powers
2D sprite rendering. For example the backend could be SDL or
any other 2D-capable graphics library.
*/
struct Driver
{
/// Get the screen sprite
virtual Sprite *getScreen() = 0;
/// Sets the video mode.
virtual void setVideoMode(int width, int height, int bpp=32, bool fullscreen=false) = 0;
/** Update the screen. Until this function is called, none of
the changes written to the screen sprite will be visible.
*/
virtual void update() = 0;
/// Set the window title, as well as the title of the window
/// when "iconified"
virtual void setWindowTitle(const std::string &title,
const std::string &icon) = 0;
/// Set the window title
void setWindowTitle(const std::string &title) { setWindowTitle(title,title); }
/// Load sprite from an image file. Thows an exception on
/// failure.
virtual Sprite* loadImage(const std::string &file) = 0;
/// Load a sprite from an image file stored in memory. Throws
/// exception on failure.
virtual Sprite* loadImage(const void* data, size_t size) = 0;
/** @brief Set gamma value for all colors.
Note: Setting this in windowed mode will affect the ENTIRE
SCREEN!
*/
virtual void setGamma(float gamma) = 0;
/// Set gamma individually for red, green, blue
virtual void setGamma(float red, float green, float blue) = 0;
/// Get screen width
virtual int width() = 0;
/// Get screen height
virtual int height() = 0;
};
}
}
#endif

@ -0,0 +1,259 @@
#include "sdl_driver.hpp"
#include <SDL.h>
#include <SDL_image.h>
#include <stdexcept>
#include <assert.h>
using namespace Mangle::Rend2D;
const SpriteData *SDL_Sprite::lock()
{
// Make sure we aren't already locked
assert(!data.pixels);
// Lock the surface and set up the data structure
SDL_LockSurface(surface);
data.pixels = surface->pixels;
data.w = surface->w;
data.h = surface->h;
data.pitch = surface->pitch;
data.bypp = surface->format->BytesPerPixel;
return &data;
}
void SDL_Sprite::unlock()
{
if(data.pixels)
{
SDL_UnlockSurface(surface);
data.pixels = NULL;
}
}
// This is a really crappy and slow implementation, only intended for
// testing purposes. Use lock/unlock for faster pixel drawing.
void SDL_Sprite::pixel(int x, int y, int color)
{
SDL_LockSurface(surface);
int bpp = surface->format->BytesPerPixel;
char *p = (char*)surface->pixels + y*surface->pitch + x*bpp;
switch(bpp)
{
case 1: *p = color; break;
case 3:
if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
{
p[0] = (color >> 16) & 0xff;
p[1] = (color >> 8) & 0xff;
p[2] = color & 0xff;
}
else
{
p[0] = color & 0xff;
p[1] = (color >> 8) & 0xff;
p[2] = (color >> 16) & 0xff;
}
break;
case 4:
*(int*)p = color;
break;
}
SDL_UnlockSurface(surface);
}
void SDL_Sprite::draw(Sprite *s, // Must be SDL_Sprite
int x, int y, // Destination position
int sx, int sy, // Source position
int w, int h // Amount to draw. -1 means remainder.
)
{
// Get source surface
SDL_Sprite *other = dynamic_cast<SDL_Sprite*>(s);
assert(other != NULL);
SDL_Surface *img = other->getSurface();
// Check coordinate validity
assert(sx <= img->w && sy <= img->h);
assert(x <= surface->w && y <= surface->h);
assert(sx >= 0 && sy >= 0);
// Compute width and height if necessary
if(w == -1) w = img->w - sx;
if(h == -1) h = img->h - sy;
// Check them if they're valid
assert(w >= 0 && w <= img->w);
assert(h >= 0 && h <= img->h);
SDL_Rect dest;
dest.x = x;
dest.y = y;
dest.w = w;
dest.h = h;
SDL_Rect src;
src.x = sx;
src.y = sy;
src.w = w;
src.h = h;
// Do the Blitman
SDL_BlitSurface(img, &src, surface, &dest);
}
SDL_Sprite::SDL_Sprite(SDL_Surface *s, bool autoDelete)
: surface(s), autoDel(autoDelete)
{
assert(surface != NULL);
data.pixels = NULL;
}
SDL_Sprite::~SDL_Sprite()
{
if(autoDel)
SDL_FreeSurface(surface);
}
void SDL_Sprite::fill(int value)
{
SDL_FillRect(surface, NULL, value);
}
int SDL_Sprite::width() { return surface->w; }
int SDL_Sprite::height() { return surface->h; }
SDLDriver::SDLDriver() : display(NULL), realDisp(NULL), softDouble(false)
{
if (SDL_InitSubSystem( SDL_INIT_VIDEO ) == -1)
throw std::runtime_error("Error initializing SDL video");
}
SDLDriver::~SDLDriver()
{
if(display) delete display;
SDL_Quit();
}
void SDLDriver::setVideoMode(int width, int height, int bpp, bool fullscreen)
{
unsigned int flags;
if(display) delete display;
if (fullscreen)
// Assume fullscreen mode allows a double-bufferd hardware
// mode. We need more test code for this to be safe though.
flags = SDL_FULLSCREEN | SDL_HWSURFACE | SDL_DOUBLEBUF;
else
flags = SDL_SWSURFACE;
// Create the surface and check it
realDisp = SDL_SetVideoMode(width, height, bpp, flags);
if(realDisp == NULL)
throw std::runtime_error("Failed setting SDL video mode");
// Code for software double buffering. I haven't found this to be
// any speed advantage at all in windowed mode (it's slower, as one
// would expect.) Not properly tested in fullscreen mode with
// hardware buffers, but it will probably only be an improvement if
// we do excessive writing (ie. write each pixel on average more
// than once) or try to read from the display buffer.
if(softDouble)
{
// Make a new surface with the same attributes as the real
// display surface.
SDL_Surface *back = SDL_DisplayFormat(realDisp);
assert(back != NULL);
// Create a sprite representing the double buffer
display = new SDL_Sprite(back);
}
else
{
// Create a sprite directly representing the display surface.
// The 'false' parameter means do not autodelete the screen
// surface upon exit (since SDL manages it)
display = new SDL_Sprite(realDisp, false);
}
}
/// Update the screen
void SDLDriver::update()
{
// Blit the soft double buffer onto the real display buffer
if(softDouble)
SDL_BlitSurface(display->getSurface(), NULL, realDisp, NULL );
if(realDisp)
SDL_Flip(realDisp);
}
/// Set the window title, as well as the title of the window when
/// "iconified"
void SDLDriver::setWindowTitle(const std::string &title,
const std::string &icon)
{
SDL_WM_SetCaption( title.c_str(), icon.c_str() );
}
// Convert the given surface to display format.
static SDL_Surface* convertImage(SDL_Surface* surf)
{
if(surf != NULL)
{
// Convert the image to the display buffer format, for faster
// blitting
SDL_Surface *surf2 = SDL_DisplayFormat(surf);
SDL_FreeSurface(surf);
surf = surf2;
}
return surf;
}
/// Load sprite from an image file, using SDL_image.
Sprite* SDLDriver::loadImage(const std::string &file)
{
SDL_Surface *surf = IMG_Load(file.c_str());
surf = convertImage(surf);
if(surf == NULL)
throw std::runtime_error("SDL failed to load image file '" + file + "'");
return spriteFromSDL(surf);
}
/// Load sprite from an SDL_RWops structure. autoFree determines
/// whether the RWops struct should be closed/freed after use.
Sprite* SDLDriver::loadImage(SDL_RWops *src, bool autoFree)
{
SDL_Surface *surf = IMG_Load_RW(src, autoFree);
surf = convertImage(surf);
if(surf == NULL)
throw std::runtime_error("SDL failed to load image");
return spriteFromSDL(surf);
}
/// Load a sprite from an image file stored in memory. Uses
/// SDL_image.
Sprite* SDLDriver::loadImage(const void* data, size_t size)
{
SDL_RWops *rw = SDL_RWFromConstMem(data, size);
return loadImage(rw, true);
}
void SDLDriver::setGamma(float red, float green, float blue)
{
SDL_SetGamma(red,green,blue);
}
/// Convert an existing SDL surface into a sprite
Sprite* SDLDriver::spriteFromSDL(SDL_Surface *surf, bool autoFree)
{
assert(surf);
return new SDL_Sprite(surf, autoFree);
}
void SDLDriver::sleep(int ms) { SDL_Delay(ms); }
unsigned int SDLDriver::ticks() { return SDL_GetTicks(); }

@ -0,0 +1,125 @@
#ifndef MANGLE_DRAW2D_SDL_H
#define MANGLE_DRAW2D_SDL_H
#include "../driver.hpp"
// Predeclarations keep the streets safe at night
struct SDL_Surface;
struct SDL_RWops;
namespace Mangle
{
namespace Rend2D
{
/// SDL-implementation of Sprite
struct SDL_Sprite : Sprite
{
/** Draw a sprite in the given position. Can only draw other SDL
sprites.
*/
void draw(Sprite *s, // Must be SDL_Sprite
int x, int y, // Destination position
int sx=0, int sy=0, // Source position
int w=-1, int h=-1 // Amount to draw. -1 means remainder.
);
SDL_Sprite(SDL_Surface *s, bool autoDelete=true);
~SDL_Sprite();
// Information retrieval
int width();
int height();
SDL_Surface *getSurface() { return surface; }
// Fill with a given pixel value
void fill(int value);
// Set one pixel
void pixel(int x, int y, int value);
const SpriteData *lock();
void unlock();
private:
// The SDL surface
SDL_Surface* surface;
// Used for locking
SpriteData data;
// If true, delete this surface when the canvas is destructed
bool autoDel;
};
class SDLDriver : public Driver
{
// The main display surface
SDL_Sprite *display;
// The actual display surface. May or may not be the same
// surface pointed to by 'display' above, depending on the
// softDouble flag.
SDL_Surface *realDisp;
// If true, we do software double buffering.
bool softDouble;
public:
SDLDriver();
~SDLDriver();
/// Sets the video mode. Will create the window if it is not
/// already set up. Note that for SDL, bpp=0 means use current
/// bpp.
void setVideoMode(int width, int height, int bpp=0, bool fullscreen=false);
/// Update the screen
void update();
/// Set the window title, as well as the title of the window
/// when "iconified"
void setWindowTitle(const std::string &title,
const std::string &icon);
// Include overloads from our Glorious parent
using Driver::setWindowTitle;
/// Load sprite from an image file, using SDL_image.
Sprite* loadImage(const std::string &file);
/// Load sprite from an SDL_RWops structure. autoFree determines
/// whether the RWops struct should be closed/freed after use.
Sprite* loadImage(SDL_RWops *src, bool autoFree=false);
/// Load a sprite from an image file stored in memory. Uses
/// SDL_image.
Sprite* loadImage(const void* data, size_t size);
/// Set gamma value
void setGamma(float gamma) { setGamma(gamma,gamma,gamma); }
/// Set gamma individually for red, green, blue
void setGamma(float red, float green, float blue);
/// Convert an existing SDL surface into a sprite
Sprite* spriteFromSDL(SDL_Surface *surf, bool autoFree = true);
// Get width and height
int width() { return display ? display->width() : 0; }
int height() { return display ? display->height() : 0; }
/// Get the screen sprite
Sprite *getScreen() { return display; }
/// Not really a graphic-related function, but very
/// handly. Sleeps the given number of milliseconds using
/// SDL_Delay().
void sleep(int ms);
/// Get the number of ticks since SDL initialization, using
/// SDL_GetTicks().
unsigned int ticks();
};
}
}
#endif

@ -0,0 +1,311 @@
#include "sdl_gl_driver.hpp"
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_opengl.h>
#include <stdexcept>
#include <assert.h>
using namespace Mangle::Rend2D;
void SDLGL_Sprite::draw(Sprite *s, // Must be SDLGL_Sprite
int x, int y, // Destination position
int sx, int sy, // Source position
int w, int h // Amount to draw. -1 means remainder.
)
{
// Get source surface
SDLGL_Sprite *other = dynamic_cast<SDLGL_Sprite*>(s);
assert(other != NULL);
SDL_Surface *img = other->getSurface();
// Check coordinate validity
assert(sx <= img->w && sy <= img->h);
assert(x <= surface->w && y <= surface->h);
assert(sx >= 0 && sy >= 0);
// Compute width and height if necessary
if(w == -1) w = img->w - sx;
if(h == -1) h = img->h - sy;
// Check them if they're valid
assert(w >= 0 && w <= img->w);
assert(h >= 0 && h <= img->h);
SDL_Rect dest;
dest.x = x;
dest.y = y;
dest.w = w;
dest.h = h;
SDL_Rect src;
src.x = sx;
src.y = sy;
src.w = w;
src.h = h;
// Do the Blitman
SDL_BlitSurface(img, &src, surface, &dest);
}
SDLGL_Sprite::SDLGL_Sprite(SDL_Surface *s, bool autoDelete)
: surface(s), autoDel(autoDelete)
{
assert(surface != NULL);
}
SDLGL_Sprite::~SDLGL_Sprite()
{
if(autoDel)
SDL_FreeSurface(surface);
}
void SDLGL_Sprite::fill(int value)
{
SDL_FillRect(surface, NULL, value);
}
int SDLGL_Sprite::width() { return surface->w; }
int SDLGL_Sprite::height() { return surface->h; }
SDLGLDriver::SDLGLDriver() : display(NULL), realDisp(NULL)
{
if (SDL_InitSubSystem( SDL_INIT_VIDEO ) == -1)
throw std::runtime_error("Error initializing SDL video");
}
SDLGLDriver::~SDLGLDriver()
{
if(display) delete display;
SDL_Quit();
}
// Surface used for the screen. Since OpenGL surfaces must have sizes
// that are powers of 2, we have to "fake" the returned display size
// to match the screen, not the surface itself. If we don't use this,
// the client program will get confused about the actual size of our
// screen, thinking it is bigger than it is.
struct FakeSizeSprite : SDLGL_Sprite
{
int fakeW, fakeH;
FakeSizeSprite(SDL_Surface *s, int fw, int fh)
: SDLGL_Sprite(s), fakeW(fw), fakeH(fh)
{}
int width() { return fakeW; }
int height() { return fakeH; }
};
static int makePow2(int num)
{
assert(num);
if((num & (num-1)) != 0)
{
int cnt = 0;
while(num)
{
num >>= 1;
cnt++;
}
num = 1 << cnt;
}
return num;
}
void SDLGLDriver::setVideoMode(int width, int height, int bpp, bool fullscreen)
{
unsigned int flags;
if(display) delete display;
flags = SDL_OPENGL;
if (fullscreen)
flags |= SDL_FULLSCREEN;
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 1 );
// Create the surface and check it
screen = SDL_SetVideoMode(width, height, bpp, flags);
if(screen == NULL)
throw std::runtime_error("Failed setting SDL video mode");
// Expand width and height to be powers of 2
int width2 = makePow2(width);
int height2 = makePow2(height);
// Create a new SDL surface of this size
const SDL_PixelFormat& fmt = *(screen->format);
realDisp = SDL_CreateRGBSurface(SDL_SWSURFACE,width2,height2,
fmt.BitsPerPixel,
fmt.Rmask,fmt.Gmask,fmt.Bmask,fmt.Amask);
// Create a sprite directly representing the display surface. This
// allows the user to blit to it directly.
display = new FakeSizeSprite(realDisp, width, height);
// Set up the OpenGL format
nOfColors = fmt.BytesPerPixel;
if(nOfColors == 4)
{
if (fmt.Rmask == 0x000000ff)
texture_format = GL_RGBA;
else
texture_format = GL_BGRA;
}
else if(nOfColors == 3)
{
if (fmt.Rmask == 0x000000ff)
texture_format = GL_RGB;
else
texture_format = GL_BGR;
}
else
assert(0 && "unsupported screen format");
glEnable(GL_TEXTURE_2D);
// Have OpenGL generate a texture object handle for us
glGenTextures( 1, &texture );
// Bind the texture object
glBindTexture( GL_TEXTURE_2D, texture );
// Set the texture's stretching properties
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
}
void SDLGLDriver::updateNoSwap()
{
if(!realDisp) return;
// Fist, set up the screen texture:
// Bind the texture object
glBindTexture( GL_TEXTURE_2D, texture );
// Edit the texture object's image data
glTexImage2D( GL_TEXTURE_2D, 0, nOfColors, realDisp->w, realDisp->h, 0,
texture_format, GL_UNSIGNED_BYTE, realDisp->pixels );
glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
// OpenGL barf. Set up the projection to match our screen
int vPort[4];
glGetIntegerv(GL_VIEWPORT, vPort);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0, vPort[2], 0, vPort[3], -1, 1);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glBegin( GL_QUADS );
// Needed to move the screen into the right place
int diff = screen->h - realDisp->h;
// Bottom-left vertex (corner)
glTexCoord2i( 0, 1 );
glVertex3f(0,diff,0);
// Bottom-right vertex (corner)
glTexCoord2i( 1, 1 );
glVertex3f( realDisp->w, diff, 0.f );
// Top-right vertex (corner)
glTexCoord2i( 1, 0 );
glVertex3f( realDisp->w, screen->h, 0.f );
// Top-left vertex (corner)
glTexCoord2i( 0, 0 );
glVertex3f( 0, screen->h, 0.f );
glEnd();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
void SDLGLDriver::swap()
{
SDL_GL_SwapBuffers();
}
void SDLGLDriver::update()
{
updateNoSwap();
swap();
}
/// Set the window title, as well as the title of the window when
/// "iconified"
void SDLGLDriver::setWindowTitle(const std::string &title,
const std::string &icon)
{
SDL_WM_SetCaption( title.c_str(), icon.c_str() );
}
// Convert the given surface to display format.
static SDL_Surface* convertImage(SDL_Surface* surf)
{
if(surf != NULL)
{
// Convert the image to the display buffer format, for faster
// blitting
SDL_Surface *surf2 = SDL_DisplayFormat(surf);
SDL_FreeSurface(surf);
surf = surf2;
}
return surf;
}
/// Load sprite from an image file, using SDL_image.
Sprite* SDLGLDriver::loadImage(const std::string &file)
{
SDL_Surface *surf = IMG_Load(file.c_str());
surf = convertImage(surf);
if(surf == NULL)
throw std::runtime_error("SDL failed to load image file '" + file + "'");
return spriteFromSDL(surf);
}
/// Load sprite from an SDL_RWops structure. autoFree determines
/// whether the RWops struct should be closed/freed after use.
Sprite* SDLGLDriver::loadImage(SDL_RWops *src, bool autoFree)
{
SDL_Surface *surf = IMG_Load_RW(src, autoFree);
surf = convertImage(surf);
if(surf == NULL)
throw std::runtime_error("SDL failed to load image");
return spriteFromSDL(surf);
}
/// Load a sprite from an image file stored in memory. Uses
/// SDL_image.
Sprite* SDLGLDriver::loadImage(const void* data, size_t size)
{
SDL_RWops *rw = SDL_RWFromConstMem(data, size);
return loadImage(rw, true);
}
void SDLGLDriver::setGamma(float red, float green, float blue)
{
SDL_SetGamma(red,green,blue);
}
/// Convert an existing SDL surface into a sprite
Sprite* SDLGLDriver::spriteFromSDL(SDL_Surface *surf, bool autoFree)
{
assert(surf);
return new SDLGL_Sprite(surf, autoFree);
}
void SDLGLDriver::sleep(int ms) { SDL_Delay(ms); }
unsigned int SDLGLDriver::ticks() { return SDL_GetTicks(); }

@ -0,0 +1,132 @@
#ifndef MANGLE_DRAW2D_SDLGL_H
#define MANGLE_DRAW2D_SDLGL_H
/** This driver is similar to SDLDriver, except that it uses SDL on
top of OpenGL.
I've decided to make it a separate file instead of just adding
optional OpenGL support to the original, so that pure SDL users
don't have to add OpenGL as a dependency.
*/
#include "../driver.hpp"
// Predeclarations keep the streets safe at night
struct SDL_Surface;
struct SDL_RWops;
namespace Mangle
{
namespace Rend2D
{
/// SDL-implementation of Sprite
struct SDLGL_Sprite : Sprite
{
/** Draw a sprite in the given position. Can only draw other SDL
sprites.
*/
void draw(Sprite *s, // Must be SDLGL_Sprite
int x, int y, // Destination position
int sx=0, int sy=0, // Source position
int w=-1, int h=-1 // Amount to draw. -1 means remainder.
);
SDLGL_Sprite(SDL_Surface *s, bool autoDelete=true);
~SDLGL_Sprite();
// Information retrieval
virtual int width();
virtual int height();
SDL_Surface *getSurface() { return surface; }
// Fill with a given pixel value
void fill(int value);
private:
// The SDL surface
SDL_Surface* surface;
// If true, delete this surface when the canvas is destructed
bool autoDel;
};
class SDLGLDriver : public Driver
{
// The main display surface
SDLGL_Sprite *display;
// The screen surface. This is completely unused.
SDL_Surface *screen;
// The display surface and main GL texture. These are used when
// drawing the entire screen as one surface, as a drop-in
// replacement for SDLDriver.
SDL_Surface *realDisp;
unsigned int texture;
int nOfColors, texture_format;
public:
SDLGLDriver();
~SDLGLDriver();
/// Sets the video mode. Will create the window if it is not
/// already set up. Note that for SDL, bpp=0 means use current
/// bpp.
void setVideoMode(int width, int height, int bpp=0, bool fullscreen=false);
/// Update the screen
void update();
/// Calls SDL_GL_SwapBuffers
void swap();
/// Draw surface to screen but do not call SDL_GL_SwapBuffers()
void updateNoSwap();
/// Set the window title, as well as the title of the window
/// when "iconified"
void setWindowTitle(const std::string &title,
const std::string &icon);
// Include overloads from our Glorious parent
using Driver::setWindowTitle;
/// Load sprite from an image file, using SDL_image.
Sprite* loadImage(const std::string &file);
/// Load sprite from an SDL_RWops structure. autoFree determines
/// whether the RWops struct should be closed/freed after use.
Sprite* loadImage(SDL_RWops *src, bool autoFree=false);
/// Load a sprite from an image file stored in memory. Uses
/// SDL_image.
Sprite* loadImage(const void* data, size_t size);
/// Set gamma value
void setGamma(float gamma) { setGamma(gamma,gamma,gamma); }
/// Set gamma individually for red, green, blue
void setGamma(float red, float green, float blue);
/// Convert an existing SDL surface into a sprite
Sprite* spriteFromSDL(SDL_Surface *surf, bool autoFree = true);
// Get width and height
int width() { return display ? display->width() : 0; }
int height() { return display ? display->height() : 0; }
/// Get the screen sprite
Sprite *getScreen() { return display; }
/// Not really a graphic-related function, but very
/// handly. Sleeps the given number of milliseconds using
/// SDL_Delay().
void sleep(int ms);
/// Get the number of ticks since SDL initialization, using
/// SDL_GetTicks().
unsigned int ticks();
};
}
}
#endif

@ -0,0 +1,57 @@
#ifndef MANGLE_REND2D_SPRITE_H
#define MANGLE_REND2D_SPRITE_H
namespace Mangle
{
namespace Rend2D
{
/**
A pointer to sprite data for direct drawing. Only to be used
while the corresponding sprite is locked.
*/
struct SpriteData
{
void *pixels; // Pixel data
int w, h; // Width and height
int pitch, bypp; // Pitch (bytes) and bytes per pixel
};
/**
A Sprite is either a bitmap to be drawn or an output of area
for blitting other bitmaps, or both. They are created by the
Driver.
*/
struct Sprite
{
/// Draw a sprite in the given position
virtual void draw(Sprite *s, // The sprite to draw
int x, int y, // Destination position
int sx=0, int sy=0, // Source position
int w=-1, int h=-1 // Amount to draw. -1 means remainder.
) = 0;
virtual ~Sprite() {}
// Information retrieval
virtual int width() = 0;
virtual int height() = 0;
/// Fill the sprite with the given pixel value. The pixel format
/// depends on the format of the sprite.
virtual void fill(int value) = 0;
/// Set one pixel value. The pixel format depends on the sprite
/// format. This is not expected to be fast, and in some
/// implementations may not work at all.
virtual void pixel(int x, int y, int value) {}
/// Lock sprite for direct drawing, and return a struct
/// containing the necessary pointer. When finished, unlock the
/// sprite with unlock(). May return NULL, if so then direct
/// drawing is not possible.
virtual const SpriteData *lock() { return NULL; }
virtual void unlock() {}
};
}
}
#endif

@ -0,0 +1,15 @@
GCC=g++ -Wall -Werror
all: sdl_test sdl_move_test sdlgl_move_test
sdl_test: sdl_test.cpp
$(GCC) $< ../servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image
sdl_move_test: sdl_move_test.cpp ../servers/sdl_driver.cpp
$(GCC) $^ -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image
sdlgl_move_test: sdlgl_move_test.cpp ../servers/sdl_gl_driver.cpp
$(GCC) $^ -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image -lGL
clean:
rm *_test

@ -0,0 +1,11 @@
Loading SDL driver.
Creating window.
Current mode: 640x480
Setting fancy title, cause we like fancy titles.
Loading tile1-blue.png from file.
Loading tile1-yellow.png from memory.
Going bananas.
Taking a breather.
WOW DID YOU SEE THAT!?
Mucking about with the gamma settings
Done.

@ -0,0 +1,30 @@
#include <iostream>
#include <fstream>
using namespace std;
#include "../servers/sdl_driver.hpp"
using namespace Mangle::Rend2D;
int main()
{
SDLDriver sdl;
sdl.setVideoMode(640,480,0,false);
sdl.setWindowTitle("Testing 123");
Sprite *screen = sdl.getScreen();
const char* imgName = "tile1-blue.png";
Sprite *image = sdl.loadImage(imgName);
for(int frames=0; frames<170; frames++)
{
screen->fill(0);
for(int j=0; j<10; j++)
for(int i=0; i<25; i++)
screen->draw(image, 2*frames+30*j, 20*i);
sdl.update();
sdl.sleep(10);
}
return 0;
}

@ -0,0 +1,65 @@
#include <iostream>
#include <fstream>
using namespace std;
#include "../servers/sdl_driver.hpp"
using namespace Mangle::Rend2D;
int main()
{
cout << "Loading SDL driver.\n";
SDLDriver sdl;
cout << "Creating window.\n";
sdl.setVideoMode(640,480);
cout << "Current mode: " << sdl.width() << "x" << sdl.height() << endl;
cout << "Setting fancy title, cause we like fancy titles.\n";
sdl.setWindowTitle("Chief executive window");
// Display surface
Sprite *screen = sdl.getScreen();
const char* imgName = "tile1-blue.png";
cout << "Loading " << imgName << " from file.\n";
Sprite *image = sdl.loadImage(imgName);
const char* imgName2 = "tile1-yellow.png";
cout << "Loading " << imgName2 << " from memory.\n";
Sprite *image2;
{
// This is hard-coded for file sizes below 500 bytes, so obviously
// you shouldn't mess with the image files.
ifstream file(imgName2, ios::binary);
char buf[500];
file.read(buf, 500);
int size = file.gcount();
image2 = sdl.loadImage(buf, size);
}
cout << "Going bananas.\n";
for(int i=1; i<20; i++)
screen->draw(image, 30*i, 20*i);
cout << "Taking a breather.\n";
sdl.update();
for(int i=1; i<20; i++)
screen->draw(image2, 30*(20-i), 20*i);
sdl.sleep(800);
sdl.update();
cout << "WOW DID YOU SEE THAT!?\n";
sdl.sleep(800);
cout << "Mucking about with the gamma settings\n";
sdl.setGamma(2.0, 0.1, 0.8);
sdl.sleep(100);
sdl.setGamma(0.6, 2.1, 2.1);
sdl.sleep(100);
sdl.setGamma(1.6);
sdl.sleep(100);
cout << "Done.\n";
return 0;
}

@ -0,0 +1,31 @@
#include <iostream>
#include <fstream>
using namespace std;
#include "../servers/sdl_gl_driver.hpp"
using namespace Mangle::Rend2D;
int main()
{
SDLGLDriver sdl;
sdl.setVideoMode(640,480,0,false);
sdl.setWindowTitle("Testing 123");
Sprite *screen = sdl.getScreen();
const char* imgName = "tile1-blue.png";
Sprite *image = sdl.loadImage(imgName);
for(int frames=0; frames<170; frames++)
{
screen->fill(0);
for(int j=0; j<10; j++)
for(int i=0; i<25; i++)
screen->draw(image, 2*frames+30*j, 20*i);
sdl.update();
sdl.sleep(5);
}
return 0;
}

@ -0,0 +1,18 @@
#!/bin/bash
make || exit
mkdir -p output
PROGS=*_test
for a in $PROGS; do
if [ -f "output/$a.out" ]; then
echo "Running $a:"
./$a | diff output/$a.out -
else
echo "Creating $a.out"
./$a > "output/$a.out"
git add "output/$a.out"
fi
done

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 257 B

@ -0,0 +1,79 @@
#ifndef MANGLE_SOUND_OGRELISTENERMOVER_H
#define MANGLE_SOUND_OGRELISTENERMOVER_H
#include <OgreCamera.h>
#include <assert.h>
#include "../output.hpp"
namespace Mangle {
namespace Sound {
/** This class lets a sound listener (ie. the SoundFactory) track a
given camera in Ogre3D. The position and orientation of the
listener will be updated to match the camera whenever the camera
is moved.
*/
struct OgreListenerMover : Ogre::Camera::Listener
{
OgreListenerMover(Mangle::Sound::SoundFactoryPtr snd)
: soundFact(snd), camera(NULL)
{}
/// Follow a camera. WARNING: This will OVERRIDE any other
/// MovableObject::Listener you may have attached to the camera.
void followCamera(Ogre::Camera *cam)
{
camera = cam;
camera->addListener(this);
}
void unfollowCamera()
{
// If the camera is null, this object wasn't following a camera.
// It doesn't make sense to call unfollow
assert(camera != NULL);
camera->removeListener(this);
camera = NULL;
}
private:
Mangle::Sound::SoundFactoryPtr soundFact;
Ogre::Camera *camera;
Ogre::Vector3 pos, dir, up;
/// From Camera::Listener. This is called once per
/// frame. Unfortunately, Ogre doesn't allow us to be notified
/// only when the camera itself has moved, so we must poll every
/// frame.
void cameraPreRenderScene(Ogre::Camera *cam)
{
assert(cam == camera);
Ogre::Vector3 nPos, nDir, nUp;
nPos = camera->getPosition();
nDir = camera->getDirection();
nUp = camera->getUp();
// Don't bother the sound system needlessly
if(nDir != dir || nPos != pos || nUp != up)
{
pos = nPos;
dir = nDir;
up = nUp;
soundFact->setListenerPos(pos.x, pos.y, pos.z,
dir.x, dir.y, dir.z,
up.x, up.y, up.z);
}
}
void cameraDestroyed(Ogre::Camera *cam)
{
assert(cam == camera);
camera = NULL;
}
};
}}
#endif

@ -0,0 +1,31 @@
#ifndef MANGLE_SOUND_OGREUPDATER_H
#define MANGLE_SOUND_OGREUPDATER_H
/*
This Ogre FrameListener calls update on a SoundFactory
*/
#include <OgreFrameListener.h>
#include "../output.hpp"
#include <assert.h>
namespace Mangle {
namespace Sound {
struct OgreOutputUpdater : Ogre::FrameListener
{
Mangle::Sound::SoundFactoryPtr driver;
OgreOutputUpdater(Mangle::Sound::SoundFactoryPtr drv)
: driver(drv)
{ assert(drv->needsUpdate); }
bool frameStarted(const Ogre::FrameEvent &evt)
{
driver->update();
return true;
}
};
}}
#endif

@ -0,0 +1,68 @@
#ifndef MANGLE_INPUT_FILTER_H
#define MANGLE_INPUT_FILTER_H
#include "../output.hpp"
#include <assert.h>
namespace Mangle {
namespace Sound {
/**
@brief This filter class adds file loading capabilities to a
Sound::SoundFactory class, by associating a SampleSourceLoader with
it.
The class takes an existing SoundFactory able to load streams, and
associates a SampleSourceLoader with it. The combined class is able
to load files directly. */
class InputFilter : public SoundFactory
{
protected:
SoundFactoryPtr snd;
SampleSourceLoaderPtr inp;
public:
/// Empty constructor
InputFilter() {}
/// Assign an input manager and a sound manager to this object
InputFilter(SoundFactoryPtr _snd, SampleSourceLoaderPtr _inp)
{ set(_snd, _inp); }
/// Assign an input manager and a sound manager to this object
void set(SoundFactoryPtr _snd, SampleSourceLoaderPtr _inp)
{
inp = _inp;
snd = _snd;
// Set capabilities
needsUpdate = snd->needsUpdate;
has3D = snd->has3D;
canLoadStream = inp->canLoadStream;
// Both these should be true, or the use of this class is pretty
// pointless
canLoadSource = snd->canLoadSource;
canLoadFile = inp->canLoadFile;
assert(canLoadSource && canLoadFile);
}
virtual SoundPtr load(const std::string &file)
{ return loadRaw(inp->load(file)); }
virtual SoundPtr load(Stream::StreamPtr input)
{ return loadRaw(inp->load(input)); }
virtual SoundPtr loadRaw(SampleSourcePtr input)
{ return snd->loadRaw(input); }
virtual void update() { snd->update(); }
virtual void setListenerPos(float x, float y, float z,
float fx, float fy, float fz,
float ux, float uy, float uz)
{ snd->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); }
};
}}
#endif

@ -0,0 +1,24 @@
#ifndef MANGLE_AUDIERE_OPENAL_H
#define MANGLE_AUDIERE_OPENAL_H
#include "input_filter.hpp"
#include "../sources/audiere_source.hpp"
#include "../outputs/openal_out.hpp"
namespace Mangle {
namespace Sound {
/// A InputFilter that adds audiere decoding to OpenAL. Audiere has
/// it's own output, but OpenAL sports 3D and other advanced features.
class OpenAL_Audiere_Factory : public InputFilter
{
public:
OpenAL_Audiere_Factory()
{
set(SoundFactoryPtr(new OpenAL_Factory),
SampleSourceLoaderPtr(new AudiereLoader));
}
};
}}
#endif

@ -0,0 +1,23 @@
#ifndef MANGLE_FFMPEG_OPENAL_H
#define MANGLE_FFMPEG_OPENAL_H
#include "input_filter.hpp"
#include "../sources/ffmpeg_source.hpp"
#include "../outputs/openal_out.hpp"
namespace Mangle {
namespace Sound {
/// A InputFilter that adds ffmpeg decoding to OpenAL.
class OpenAL_FFMpeg_Factory : public InputFilter
{
public:
OpenAL_FFMpeg_Factory()
{
set(SoundFactoryPtr(new OpenAL_Factory),
SampleSourceLoaderPtr(new FFMpegLoader));
}
};
}}
#endif

@ -0,0 +1,24 @@
#ifndef MANGLE_MPG123_OPENAL_H
#define MANGLE_MPG123_OPENAL_H
#include "input_filter.hpp"
#include "../sources/mpg123_source.hpp"
#include "../outputs/openal_out.hpp"
namespace Mangle {
namespace Sound {
/// A InputFilter that adds mpg123 decoding to OpenAL. Only supports
/// MP3 files.
class OpenAL_Mpg123_Factory : public InputFilter
{
public:
OpenAL_Mpg123_Factory()
{
set(SoundFactoryPtr(new OpenAL_Factory),
SampleSourceLoaderPtr(new Mpg123Loader));
}
};
}}
#endif

@ -0,0 +1,24 @@
#ifndef MANGLE_SNDFILE_OPENAL_H
#define MANGLE_SNDFILE_OPENAL_H
#include "input_filter.hpp"
#include "../sources/libsndfile.hpp"
#include "../outputs/openal_out.hpp"
namespace Mangle {
namespace Sound {
/// A InputFilter that adds libsnd decoding to OpenAL. libsndfile
/// supports most formats except MP3.
class OpenAL_SndFile_Factory : public InputFilter
{
public:
OpenAL_SndFile_Factory()
{
set(SoundFactoryPtr(new OpenAL_Factory),
SampleSourceLoaderPtr(new SndFileLoader));
}
};
}}
#endif

@ -0,0 +1,33 @@
#ifndef MANGLE_SNDFILE_MPG123_OPENAL_H
#define MANGLE_SNDFILE_MPG123_OPENAL_H
#include "input_filter.hpp"
#include "source_splicer.hpp"
#include "../sources/mpg123_source.hpp"
#include "../sources/libsndfile.hpp"
#include "../outputs/openal_out.hpp"
namespace Mangle {
namespace Sound {
/// A InputFilter that uses OpenAL for output, and mpg123 (for MP3) +
/// libsndfile (for everything else) to decode files. Can only load
/// from the file system, and uses the file name to differentiate
/// between mp3 and non-mp3 types.
class OpenAL_SndFile_Mpg123_Factory : public InputFilter
{
public:
OpenAL_SndFile_Mpg123_Factory()
{
SourceSplicer *splice = new SourceSplicer;
splice->add("mp3", SampleSourceLoaderPtr(new Mpg123Loader));
splice->setDefault(SampleSourceLoaderPtr(new SndFileLoader));
set(SoundFactoryPtr(new OpenAL_Factory),
SampleSourceLoaderPtr(splice));
}
};
}}
#endif

@ -0,0 +1,39 @@
#ifndef MANGLE_VARIOUS_OPENAL_H
#define MANGLE_VARIOUS_OPENAL_H
#include "input_filter.hpp"
#include "source_splicer.hpp"
#include "../sources/mpg123_source.hpp"
#include "../sources/wav_source.hpp"
#include "../outputs/openal_out.hpp"
namespace Mangle {
namespace Sound {
/** A InputFilter that uses OpenAL for output, and load input from
various individual sources, depending on file extension. Currently
supports:
MP3: mpg123
WAV: custom wav loader (PCM only)
This could be an alternative to using eg. 3rd party decoder
libraries like libsndfile.
*/
class OpenAL_Various_Factory : public InputFilter
{
public:
OpenAL_Various_Factory()
{
SourceSplicer *splice = new SourceSplicer;
splice->add("mp3", SampleSourceLoaderPtr(new Mpg123Loader));
splice->add("wav", SampleSourceLoaderPtr(new WavLoader));
set(SoundFactoryPtr(new OpenAL_Factory),
SampleSourceLoaderPtr(splice));
}
};
}}
#endif

@ -0,0 +1,72 @@
#ifndef MANGLE_SOUND_OUTPUT_PUREFILTER_H
#define MANGLE_SOUND_OUTPUT_PUREFILTER_H
#include "../output.hpp"
namespace Mangle
{
namespace Sound
{
// For use in writing other filters
class SoundFilter : public Sound
{
protected:
SoundPtr client;
public:
SoundFilter(SoundPtr c) : client(c) {}
void play() { client->play(); }
void stop() { client->stop(); }
void pause() { client->pause(); }
bool isPlaying() const { return client->isPlaying(); }
void setVolume(float f) { client->setVolume(f); }
void setPan(float f) { client->setPan(f); }
void setPos(float x, float y, float z)
{ client->setPos(x,y,z); }
void setPitch(float p) { client->setPitch(p); }
void setRepeat(bool b) { client->setRepeat(b); }
void setRange(float a, float b=0, float c=0)
{ client->setRange(a,b,c); }
void setStreaming(bool b) { client->setStreaming(b); }
// The clone() function is not implemented here, as you will
// almost certainly want to override it yourself
};
class FactoryFilter : public SoundFactory
{
protected:
SoundFactoryPtr client;
public:
FactoryFilter(SoundFactoryPtr c) : client(c)
{
needsUpdate = client->needsUpdate;
has3D = client->has3D;
canLoadFile = client->canLoadFile;
canLoadStream = client->canLoadStream;
canLoadSource = client->canLoadSource;
}
SoundPtr loadRaw(SampleSourcePtr input)
{ return client->loadRaw(input); }
SoundPtr load(Stream::StreamPtr input)
{ return client->load(input); }
SoundPtr load(const std::string &file)
{ return client->load(file); }
void update()
{ client->update(); }
void setListenerPos(float x, float y, float z,
float fx, float fy, float fz,
float ux, float uy, float uz)
{
client->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz);
}
};
}
}
#endif

@ -0,0 +1,90 @@
#ifndef MANGLE_SOUND_SOURCE_SPLICE_H
#define MANGLE_SOUND_SOURCE_SPLICE_H
#include "../source.hpp"
#include <stdexcept>
#include <string>
#include <list>
#include <assert.h>
namespace Mangle
{
namespace Sound
{
class SourceSplicer : public SampleSourceLoader
{
struct SourceType
{
std::string type;
SampleSourceLoaderPtr loader;
};
typedef std::list<SourceType> TypeList;
TypeList list;
SampleSourceLoaderPtr catchAll;
static bool isMatch(char a, char b)
{
if(a >= 'A' && a <= 'Z')
a += 'a' - 'A';
if(b >= 'A' && b <= 'Z')
b += 'a' - 'A';
return a == b;
}
public:
SourceSplicer()
{
canLoadStream = false;
canLoadFile = true;
}
void add(const std::string &type, SampleSourceLoaderPtr fact)
{
SourceType tp;
tp.type = type;
tp.loader = fact;
list.push_back(tp);
}
void setDefault(SampleSourceLoaderPtr def)
{
catchAll = def;
}
SampleSourcePtr load(const std::string &file)
{
// Search the list for this file type.
for(TypeList::iterator it = list.begin();
it != list.end(); it++)
{
const std::string &t = it->type;
int diff = file.size() - t.size();
if(diff < 0) continue;
bool match = true;
for(unsigned i=0; i<t.size(); i++)
if(!isMatch(t[i], file[i+diff]))
{
match = false;
break;
}
// Got something! We're done.
if(match)
return it->loader->load(file);
}
// If not found, use the catch-all
if(catchAll)
return catchAll->load(file);
throw std::runtime_error("No handler for sound file " + file);
}
SampleSourcePtr load(Stream::StreamPtr input) { assert(0); }
};
}
}
#endif

@ -0,0 +1,180 @@
#ifndef MANGLE_SOUND_OUTPUT_H
#define MANGLE_SOUND_OUTPUT_H
#include <string>
#include <assert.h>
#include "source.hpp"
#include "../stream/stream.hpp"
namespace Mangle {
namespace Sound {
/// Abstract interface for a single playable sound
/** This class represents one sound outlet, which may be played,
stopped, paused and so on.
Sound instances are created from the SoundFactory class. Sounds
may be connected to a SampleSource or read directly from a file,
and they may support 3d sounds, looping and other features
depending on the capabilities of the backend system.
To create multiple instances of one sound, it is recommended to
'clone' an existing instance instead of reloading it from
file. Cloned sounds will often (depending on the back-end) use
less memory due to shared buffers.
*/
class Sound;
typedef boost::shared_ptr<Sound> SoundPtr;
typedef boost::weak_ptr <Sound> WSoundPtr;
class Sound
{
public:
/// Play or resume the sound
virtual void play() = 0;
/// Stop the sound
virtual void stop() = 0;
/// Pause the sound, may be resumed later
virtual void pause() = 0;
/// Check if the sound is still playing
virtual bool isPlaying() const = 0;
/// Set the volume. The parameter must be between 0.0 and 1.0.
virtual void setVolume(float) = 0;
/// Set left/right pan. -1.0 is left, 0.0 is center and 1.0 is right.
virtual void setPan(float) = 0;
/// Set pitch (1.0 is normal speed)
virtual void setPitch(float) = 0;
/// Set range factors for 3D sounds. The meaning of the fields
/// depend on implementation.
virtual void setRange(float a, float b=0.0, float c=0.0) = 0;
/// Set the position. May not work with all backends.
virtual void setPos(float x, float y, float z) = 0;
/// Set loop mode
virtual void setRepeat(bool) = 0;
/// Set streaming mode.
/** This may be used by implementations to optimize for very large
files. If streaming mode is off (default), most implementations
will load the entire file into memory before starting playback.
*/
virtual void setStreaming(bool) = 0;
/// Create a new instance of this sound.
/** Playback status is not cloned, only the sound data
itself. Back-ends can use this as a means of sharing data and
saving memory. */
virtual SoundPtr clone() = 0;
/// Virtual destructor
virtual ~Sound() {}
};
/// Factory interface for creating Sound objects
/** The SoundFactory is the main entry point to a given sound output
system. It is used to create Sound objects, which may be connected
to a sound file or stream, and which may be individually played,
paused, and so on.
The class also contains a set of public bools which describe the
capabilities the particular system. These should be set by
implementations (base classes) in their respective constructors.
*/
class SoundFactory
{
public:
/// Virtual destructor
virtual ~SoundFactory() {}
/** @brief If set to true, you should call update() regularly (every frame
or so) on this sound manager. If false, update() should not be
called.
*/
bool needsUpdate;
/** @brief true if 3D functions are available. If false, all use of
3D sounds and calls to setPos / setListenerPos will result in
undefined behavior.
*/
bool has3D;
/// true if we can load sounds directly from file (containing encoded data)
bool canLoadFile;
/// If true, we can lound sound files from a Stream (containing encoded data)
bool canLoadStream;
/// true if we can load sounds from a SampleSource (containing raw data)
bool canLoadSource;
/**
@brief Load a sound from a sample source. Only valid if
canLoadSource is true.
This function loads a sound from a given stream as defined by
SampleSource.
@param input the input source
@param stream true if the file should be streamed.
Implementations may use this for optimizing playback of
large files, but they are not required to.
@return a new Sound object
*/
virtual SoundPtr loadRaw(SampleSourcePtr input) = 0;
/**
@brief Load a sound file from stream. Only valid if canLoadStream
is true.
@param input audio file stream
@param stream true if the file should be streamed
@see load(InputSource*,bool)
*/
virtual SoundPtr load(Stream::StreamPtr input) = 0;
/**
@brief Load a sound directly from file. Only valid if canLoadFile
is true.
@param file filename
@param stream true if the file should be streamed
@see load(InputSource*,bool)
*/
virtual SoundPtr load(const std::string &file) = 0;
/// Call this every frame if needsUpdate is true
/**
This should be called regularly (about every frame in a normal
game setting.) Implementions may use this for filling streaming
buffers and similar tasks. Implementations that do not need this
should set needsUpdate to false.
*/
virtual void update() { assert(0); }
/// Set listener position (coordinates, front and up vectors)
/**
Only valid if has3D is true.
@param x,y,z listener position
@param fx,fy,fz listener's looking direction
@param ux,uy,uz listener's up direction
*/
virtual void setListenerPos(float x, float y, float z,
float fx, float fy, float fz,
float ux, float uy, float uz) = 0;
};
typedef boost::shared_ptr<SoundFactory> SoundFactoryPtr;
}} // Namespaces
#endif

@ -0,0 +1,492 @@
#include "openal_out.hpp"
#include <assert.h>
#include <stdexcept>
#include "../../stream/filters/buffer_stream.hpp"
#ifdef _WIN32
#include <al.h>
#include <alc.h>
#elif defined(__APPLE__)
#include <OpenAL/alc.h>
#include <OpenAL/al.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
using namespace Mangle::Sound;
// ---- Helper functions and classes ----
// Static buffer used to shuffle sound data from the input into
// OpenAL. The data is only stored temporarily and then immediately
// shuffled off to the library. This is not thread safe, but it works
// fine with multiple sounds in one thread. It could be made thread
// safe simply by using thread local storage.
const size_t BSIZE = 32*1024;
static char tmp_buffer[BSIZE];
// Number of buffers used (per sound) for streaming sounds. Each
// buffer is of size BSIZE. Increasing this will make streaming sounds
// more fault tolerant against temporary lapses in call to update(),
// but will also increase memory usage.
// This was changed from 4 to 150 for an estimated 30 seconds tolerance.
// At some point we should replace it with a more multithreading-ish
// solution.
const int STREAM_BUF_NUM = 150;
static void fail(const std::string &msg)
{ throw std::runtime_error("OpenAL exception: " + msg); }
/*
Check for AL error. Since we're always calling this with string
literals, and it only makes sense to optimize for the non-error
case, the parameter is const char* rather than std::string.
This way we don't force the compiler to create a string object each
time we're called (since the string is never used unless there's an
error), although a good compiler might have optimized that away in
any case.
*/
static void checkALError(const char *where)
{
ALenum err = alGetError();
if(err != AL_NO_ERROR)
{
std::string msg = where;
const ALchar* errmsg = alGetString(err);
if(errmsg)
fail("\"" + std::string(alGetString(err)) + "\" while " + msg);
else
fail("non-specified error while " + msg + " (did you forget to initialize OpenAL?)");
}
}
static void getALFormat(SampleSourcePtr inp, int &fmt, int &rate)
{
boost::int32_t rate_, ch, bits;
inp->getInfo(&rate_, &ch, &bits);
rate = rate_;
fmt = 0;
if(bits == 8)
{
if(ch == 1) fmt = AL_FORMAT_MONO8;
if(ch == 2) fmt = AL_FORMAT_STEREO8;
if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD8");
if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN8");
}
}
if(bits == 16)
{
if(ch == 1) fmt = AL_FORMAT_MONO16;
if(ch == 2) fmt = AL_FORMAT_STEREO16;
if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16");
if(alIsExtensionPresent("AL_EXT_MCFORMATS"))
{
if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16");
if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN16");
}
}
if(fmt == 0)
fail("Unsupported input format");
}
/// OpenAL sound output
class Mangle::Sound::OpenAL_Sound : public Sound
{
ALuint inst;
// Buffers. Only the first is used for non-streaming sounds.
ALuint bufferID[STREAM_BUF_NUM];
// Number of buffers used
int bufNum;
// Parameters used for filling buffers
int fmt, rate;
// Poor mans reference counting. Might improve this later. When
// NULL, the buffer has not been set up yet.
int *refCnt;
bool streaming;
// Input stream
SampleSourcePtr input;
OpenAL_Factory *owner;
bool ownerAlive;
// Used for streamed sound list
OpenAL_Sound *next, *prev;
void setupBuffer();
// Fill data into the given buffer and queue it, if there is any
// data left to queue. Assumes the buffer is already unqueued, if
// necessary.
void queueBuffer(ALuint buf)
{
// If there is no more data, do nothing
if(!input) return;
if(input->eof())
{
input.reset();
return;
}
// Get some new data
size_t bytes = input->read(tmp_buffer, BSIZE);
if(bytes == 0)
{
input.reset();
return;
}
// Move data into the OpenAL buffer
alBufferData(buf, fmt, tmp_buffer, bytes, rate);
// Queue it
alSourceQueueBuffers(inst, 1, &buf);
checkALError("Queueing buffer data");
}
public:
/// Read samples from the given input buffer
OpenAL_Sound(SampleSourcePtr input, OpenAL_Factory *fact);
/// Play an existing buffer, with a given ref counter. Used
/// internally for cloning.
OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact);
~OpenAL_Sound();
// Must be called regularly on streamed sounds
void update()
{
if(!streaming) return;
if(!input) return;
// Get the number of processed buffers
ALint count;
alGetSourcei(inst, AL_BUFFERS_PROCESSED, &count);
checkALError("getting number of unprocessed buffers");
for(int i=0; i<count; i++)
{
ALuint buf;
// Unqueue one of the processed buffer
alSourceUnqueueBuffers(inst, 1, &buf);
// Then reload it with data (if any) and queue it up
queueBuffer(buf);
}
}
void play();
void stop();
void pause();
bool isPlaying() const;
void setVolume(float);
void setPos(float x, float y, float z);
void setPitch(float);
void setRepeat(bool);
void notifyOwnerDeath()
{ ownerAlive = false; }
// We can enable streaming, but never disable it.
void setStreaming(bool s)
{ if(s) streaming = true; }
SoundPtr clone();
// a = AL_REFERENCE_DISTANCE
// b = AL_MAX_DISTANCE
// c = ignored
void setRange(float a, float b=0.0, float c=0.0);
/// Not implemented
void setPan(float) {}
};
// ---- OpenAL_Factory ----
SoundPtr OpenAL_Factory::loadRaw(SampleSourcePtr input)
{
return SoundPtr(new OpenAL_Sound(input, this));
}
void OpenAL_Factory::setListenerPos(float x, float y, float z,
float fx, float fy, float fz,
float ux, float uy, float uz)
{
ALfloat orient[6];
orient[0] = fx;
orient[1] = fy;
orient[2] = fz;
orient[3] = ux;
orient[4] = uy;
orient[5] = uz;
alListener3f(AL_POSITION, x, y, z);
alListenerfv(AL_ORIENTATION, orient);
}
OpenAL_Factory::OpenAL_Factory(bool doSetup)
: device(NULL), context(NULL), didSetup(doSetup)
{
needsUpdate = true;
has3D = true;
canLoadFile = false;
canLoadStream = false;
canLoadSource = true;
ALCdevice *Device;
ALCcontext *Context;
if(doSetup)
{
// Set up sound system
Device = alcOpenDevice(NULL);
Context = alcCreateContext(Device, NULL);
if(!Device || !Context)
fail("Failed to initialize context or device");
alcMakeContextCurrent(Context);
device = Device;
context = Context;
}
}
void OpenAL_Factory::update()
{
// Loop through all streaming sounds and update them
StreamList::iterator it = streaming.begin();
for(;it != streaming.end(); it++)
(*it)->update();
}
void OpenAL_Factory::notifyStreaming(OpenAL_Sound *snd)
{
// Add the sound to the streaming list
streaming.push_back(snd);
}
void OpenAL_Factory::notifyDelete(OpenAL_Sound *snd)
{
// Remove the sound from the stream list
streaming.remove(snd);
}
OpenAL_Factory::~OpenAL_Factory()
{
// Notify remaining streamed sounds that we're dying
StreamList::iterator it = streaming.begin();
for(;it != streaming.end(); it++)
(*it)->notifyOwnerDeath();
// Deinitialize sound system
if(didSetup)
{
alcMakeContextCurrent(NULL);
if(context) alcDestroyContext((ALCcontext*)context);
if(device) alcCloseDevice((ALCdevice*)device);
}
}
// ---- OpenAL_Sound ----
void OpenAL_Sound::play()
{
setupBuffer();
alSourcePlay(inst);
checkALError("starting playback");
}
void OpenAL_Sound::stop()
{
alSourceStop(inst);
checkALError("stopping");
}
void OpenAL_Sound::pause()
{
alSourcePause(inst);
checkALError("pausing");
}
bool OpenAL_Sound::isPlaying() const
{
ALint state;
alGetSourcei(inst, AL_SOURCE_STATE, &state);
return state == AL_PLAYING;
}
void OpenAL_Sound::setVolume(float volume)
{
if(volume > 1.0) volume = 1.0;
if(volume < 0.0) volume = 0.0;
alSourcef(inst, AL_GAIN, volume);
checkALError("setting volume");
}
void OpenAL_Sound::setRange(float a, float b, float)
{
alSourcef(inst, AL_REFERENCE_DISTANCE, a);
alSourcef(inst, AL_MAX_DISTANCE, b);
checkALError("setting sound ranges");
}
void OpenAL_Sound::setPos(float x, float y, float z)
{
alSource3f(inst, AL_POSITION, x, y, z);
checkALError("setting position");
}
void OpenAL_Sound::setPitch(float pitch)
{
alSourcef(inst, AL_PITCH, pitch);
checkALError("setting pitch");
}
void OpenAL_Sound::setRepeat(bool rep)
{
alSourcei(inst, AL_LOOPING, rep?AL_TRUE:AL_FALSE);
}
SoundPtr OpenAL_Sound::clone()
{
setupBuffer();
assert(!streaming && "cloning streamed sounds not supported");
return SoundPtr(new OpenAL_Sound(bufferID[0], refCnt, owner));
}
// Constructor used for cloned sounds
OpenAL_Sound::OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact)
: refCnt(ref), streaming(false), owner(fact), ownerAlive(false)
{
// Increase the reference count
assert(ref != NULL);
(*refCnt)++;
// Set up buffer
bufferID[0] = buf;
bufNum = 1;
// Create a source
alGenSources(1, &inst);
checkALError("creating instance (clone)");
alSourcei(inst, AL_BUFFER, bufferID[0]);
checkALError("assigning buffer (clone)");
}
// Constructor used for original (non-cloned) sounds
OpenAL_Sound::OpenAL_Sound(SampleSourcePtr _input, OpenAL_Factory *fact)
: refCnt(NULL), streaming(false), input(_input), owner(fact), ownerAlive(false)
{
// Create a source
alGenSources(1, &inst);
checkALError("creating source");
// By default, the sound starts out in a buffer-less mode. We don't
// create a buffer until the sound is played. This gives the user
// the chance to call setStreaming(true) first.
}
void OpenAL_Sound::setupBuffer()
{
if(refCnt != NULL) return;
assert(input);
// Get the format
getALFormat(input, fmt, rate);
// Create a cheap reference counter for the buffer
refCnt = new int;
*refCnt = 1;
if(streaming) bufNum = STREAM_BUF_NUM;
else bufNum = 1;
// Set up the OpenAL buffer(s)
alGenBuffers(bufNum, bufferID);
checkALError("generating buffer(s)");
assert(bufferID[0] != 0);
// STREAMING.
if(streaming)
{
// Just queue all the buffers with data and exit. queueBuffer()
// will work correctly also in the case where there is not
// enough data to fill all the buffers.
for(int i=0; i<bufNum; i++)
queueBuffer(bufferID[i]);
// Notify the manager what we're doing
owner->notifyStreaming(this);
ownerAlive = true;
return;
}
// NON-STREAMING. We have to load all the data and shove it into the
// buffer.
// Does the stream support pointer operations?
if(input->hasPtr)
{
// If so, we can read the data directly from the stream
alBufferData(bufferID[0], fmt, input->getPtr(), input->size(), rate);
}
else
{
// Read the entire stream into a temporary buffer first
Mangle::Stream::BufferStream buf(input, 128*1024);
// Then copy that into OpenAL
alBufferData(bufferID[0], fmt, buf.getPtr(), buf.size(), rate);
}
checkALError("loading sound data");
// We're done with the input stream, release the pointer
input.reset();
alSourcei(inst, AL_BUFFER, bufferID[0]);
checkALError("assigning buffer");
}
OpenAL_Sound::~OpenAL_Sound()
{
// Stop
alSourceStop(inst);
// Return sound
alDeleteSources(1, &inst);
// Notify the factory that we quit. You will hear from our union
// rep. The bool check is to handle cases where the manager goes out
// of scope before the sounds do. In that case, don't try to contact
// the factory.
if(ownerAlive)
owner->notifyDelete(this);
// Decrease the reference counter
if((-- (*refCnt)) == 0)
{
// We're the last owner. Delete the buffer(s) and the counter
// itself.
alDeleteBuffers(bufNum, bufferID);
checkALError("deleting buffer");
delete refCnt;
}
}

@ -0,0 +1,44 @@
#ifndef MANGLE_SOUND_OPENAL_OUT_H
#define MANGLE_SOUND_OPENAL_OUT_H
#include "../output.hpp"
#include <list>
namespace Mangle {
namespace Sound {
class OpenAL_Sound;
class OpenAL_Factory : public SoundFactory
{
void *device;
void *context;
bool didSetup;
// List of streaming sounds that need to be updated every frame.
typedef std::list<OpenAL_Sound*> StreamList;
StreamList streaming;
friend class OpenAL_Sound;
void notifyStreaming(OpenAL_Sound*);
void notifyDelete(OpenAL_Sound*);
public:
/// Initialize object. Pass true (default) if you want the
/// constructor to set up the current ALCdevice and ALCcontext for
/// you.
OpenAL_Factory(bool doSetup = true);
~OpenAL_Factory();
SoundPtr load(const std::string &file) { assert(0); return SoundPtr(); }
SoundPtr load(Stream::StreamPtr input) { assert(0); return SoundPtr(); }
SoundPtr loadRaw(SampleSourcePtr input);
void update();
void setListenerPos(float x, float y, float z,
float fx, float fy, float fz,
float ux, float uy, float uz);
};
}} // namespaces
#endif

@ -0,0 +1,62 @@
#ifndef MANGLE_SOUND_SOURCE_H
#define MANGLE_SOUND_SOURCE_H
#include <string>
#include <boost/cstdint.hpp>
#include <assert.h>
#include "../stream/stream.hpp"
namespace Mangle {
namespace Sound {
typedef boost::int32_t int32_t;
/// A stream containing raw sound data and information about the format
class SampleSource : public Stream::Stream
{
protected:
bool isEof;
public:
SampleSource() : isEof(false) {}
/// Get the sample rate, number of channels, and bits per
/// sample. NULL parameters are ignored.
virtual void getInfo(int32_t *rate, int32_t *channels, int32_t *bits) = 0;
bool eof() const { return isEof; }
// Disabled functions by default. You can still override them in
// subclasses.
void seek(size_t pos) { assert(0); }
size_t tell() const { assert(0); return 0; }
size_t size() const { assert(0); return 0; }
};
typedef boost::shared_ptr<SampleSource> SampleSourcePtr;
/// A factory interface for loading SampleSources from file or stream
class SampleSourceLoader
{
public:
/// If true, the stream version of load() works
bool canLoadStream;
/// If true, the file version of load() works
bool canLoadFile;
/// Load a sound input source from file (if canLoadFile is true)
virtual SampleSourcePtr load(const std::string &file) = 0;
/// Load a sound input source from stream (if canLoadStream is true)
virtual SampleSourcePtr load(Stream::StreamPtr input) = 0;
/// Virtual destructor
virtual ~SampleSourceLoader() {}
};
typedef boost::shared_ptr<SampleSourceLoader> SampleSourceLoaderPtr;
}} // namespaces
#endif

@ -0,0 +1,77 @@
#include "audiere_source.hpp"
#include "../../stream/clients/audiere_file.hpp"
#include <stdexcept>
using namespace Mangle::Stream;
static void fail(const std::string &msg)
{ throw std::runtime_error("Audiere exception: " + msg); }
using namespace audiere;
using namespace Mangle::Sound;
// --- SampleSource ---
void AudiereSource::getInfo(Mangle::Sound::int32_t *rate,
Mangle::Sound::int32_t *channels, Mangle::Sound::int32_t *bits)
{
SampleFormat fmt;
int channels_, rate_;
sample->getFormat(channels_, rate_, fmt);
*channels = channels_;
*rate = rate_;
if(bits)
{
if(fmt == SF_U8)
*bits = 8;
else if(fmt == SF_S16)
*bits = 16;
else assert(0);
}
}
// --- Constructors ---
AudiereSource::AudiereSource(const std::string &file)
{
sample = OpenSampleSource(file.c_str());
if(!sample)
fail("Couldn't load file " + file);
doSetup();
}
AudiereSource::AudiereSource(StreamPtr input)
{
// Use our Stream::AudiereFile implementation to convert a Mangle
// 'Stream' to an Audiere 'File'
sample = OpenSampleSource(new AudiereFile(input));
if(!sample)
fail("Couldn't load stream");
doSetup();
}
AudiereSource::AudiereSource(audiere::SampleSourcePtr src)
: sample(src)
{ assert(sample); doSetup(); }
// Common function called from all constructors
void AudiereSource::doSetup()
{
assert(sample);
SampleFormat fmt;
int channels, rate;
sample->getFormat(channels, rate, fmt);
// Calculate the size of one frame, and pass it to SampleReader.
setup(GetSampleSize(fmt) * channels);
isSeekable = sample->isSeekable();
hasPosition = true;
hasSize = true;
}

@ -0,0 +1,48 @@
#ifndef MANGLE_SOUND_AUDIERE_SOURCE_H
#define MANGLE_SOUND_AUDIERE_SOURCE_H
#include "sample_reader.hpp"
// audiere.h from 1.9.4 (latest) release uses
// cstring routines like strchr() and strlen() without
// including cstring itself.
#include <cstring>
#include <audiere.h>
namespace Mangle {
namespace Sound {
/// A sample source that decodes files using Audiere
class AudiereSource : public SampleReader
{
audiere::SampleSourcePtr sample;
size_t readSamples(void *data, size_t length)
{ return sample->read(length, data); }
void doSetup();
public:
/// Decode the given sound file
AudiereSource(const std::string &file);
/// Decode the given sound stream
AudiereSource(Mangle::Stream::StreamPtr src);
/// Read directly from an existing audiere::SampleSource
AudiereSource(audiere::SampleSourcePtr src);
void getInfo(int32_t *rate, int32_t *channels, int32_t *bits);
void seek(size_t pos) { sample->setPosition(pos/frameSize); }
size_t tell() const { return sample->getPosition()*frameSize; }
size_t size() const { return sample->getLength()*frameSize; }
};
#include "loadertemplate.hpp"
/// A factory that loads AudiereSources from file and stream
typedef SSL_Template<AudiereSource,true,true> AudiereLoader;
}} // Namespace
#endif

@ -0,0 +1,189 @@
#include "ffmpeg_source.hpp"
#include <stdexcept>
using namespace Mangle::Sound;
// Static output buffer. Not thread safe, but supports multiple
// streams operated from the same thread.
static uint8_t outBuf[AVCODEC_MAX_AUDIO_FRAME_SIZE];
static void fail(const std::string &msg)
{ throw std::runtime_error("FFMpeg exception: " + msg); }
// --- Loader ---
static bool init = false;
FFMpegLoader::FFMpegLoader(bool setup)
{
if(setup && !init)
{
av_register_all();
av_log_set_level(AV_LOG_ERROR);
init = true;
}
}
// --- Source ---
FFMpegSource::FFMpegSource(const std::string &file)
{
std::string msg;
AVCodec *codec;
if(av_open_input_file(&FmtCtx, file.c_str(), NULL, 0, NULL) != 0)
fail("Error loading audio file " + file);
if(av_find_stream_info(FmtCtx) < 0)
{
msg = "Error in file stream " + file;
goto err;
}
// Pick the first audio stream, if any
for(StreamNum = 0; StreamNum < FmtCtx->nb_streams; StreamNum++)
{
// Pick the first audio stream
if(FmtCtx->streams[StreamNum]->codec->codec_type == CODEC_TYPE_AUDIO)
break;
}
if(StreamNum == FmtCtx->nb_streams)
fail("File '" + file + "' didn't contain any audio streams");
// Open the decoder
CodecCtx = FmtCtx->streams[StreamNum]->codec;
codec = avcodec_find_decoder(CodecCtx->codec_id);
if(!codec || avcodec_open(CodecCtx, codec) < 0)
{
msg = "Error loading '" + file + "': ";
if(codec)
msg += "coded error";
else
msg += "no codec found";
goto err;
}
// No errors, we're done
return;
// Handle errors
err:
av_close_input_file(FmtCtx);
fail(msg);
}
FFMpegSource::~FFMpegSource()
{
avcodec_close(CodecCtx);
av_close_input_file(FmtCtx);
}
void FFMpegSource::getInfo(int32_t *rate, int32_t *channels, int32_t *bits)
{
if(rate) *rate = CodecCtx->sample_rate;
if(channels) *channels = CodecCtx->channels;
if(bits) *bits = 16;
}
size_t FFMpegSource::read(void *data, size_t length)
{
if(isEof) return 0;
size_t left = length;
uint8_t *outPtr = (uint8_t*)data;
// First, copy over any stored data we might be sitting on
{
size_t s = storage.size();
size_t copy = s;
if(s)
{
// Make sure there's room
if(copy > left)
copy = left;
// Copy
memcpy(outPtr, &storage[0], copy);
outPtr += copy;
left -= copy;
// Is there anything left in the storage?
assert(s>= copy);
s -= copy;
if(s)
{
assert(left == 0);
// Move it to the start and resize
memmove(&storage[0], &storage[copy], s);
storage.resize(s);
}
}
}
// Next, get more input data from stream, and decode it
while(left)
{
AVPacket packet;
// Get the next packet, if any
if(av_read_frame(FmtCtx, &packet) < 0)
break;
// We only allow one stream per file at the moment
assert((int)StreamNum == packet.stream_index);
// Decode the packet
int len = AVCODEC_MAX_AUDIO_FRAME_SIZE;
int tmp = avcodec_decode_audio2(CodecCtx, (int16_t*)outBuf,
&len, packet.data, packet.size);
assert(tmp < 0 || tmp == packet.size);
// We don't need the input packet any longer
av_free_packet(&packet);
if(tmp < 0)
fail("Error decoding audio stream");
// Copy whatever data we got, and advance the pointer
if(len > 0)
{
// copy = how many bytes do we copy now
size_t copy = len;
if(copy > left)
copy = left;
// len = how many bytes are left uncopied
len -= copy;
// copy data
memcpy(outPtr, outBuf, copy);
// left = how much space is left in the caller output
// buffer. This loop repeats as long left is > 0
left -= copy;
outPtr += copy;
assert(left >= 0);
if(len > 0)
{
// There were uncopied bytes. Store them for later.
assert(left == 0);
storage.resize(len);
memcpy(&storage[0], outBuf, len);
}
}
}
// End of loop. Return the number of bytes copied.
assert(left <= length);
// If we're returning less than asked for, then we're done
if(left > 0)
isEof = true;
return length - left;
}

@ -0,0 +1,52 @@
#ifndef MANGLE_SOUND_FFMPEG_H
#define MANGLE_SOUND_FFMPEG_H
#include "../source.hpp"
#include <vector>
#include <assert.h>
extern "C"
{
#include <avcodec.h>
#include <avformat.h>
}
namespace Mangle {
namespace Sound {
class FFMpegSource : public SampleSource
{
AVFormatContext *FmtCtx;
AVCodecContext *CodecCtx;
unsigned int StreamNum;
std::vector<uint8_t> storage;
public:
/// Decode the given sound file
FFMpegSource(const std::string &file);
/// Decode the given sound stream (not supported by FFmpeg)
FFMpegSource(Mangle::Stream::StreamPtr src) { assert(0); }
~FFMpegSource();
// Overrides
void getInfo(int32_t *rate, int32_t *channels, int32_t *bits);
size_t read(void *data, size_t length);
};
#include "loadertemplate.hpp"
/// A factory that loads FFMpegSources from file
class FFMpegLoader : public SSL_Template<FFMpegSource,false,true>
{
public:
/// Sets up the libavcodec library. If you want to do your own
/// setup, send a setup=false parameter.
FFMpegLoader(bool setup=true);
};
}} // namespaces
#endif

@ -0,0 +1,48 @@
#include "libsndfile.hpp"
#include <stdexcept>
#include <sndfile.h>
using namespace Mangle::Stream;
static void fail(const std::string &msg)
{ throw std::runtime_error("Mangle::libsndfile: " + msg); }
using namespace Mangle::Sound;
void SndFileSource::getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits)
{
*_rate = rate;
*_channels = channels;
*_bits = bits;
}
size_t SndFileSource::readSamples(void *data, size_t length)
{
// readf_* reads entire frames, including channels
return sf_readf_short((SNDFILE*)handle, (short*)data, length);
}
SndFileSource::SndFileSource(const std::string &file)
{
SF_INFO info;
info.format = 0;
handle = sf_open(file.c_str(), SFM_READ, &info);
if(handle == NULL)
fail("Failed to open " + file);
// I THINK that using sf_read_short forces the library to convert to
// 16 bits no matter what, but the libsndfile docs aren't exactly
// very clear on this point.
channels = info.channels;
rate = info.samplerate;
bits = 16;
// 16 bits per sample times number of channels
setup(2*channels);
}
SndFileSource::~SndFileSource()
{
sf_close((SNDFILE*)handle);
}

@ -0,0 +1,36 @@
#ifndef MANGLE_SOUND_SNDFILE_SOURCE_H
#define MANGLE_SOUND_SNDFILE_SOURCE_H
#include "sample_reader.hpp"
namespace Mangle {
namespace Sound {
/// A sample source that decodes files using libsndfile. Supports most
/// formats except mp3.
class SndFileSource : public SampleReader
{
void *handle;
int channels, rate, bits;
size_t readSamples(void *data, size_t length);
public:
/// Decode the given sound file
SndFileSource(const std::string &file);
/// Decode the given sound stream (not supported)
SndFileSource(Mangle::Stream::StreamPtr src) { assert(0); }
~SndFileSource();
void getInfo(int32_t *rate, int32_t *channels, int32_t *bits);
};
#include "loadertemplate.hpp"
/// A factory that loads SndFileSources from file and stream
typedef SSL_Template<SndFileSource,false,true> SndFileLoader;
}} // Namespace
#endif

@ -0,0 +1,28 @@
#ifndef SSL_TEMPL_H
#define SSL_TEMPL_H
template <class SourceT, bool stream, bool file>
class SSL_Template : public SampleSourceLoader
{
public:
SSL_Template()
{
canLoadStream = stream;
canLoadFile = file;
}
SampleSourcePtr load(const std::string &filename)
{
assert(canLoadFile);
return SampleSourcePtr(new SourceT(filename));
}
SampleSourcePtr load(Stream::StreamPtr input)
{
assert(canLoadStream);
return SampleSourcePtr(new SourceT(input));
}
};
#endif

@ -0,0 +1,115 @@
#include "mpg123_source.hpp"
#include <stdexcept>
#include <mpg123.h>
using namespace Mangle::Stream;
/*
TODOs:
- mpg123 impressively enough supports custom stream reading. Which
means we could (and SHOULD!) support reading from Mangle::Streams
as well. But I'll save it til I need it.
An alternative way to do this is through feeding (which they also
support), but that's more messy.
- the library also supports output, via various other sources,
including ALSA, OSS, PortAudio, PulseAudio and SDL. Using this
library as a pure output library (if that is possible) would be a
nice shortcut over using those libraries - OTOH it's another
dependency.
- we could implement seek(), tell() and size(), but they aren't
really necessary. Furthermore, since the returned size is only a
guess, it is not safe to rely on it.
*/
static void fail(const std::string &msg)
{ throw std::runtime_error("Mangle::Mpg123 exception: " + msg); }
static void checkError(int err, void *mh = NULL)
{
if(err != MPG123_OK)
{
std::string msg;
if(mh) msg = mpg123_strerror((mpg123_handle*)mh);
else msg = mpg123_plain_strerror(err);
fail(msg);
}
}
using namespace Mangle::Sound;
void Mpg123Source::getInfo(int32_t *pRate, int32_t *pChannels, int32_t *pBits)
{
// Use the values we found in the constructor
*pRate = rate;
*pChannels = channels;
*pBits = bits;
}
size_t Mpg123Source::read(void *data, size_t length)
{
size_t done;
// This is extraordinarily nice. I like this library.
int err = mpg123_read((mpg123_handle*)mh, (unsigned char*)data, length, &done);
assert(done <= length);
if(err == MPG123_DONE)
isEof = true;
else
checkError(err, mh);
return done;
}
Mpg123Loader::Mpg123Loader(bool setup)
{
// Do as we're told
if(setup)
{
int err = mpg123_init();
checkError(err);
}
didSetup = setup;
}
Mpg123Loader::~Mpg123Loader()
{
// Deinitialize the library on exit
if(didSetup)
mpg123_exit();
}
Mpg123Source::Mpg123Source(const std::string &file)
{
int err;
// Create a new handle
mh = mpg123_new(NULL, &err);
if(mh == NULL)
checkError(err, mh);
mpg123_handle *mhh = (mpg123_handle*)mh;
// Open the file (hack around constness)
err = mpg123_open(mhh, (char*)file.c_str());
checkError(err, mh);
// Get the format
int encoding;
err = mpg123_getformat(mhh, &rate, &channels, &encoding);
checkError(err, mh);
if(encoding != MPG123_ENC_SIGNED_16)
fail("Unsupported encoding in " + file);
// This is the only bit size we support.
bits = 16;
}
Mpg123Source::~Mpg123Source()
{
mpg123_close((mpg123_handle*)mh);
mpg123_delete((mpg123_handle*)mh);
}

@ -0,0 +1,47 @@
#ifndef MANGLE_SOUND_MPG123_SOURCE_H
#define MANGLE_SOUND_MPG123_SOURCE_H
#include "../source.hpp"
#include <assert.h>
namespace Mangle {
namespace Sound {
/// A sample source that decodes files using libmpg123. Only supports
/// MP3 files.
class Mpg123Source : public SampleSource
{
void *mh;
long int rate;
int channels, bits;
public:
/// Decode the given sound file
Mpg123Source(const std::string &file);
/// Needed by SSL_Template but not yet supported
Mpg123Source(Mangle::Stream::StreamPtr data)
{ assert(0); }
~Mpg123Source();
void getInfo(int32_t *rate, int32_t *channels, int32_t *bits);
size_t read(void *data, size_t length);
};
#include "loadertemplate.hpp"
/// A factory that loads Mpg123Sources from file and stream
struct Mpg123Loader : SSL_Template<Mpg123Source,false,true>
{
/** Sets up libmpg123 for you, and closes it on destruction. If you
want to do this yourself, send setup=false.
*/
Mpg123Loader(bool setup=true);
~Mpg123Loader();
private:
bool didSetup;
};
}} // Namespace
#endif

@ -0,0 +1,99 @@
#include "sample_reader.hpp"
#include <string.h>
using namespace Mangle::Sound;
void SampleReader::setup(int size)
{
pullSize = 0;
frameSize = size;
pullOver = new char[size];
}
SampleReader::~SampleReader()
{
if(pullOver)
delete[] pullOver;
}
size_t SampleReader::read(void *_data, size_t length)
{
if(isEof) return 0;
char *data = (char*)_data;
// Pullsize holds the number of bytes that were copied "extra" at
// the end of LAST round. If non-zero, it also means there is data
// left in the pullOver buffer.
if(pullSize)
{
// Amount of data left
size_t doRead = frameSize - pullSize;
assert(doRead > 0);
// Make sure we don't read more than we're supposed to
if(doRead > length) doRead = length;
memcpy(data, pullOver+pullSize, doRead);
// Update the number of bytes now copied
pullSize += doRead;
assert(pullSize <= frameSize);
if(pullSize < frameSize)
{
// There is STILL data left in the pull buffer, and we've
// done everything we were supposed to. Leave it and return.
assert(doRead == length);
return doRead;
}
// Set up variables for further reading below. No need to update
// pullSize, it is overwritten anyway.
length -= doRead;
data += doRead;
}
// Number of whole frames
size_t frames = length / frameSize;
// Read the data
size_t res = readSamples(data, frames);
assert(res <= frames);
// Total bytes read
size_t num = res*frameSize;
data += num;
if(res < frames)
{
// End of stream.
isEof = true;
// Determine how much we read
return data-(char*)_data;
}
// Determine the overshoot
pullSize = length - num;
assert(pullSize < frameSize && pullSize >= 0);
// Are we missing data?
if(pullSize)
{
// Fill in one sample
res = readSamples(pullOver,1);
assert(res == 1 || res == 0);
if(res)
{
// Move as much as we can into the output buffer
memcpy(data, pullOver, pullSize);
data += pullSize;
}
else
// Failed reading, we're out of data
isEof = true;
}
// Return the total number of bytes stored
return data-(char*)_data;
}

@ -0,0 +1,48 @@
#ifndef MANGLE_SOUND_SAMPLE_READER_H
#define MANGLE_SOUND_SAMPLE_READER_H
#include "../source.hpp"
namespace Mangle {
namespace Sound {
/* This is a helper base class for other SampleSource
implementations. Certain sources (like Audiere and libsndfile)
insist on reading whole samples rather than bytes. This class
compensates for that, and allows you to read bytes rather than
samples.
There are two ways for subclasses to use this class. EITHER call
setup() with the size of frameSize. This will allocate a buffer,
which the destructor frees. OR set frameSize manually and
manipulate the pullOver pointer yourself. In that case you MUST
reset it to NULL if you don't want the destructor to call
delete[] on it.
*/
class SampleReader : public SampleSource
{
// How much of the above buffer is in use.
int pullSize;
protected:
// Pullover buffer
char* pullOver;
// Size of one frame, in bytes. This is also the size of the
// pullOver buffer.
int frameSize;
// The parameter gives the size of one sample/frame, in bytes.
void setup(int);
// Read the given number of samples, in multiples of frameSize. Does
// not have to set or respect isEof.
virtual size_t readSamples(void *data, size_t num) = 0;
public:
SampleReader() : pullSize(0), pullOver(NULL) {}
~SampleReader();
size_t read(void *data, size_t length);
};
}} // Namespace
#endif

@ -0,0 +1,47 @@
#ifndef MANGLE_SOUND_STREAMSOURCE_H
#define MANGLE_SOUND_STREAMSOURCE_H
#include "../source.hpp"
namespace Mangle {
namespace Sound {
/// A class for reading raw samples directly from a stream.
class Stream2Samples : public SampleSource
{
Mangle::Stream::StreamPtr inp;
int32_t rate, channels, bits;
public:
Stream2Samples(Mangle::Stream::StreamPtr _inp, int32_t _rate, int32_t _channels, int32_t _bits)
: inp(_inp), rate(_rate), channels(_channels), bits(_bits)
{
isSeekable = inp->isSeekable;
hasPosition = inp->hasPosition;
hasSize = inp->hasSize;
hasPtr = inp->hasPtr;
}
/// Get the sample rate, number of channels, and bits per
/// sample. NULL parameters are ignored.
void getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits)
{
if(_rate) *_rate = rate;
if(_channels) *_channels = channels;
if(_bits) *_bits = bits;
}
size_t read(void *out, size_t count)
{ return inp->read(out, count); }
void seek(size_t pos) { inp->seek(pos); }
size_t tell() const { return inp->tell(); }
size_t size() const { return inp->size(); }
bool eof() const { return inp->eof(); }
const void *getPtr() { return inp->getPtr(); }
const void *getPtr(size_t size) { return inp->getPtr(size); }
const void *getPtr(size_t pos, size_t size) { return inp->getPtr(pos, size); }
};
}} // namespaces
#endif

@ -0,0 +1,99 @@
#include "wav_source.hpp"
#include "../../stream/servers/file_stream.hpp"
#include <stdexcept>
using namespace Mangle::Stream;
using namespace Mangle::Sound;
static void fail(const std::string &msg)
{ throw std::runtime_error("Mangle::Wav exception: " + msg); }
void WavSource::getInfo(int32_t *pRate, int32_t *pChannels, int32_t *pBits)
{
// Use the values we found in the constructor
*pRate = rate;
*pChannels = channels;
*pBits = bits;
}
void WavSource::seek(size_t pos)
{
// Seek the stream and set 'left'
assert(isSeekable);
if(pos > total) pos = total;
input->seek(dataOffset + pos);
left = total-pos;
}
size_t WavSource::read(void *data, size_t length)
{
if(length > left)
length = left;
size_t read = input->read(data, length);
if(read < length)
// Something went wrong
fail("WAV read error");
return length;
}
void WavSource::open(Mangle::Stream::StreamPtr data)
{
input = data;
hasPosition = true;
hasSize = true;
// If we can check position and seek in the input stream, then we
// can seek the wav data too.
isSeekable = input->isSeekable && input->hasPosition;
// Read header
unsigned int val;
input->read(&val,4); // header
if(val != 0x46464952) // "RIFF"
fail("Not a WAV file");
input->read(&val,4); // size (ignored)
input->read(&val,4); // file format
if(val != 0x45564157) // "WAVE"
fail("Not a valid WAV file");
input->read(&val,4); // "fmt "
input->read(&val,4); // chunk size (must be 16)
if(val != 16)
fail("Unsupported WAV format");
input->read(&val,2);
if(val != 1)
fail("Non-PCM (compressed) WAV files not supported");
// Sound data specification
channels = 0;
input->read(&channels,2);
input->read(&rate, 4);
// Skip next 6 bytes
input->read(&val, 4);
input->read(&val, 2);
// Bits per sample
bits = 0;
input->read(&bits,2);
input->read(&val,4); // Data header
if(val != 0x61746164) // "data"
fail("Expected data block");
// Finally, read the data size
input->read(&total,4);
left = total;
// Store the beginning of the data block for later
if(input->hasPosition)
dataOffset = input->tell();
}
WavSource::WavSource(const std::string &file)
{ open(StreamPtr(new FileStream(file))); }

@ -0,0 +1,49 @@
#ifndef MANGLE_SOUND_WAV_SOURCE_H
#define MANGLE_SOUND_WAV_SOURCE_H
#include "../source.hpp"
#include <assert.h>
namespace Mangle {
namespace Sound {
/// WAV file decoder. Has no external library dependencies.
class WavSource : public SampleSource
{
// Sound info
uint32_t rate, channels, bits;
// Total size (of output) and bytes left
uint32_t total, left;
// Offset in input of the beginning of the data block
size_t dataOffset;
Mangle::Stream::StreamPtr input;
void open(Mangle::Stream::StreamPtr);
public:
/// Decode the given sound file
WavSource(const std::string&);
/// Decode from stream
WavSource(Mangle::Stream::StreamPtr s)
{ open(s); }
void getInfo(int32_t *rate, int32_t *channels, int32_t *bits);
size_t read(void *data, size_t length);
void seek(size_t);
size_t tell() const { return total-left; }
size_t size() const { return total; }
bool eof() const { return left > 0; }
};
#include "loadertemplate.hpp"
/// A factory that loads WavSources from file and stream
typedef SSL_Template<WavSource,true,true> WavLoader;
}} // Namespace
#endif

@ -0,0 +1,38 @@
GCC=g++ -I../ -Wall
all: audiere_source_test ffmpeg_source_test openal_output_test openal_audiere_test openal_ffmpeg_test openal_mpg123_test openal_sndfile_test wav_source_test openal_various_test
L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat)
I_FFMPEG=-I/usr/include/libavcodec -I/usr/include/libavformat
L_OPENAL=$(shell pkg-config --libs openal)
L_AUDIERE=-laudiere
wav_source_test: wav_source_test.cpp ../sources/wav_source.cpp
$(GCC) $^ -o $@
openal_various_test: openal_various_test.cpp ../sources/mpg123_source.cpp ../sources/wav_source.cpp ../outputs/openal_out.cpp
$(GCC) $^ -o $@ -lmpg123 ${L_OPENAL}
openal_audiere_test: openal_audiere_test.cpp ../sources/audiere_source.cpp ../sources/sample_reader.cpp ../outputs/openal_out.cpp ../../stream/clients/audiere_file.cpp
$(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL)
openal_ffmpeg_test: openal_ffmpeg_test.cpp ../sources/ffmpeg_source.cpp ../outputs/openal_out.cpp
$(GCC) $^ -o $@ $(L_FFMPEG) $(L_OPENAL) $(I_FFMPEG)
openal_mpg123_test: openal_mpg123_test.cpp ../sources/mpg123_source.cpp ../outputs/openal_out.cpp
$(GCC) $^ -o $@ -lmpg123 ${L_OPENAL}
openal_sndfile_test: openal_sndfile_test.cpp ../sources/libsndfile.cpp ../sources/sample_reader.cpp ../outputs/openal_out.cpp
$(GCC) $^ -o $@ -lsndfile ${L_OPENAL}
openal_output_test: openal_output_test.cpp ../outputs/openal_out.cpp
$(GCC) $^ -o $@ $(L_OPENAL)
audiere_source_test: audiere_source_test.cpp ../sources/audiere_source.cpp ../../stream/clients/audiere_file.cpp ../sources/sample_reader.cpp
$(GCC) $^ -o $@ $(L_AUDIERE)
ffmpeg_source_test: ffmpeg_source_test.cpp ../sources/ffmpeg_source.cpp
$(GCC) $^ -o $@ $(L_FFMPEG) $(I_FFMPEG)
clean:
rm *_test

@ -0,0 +1,68 @@
#include <iostream>
#include "../../stream/servers/file_stream.hpp"
#include "../sources/audiere_source.hpp"
#include <assert.h>
#include <string.h>
using namespace std;
using namespace Mangle::Stream;
using namespace Mangle::Sound;
// Contents and size of cow.raw
void *orig;
size_t orig_size;
void run(SampleSourcePtr &src)
{
size_t ss = src->size();
assert(ss == orig_size);
cout << "Source size: " << ss << endl;
int rate, channels, bits;
src->getInfo(&rate, &channels, &bits);
cout << "rate=" << rate << "\nchannels=" << channels
<< "\nbits=" << bits << endl;
cout << "Reading entire buffer into memory\n";
void *buf = malloc(ss);
src->read(buf, ss);
cout << "Comparing...\n";
if(memcmp(buf, orig, ss) != 0)
{
cout << "Oops!\n";
assert(0);
}
cout << "Done\n";
}
int main()
{
{
cout << "Reading cow.raw first\n";
FileStream tmp("cow.raw");
orig_size = tmp.size();
cout << "Size: " << orig_size << endl;
orig = malloc(orig_size);
tmp.read(orig, orig_size);
cout << "Done\n";
}
{
cout << "\nLoading cow.wav by filename:\n";
SampleSourcePtr cow_file( new AudiereSource("cow.wav") );
run(cow_file);
}
{
cout << "\nLoading cow.wav by stream:\n";
StreamPtr inp( new FileStream("cow.wav") );
SampleSourcePtr cow_stream( new AudiereSource(inp) );
run(cow_stream);
}
return 0;
}

Binary file not shown.

Binary file not shown.

@ -0,0 +1,62 @@
#include <iostream>
#include "../../stream/servers/file_stream.hpp"
#include "../sources/ffmpeg_source.hpp"
#include <assert.h>
#include <string.h>
using namespace std;
using namespace Mangle::Stream;
using namespace Mangle::Sound;
// Contents and size of cow.raw
void *orig;
size_t orig_size;
void run(SampleSourcePtr &src)
{
int rate, channels, bits;
src->getInfo(&rate, &channels, &bits);
cout << "rate=" << rate << "\nchannels=" << channels
<< "\nbits=" << bits << endl;
cout << "Reading entire buffer into memory\n";
void *buf = malloc(orig_size);
size_t ss = src->read(buf, orig_size);
cout << "Actually read: " << ss << endl;
assert(ss == orig_size);
cout << "Comparing...\n";
if(memcmp(buf, orig, ss) != 0)
{
cout << "Oops!\n";
assert(0);
}
cout << "Done\n";
}
int main()
{
{
cout << "Reading cow.raw first\n";
FileStream tmp("cow.raw");
orig_size = tmp.size();
cout << "Size: " << orig_size << endl;
orig = malloc(orig_size);
tmp.read(orig, orig_size);
cout << "Done\n";
}
// Initializes the library, not used for anything else.
FFMpegLoader fm;
{
cout << "\nLoading cow.wav by filename:\n";
SampleSourcePtr cow_file( new FFMpegSource("cow.wav") );
run(cow_file);
}
return 0;
}

@ -0,0 +1,52 @@
#include <iostream>
#include <exception>
#include "../../stream/servers/file_stream.hpp"
#include "../filters/openal_audiere.hpp"
using namespace std;
using namespace Mangle::Stream;
using namespace Mangle::Sound;
OpenAL_Audiere_Factory mg;
void play(const char* name, bool stream=false)
{
// Only load streams if the backend supports it
if(stream && !mg.canLoadStream)
return;
cout << "Playing " << name;
if(stream) cout << " (from stream)";
cout << "\n";
SoundPtr snd;
try
{
if(stream)
snd = mg.load(StreamPtr(new FileStream(name)));
else
snd = mg.load(name);
snd->play();
while(snd->isPlaying())
{
usleep(10000);
if(mg.needsUpdate) mg.update();
}
}
catch(exception &e)
{
cout << " ERROR: " << e.what() << "\n";
}
}
int main()
{
play("cow.wav");
play("owl.ogg");
play("cow.wav", true);
return 0;
}

@ -0,0 +1,52 @@
#include <iostream>
#include <exception>
#include "../../stream/servers/file_stream.hpp"
#include "../filters/openal_ffmpeg.hpp"
using namespace std;
using namespace Mangle::Stream;
using namespace Mangle::Sound;
OpenAL_FFMpeg_Factory mg;
void play(const char* name, bool stream=false)
{
// Only load streams if the backend supports it
if(stream && !mg.canLoadStream)
return;
cout << "Playing " << name;
if(stream) cout << " (from stream)";
cout << "\n";
SoundPtr snd;
try
{
if(stream)
snd = mg.load(StreamPtr(new FileStream(name)));
else
snd = mg.load(name);
snd->play();
while(snd->isPlaying())
{
usleep(10000);
if(mg.needsUpdate) mg.update();
}
}
catch(exception &e)
{
cout << " ERROR: " << e.what() << "\n";
}
}
int main()
{
play("cow.wav");
play("owl.ogg");
play("cow.wav", true);
return 0;
}

@ -0,0 +1,54 @@
#include <iostream>
#include <exception>
#include "../../stream/servers/file_stream.hpp"
#include "../filters/openal_mpg123.hpp"
using namespace std;
using namespace Mangle::Stream;
using namespace Mangle::Sound;
OpenAL_Mpg123_Factory mg;
void play(const char* name, bool stream=false)
{
// Only load streams if the backend supports it
if(stream && !mg.canLoadStream)
return;
cout << "Playing " << name;
if(stream) cout << " (from stream)";
cout << "\n";
SoundPtr snd;
try
{
if(stream)
snd = mg.load(StreamPtr(new FileStream(name)));
else
snd = mg.load(name);
snd->setStreaming(true);
snd->play();
while(snd->isPlaying())
{
usleep(10000);
if(mg.needsUpdate) mg.update();
}
}
catch(exception &e)
{
cout << " ERROR: " << e.what() << "\n";
}
}
int main(int argc, char**argv)
{
if(argc != 2)
cout << "Please specify an MP3 file\n";
else
play(argv[1]);
return 0;
}

@ -0,0 +1,59 @@
#include <iostream>
#include <exception>
#include "../../stream/servers/file_stream.hpp"
#include "../sources/stream_source.hpp"
#include "../outputs/openal_out.hpp"
using namespace std;
using namespace Mangle::Stream;
using namespace Mangle::Sound;
int main()
{
cout << "Loading cow.raw\n";
int rate = 11025;
int chan = 1;
int bits = 16;
cout << " rate=" << rate << "\n channels=" << chan
<< "\n bits=" << bits << endl;
StreamPtr file( new FileStream("cow.raw") );
SampleSourcePtr source( new Stream2Samples( file, rate, chan, bits));
cout << "Playing\n";
OpenAL_Factory mg;
SoundPtr snd = mg.loadRaw(source);
try
{
// Try setting all kinds of stuff before playing. OpenAL_Sound
// uses delayed buffer loading, but these should still work
// without a buffer.
snd->stop();
snd->pause();
snd->setVolume(0.8);
snd->setPitch(0.9);
// Also test streaming, since all the other examples test
// non-streaming sounds.
snd->setStreaming(true);
snd->play();
while(snd->isPlaying())
{
usleep(10000);
mg.update();
}
}
catch(exception &e)
{
cout << " ERROR: " << e.what() << "\n";
}
return 0;
}

@ -0,0 +1,52 @@
#include <iostream>
#include <exception>
#include "../../stream/servers/file_stream.hpp"
#include "../filters/openal_sndfile.hpp"
using namespace std;
using namespace Mangle::Stream;
using namespace Mangle::Sound;
OpenAL_SndFile_Factory mg;
void play(const char* name, bool stream=false)
{
// Only load streams if the backend supports it
if(stream && !mg.canLoadStream)
return;
cout << "Playing " << name;
if(stream) cout << " (from stream)";
cout << "\n";
SoundPtr snd;
try
{
if(stream)
snd = mg.load(StreamPtr(new FileStream(name)));
else
snd = mg.load(name);
snd->play();
while(snd->isPlaying())
{
usleep(10000);
if(mg.needsUpdate) mg.update();
}
}
catch(exception &e)
{
cout << " ERROR: " << e.what() << "\n";
}
}
int main()
{
play("cow.wav");
play("owl.ogg");
play("cow.wav", true);
return 0;
}

@ -0,0 +1,51 @@
#include <iostream>
#include <exception>
#include "../../stream/servers/file_stream.hpp"
#include "../filters/openal_various.hpp"
using namespace std;
using namespace Mangle::Stream;
using namespace Mangle::Sound;
OpenAL_Various_Factory mg;
void play(const char* name, bool stream=false)
{
// Only load streams if the backend supports it
if(stream && !mg.canLoadStream)
return;
cout << "Playing " << name;
if(stream) cout << " (from stream)";
cout << "\n";
SoundPtr snd;
try
{
if(stream)
snd = mg.load(StreamPtr(new FileStream(name)));
else
snd = mg.load(name);
snd->play();
while(snd->isPlaying())
{
usleep(10000);
if(mg.needsUpdate) mg.update();
}
}
catch(exception &e)
{
cout << " ERROR: " << e.what() << "\n";
}
}
int main()
{
play("cow.wav");
play("cow.wav", true);
return 0;
}

@ -0,0 +1,21 @@
Reading cow.raw first
Size: 37502
Done
Loading cow.wav by filename:
Source size: 37502
rate=11025
channels=1
bits=16
Reading entire buffer into memory
Comparing...
Done
Loading cow.wav by stream:
Source size: 37502
rate=11025
channels=1
bits=16
Reading entire buffer into memory
Comparing...
Done

@ -0,0 +1,12 @@
Reading cow.raw first
Size: 37502
Done
Loading cow.wav by filename:
rate=11025
channels=1
bits=16
Reading entire buffer into memory
Actually read: 37502
Comparing...
Done

@ -0,0 +1,3 @@
Playing cow.wav
Playing owl.ogg
Playing cow.wav (from stream)

@ -0,0 +1,2 @@
Playing cow.wav
Playing owl.ogg

@ -0,0 +1,5 @@
Loading cow.raw
rate=11025
channels=1
bits=16
Playing

@ -0,0 +1,2 @@
Playing cow.wav
Playing owl.ogg

@ -0,0 +1,12 @@
Source size: 37502
rate=11025
channels=1
bits=16
Reading entire buffer into memory
Reading cow.raw
Size: 37502
Comparing...
Done

Binary file not shown.

@ -0,0 +1,18 @@
#!/bin/bash
make || exit
mkdir -p output
PROGS=*_test
for a in $PROGS; do
if [ -f "output/$a.out" ]; then
echo "Running $a:"
./$a | diff output/$a.out -
else
echo "Creating $a.out"
./$a > "output/$a.out"
git add "output/$a.out"
fi
done

@ -0,0 +1,48 @@
#include <iostream>
#include "../sources/wav_source.hpp"
#include "../../stream/servers/file_stream.hpp"
#include <assert.h>
#include <string.h>
using namespace std;
using namespace Mangle::Sound;
using namespace Mangle::Stream;
int main()
{
WavSource wav("cow.wav");
cout << "Source size: " << wav.size() << endl;
int rate, channels, bits;
wav.getInfo(&rate, &channels, &bits);
cout << "rate=" << rate << "\nchannels=" << channels
<< "\nbits=" << bits << endl;
cout << "Reading entire buffer into memory\n";
void *buf = malloc(wav.size());
wav.read(buf, wav.size());
cout << "\nReading cow.raw\n";
FileStream tmp("cow.raw");
cout << "Size: " << tmp.size() << endl;
void *buf2 = malloc(tmp.size());
tmp.read(buf2, tmp.size());
cout << "\nComparing...\n";
if(tmp.size() != wav.size())
{
cout << "SIZE MISMATCH!\n";
assert(0);
}
if(memcmp(buf, buf2, wav.size()) != 0)
{
cout << "CONTENT MISMATCH!\n";
assert(0);
}
cout << "\nDone\n";
return 0;
}

@ -0,0 +1,32 @@
#include "audiere_file.hpp"
using namespace audiere;
using namespace Mangle::Stream;
bool AudiereFile::seek(int pos, SeekMode mode)
{
assert(inp->isSeekable);
assert(inp->hasPosition);
size_t newPos;
switch(mode)
{
case BEGIN: newPos = pos; break;
case CURRENT: newPos = pos+tell(); break;
case END:
// Seeking from the end. This requires that we're able to get
// the entire size of the stream. The pos also has to be
// non-positive.
assert(inp->hasSize);
assert(pos <= 0);
newPos = inp->size() + pos;
break;
default:
assert(0 && "invalid seek mode");
}
inp->seek(newPos);
return inp->tell() == newPos;
}

@ -0,0 +1,38 @@
#ifndef MANGLE_STREAM_AUDIERECLIENT_H
#define MANGLE_STREAM_AUDIERECLIENT_H
#include <audiere.h>
#include <assert.h>
#include "../stream.hpp"
namespace Mangle {
namespace Stream {
/** @brief An Audiere::File that wraps a Mangle::Stream input.
This lets Audiere read sound files from any generic archive or
file manager that supports Mangle streams.
*/
class AudiereFile : public audiere::RefImplementation<audiere::File>
{
StreamPtr inp;
public:
AudiereFile(StreamPtr _inp)
: inp(_inp) {}
/// Read 'count' bytes, return bytes successfully read
int ADR_CALL read(void *buf, int count)
{ return inp->read(buf,count); }
/// Seek, relative to specified seek mode. Returns true if successful.
bool ADR_CALL seek(int pos, audiere::File::SeekMode mode);
/// Get current position
int ADR_CALL tell()
{ assert(inp->hasPosition); return inp->tell(); }
};
}} // namespaces
#endif

@ -0,0 +1,221 @@
#include "io_stream.hpp"
// This seems to work
#ifndef EOF
#define EOF -1
#endif
using namespace Mangle::Stream;
#define BSIZE 1024
// Streambuf for normal stream reading
class _istreambuf : public std::streambuf
{
StreamPtr client;
char buf[BSIZE];
public:
_istreambuf(StreamPtr strm) : client(strm)
{
// Make sure we picked the right class
assert(client->isReadable);
assert(!client->hasPtr);
// Tell streambuf to delegate reading operations to underflow()
setg(NULL,NULL,NULL);
// Disallow writing
setp(NULL,NULL);
}
/* Underflow is called when there is no more info to read in the
input buffer. We need to refill buf with new data (if any), and
set up the internal pointers with setg() to reflect the new
state.
*/
int underflow()
{
// Read some more data
size_t read = client->read(buf, BSIZE);
assert(read <= BSIZE);
// If we're out of data, then EOF
if(read == 0)
return EOF;
// Otherwise, set up input buffer
setg(buf, buf, buf+read);
// Return the first char
return *((unsigned char*)buf);
}
// Seek stream, if the source supports it. Ignores the second
// parameter as Mangle doesn't separate input and output pointers.
std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::in)
{
// Does this stream know how to seek?
if(!client->isSeekable || !client->hasPosition)
// If not, signal an error.
return -1;
// Set stream position and reset the buffer.
client->seek(pos);
setg(NULL,NULL,NULL);
return client->tell();
}
};
// Streambuf optimized for pointer-based input streams
class _ptrstreambuf : public std::streambuf
{
StreamPtr client;
public:
_ptrstreambuf(StreamPtr strm) : client(strm)
{
// Make sure we picked the right class
assert(client->isReadable);
assert(client->hasPtr);
// seekpos() does all the work
seekpos(0);
}
// Underflow is only called when we're at the end of the file
int underflow() { return EOF; }
// Seek to a new position within the memory stream. This bypasses
// client->seek() entirely so isSeekable doesn't have to be set.
std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::in)
{
// All pointer streams need a size
assert(client->hasSize);
// Figure out how much will be left of the stream after seeking
size_t size = client->size() - pos;
// Get a pointer
char* ptr = (char*)client->getPtr(pos,size);
// And use it
setg(ptr,ptr,ptr+size);
return pos;
}
};
// Streambuf for stream writing
class _ostreambuf : public std::streambuf
{
StreamPtr client;
char buf[BSIZE];
public:
_ostreambuf(StreamPtr strm) : client(strm)
{
// Make sure we picked the right class
assert(client->isWritable);
// Inform streambuf about our nice buffer
setp(buf, buf+BSIZE);
// Disallow reading
setg(NULL,NULL,NULL);
}
/* Sync means to flush (write) all current data to the output
stream. It will also set up the entire output buffer to be usable
again.
*/
int sync()
{
// Get the number of bytes that streambuf wants us to write
int num = pptr() - pbase();
assert(num >= 0);
// Is there any work to do?
if(num == 0) return 0;
if((int)client->write(pbase(), num) != num)
// Inform caller that writing failed
return -1;
// Reset output buffer pointers
setp(buf, buf+BSIZE);
// No error
return 0;
}
/* Called whenever the output buffer is full.
*/
int overflow(int c)
{
// First, write all existing data
if(sync()) return EOF;
// Put the requested character in the next round of output
if(c != EOF)
{
*pptr() = c;
pbump(1);
}
// No error
return 0;
}
// Seek stream, if the source supports it.
std::streampos seekpos(std::streampos pos, std::ios_base::openmode = std::ios_base::out)
{
if(!client->isSeekable || !client->hasPosition)
return -1;
// Flush data and reset buffers
sync();
// Set stream position
client->seek(pos);
return client->tell();
}
};
MangleIStream::MangleIStream(StreamPtr inp)
: std::istream(NULL)
{
assert(inp->isReadable);
// Pick the right streambuf implementation based on whether the
// input supports pointers or not.
if(inp->hasPtr)
buf = new _ptrstreambuf(inp);
else
buf = new _istreambuf(inp);
rdbuf(buf);
}
MangleIStream::~MangleIStream()
{
delete buf;
}
MangleOStream::MangleOStream(StreamPtr out)
: std::ostream(NULL)
{
assert(out->isWritable);
buf = new _ostreambuf(out);
rdbuf(buf);
}
MangleOStream::~MangleOStream()
{
// Make sure we don't have lingering data on exit
flush();
delete buf;
}

@ -0,0 +1,43 @@
#ifndef MANGLE_STREAM_IOSTREAM_H
#define MANGLE_STREAM_IOSTREAM_H
#include <assert.h>
#include "../stream.hpp"
#include <iostream>
namespace Mangle {
namespace Stream {
/** This file contains classes for wrapping an std::istream or
std::ostream around a Mangle::Stream.
This allows you to use Mangle streams in places that require std
streams.
This is much easier than trying to make your own custom streams
into iostreams. The std::iostream interface is horrible and NOT
designed for easy subclassing. Create a Mangle::Stream instead,
and use this wrapper.
*/
// An istream wrapping a readable Mangle::Stream. Has extra
// optimizations for pointer-based streams.
class MangleIStream : public std::istream
{
std::streambuf *buf;
public:
MangleIStream(StreamPtr inp);
~MangleIStream();
};
// An ostream wrapping a writable Mangle::Stream.
class MangleOStream : public std::ostream
{
std::streambuf *buf;
public:
MangleOStream(StreamPtr inp);
~MangleOStream();
};
}} // namespaces
#endif

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save