Merge mangle into openmw
commit
48c12e9d5c
@ -1,6 +1,3 @@
|
|||||||
[submodule "libs/mangle"]
|
|
||||||
path = libs/mangle
|
|
||||||
url = git://github.com/zinnschlag/mangle.git
|
|
||||||
[submodule "libs/openengine"]
|
[submodule "libs/openengine"]
|
||||||
path = libs/openengine
|
path = libs/openengine
|
||||||
url = git://github.com/zinnschlag/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 @@
|
|||||||
|
*_test
|
@ -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 @@
|
|||||||
|
|
@ -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 @@
|
|||||||
|
*_test
|
@ -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 @@
|
|||||||
|
Please specify an MP3 file
|
@ -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 @@
|
|||||||
|
Playing cow.wav
|
@ -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…
Reference in New Issue