diff --git a/.gitmodules b/.gitmodules index 1ce6250b3a..a4272d717c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "libs/mangle"] - path = libs/mangle - url = git://github.com/zinnschlag/mangle.git [submodule "libs/openengine"] path = libs/openengine url = git://github.com/zinnschlag/OpenEngine diff --git a/libs/mangle b/libs/mangle deleted file mode 160000 index 14b2851e72..0000000000 --- a/libs/mangle +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 14b2851e72f610ae81dd296598867e6fb0babd2a diff --git a/libs/mangle/.gitignore b/libs/mangle/.gitignore new file mode 100644 index 0000000000..cd24d78972 --- /dev/null +++ b/libs/mangle/.gitignore @@ -0,0 +1,3 @@ +upload_docs.sh +docs +*~ diff --git a/libs/mangle/Doxyfile b/libs/mangle/Doxyfile new file mode 100644 index 0000000000..f3e0180029 --- /dev/null +++ b/libs/mangle/Doxyfile @@ -0,0 +1,1510 @@ +# Doxyfile 1.5.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = Mangle + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 1 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, +# Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = sound stream vfs input + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = */tests/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = docs + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NONE + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/libs/mangle/LICENSE.txt b/libs/mangle/LICENSE.txt new file mode 100644 index 0000000000..ccfcc9f220 --- /dev/null +++ b/libs/mangle/LICENSE.txt @@ -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. + diff --git a/libs/mangle/README.txt b/libs/mangle/README.txt new file mode 100644 index 0000000000..f4849bebda --- /dev/null +++ b/libs/mangle/README.txt @@ -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. diff --git a/libs/mangle/input/clients/ogre_input_capture.hpp b/libs/mangle/input/clients/ogre_input_capture.hpp new file mode 100644 index 0000000000..2e77dc10b1 --- /dev/null +++ b/libs/mangle/input/clients/ogre_input_capture.hpp @@ -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 +#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 diff --git a/libs/mangle/input/driver.hpp b/libs/mangle/input/driver.hpp new file mode 100644 index 0000000000..f4ba159c52 --- /dev/null +++ b/libs/mangle/input/driver.hpp @@ -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 DriverPtr; + } +} +#endif diff --git a/libs/mangle/input/event.hpp b/libs/mangle/input/event.hpp new file mode 100644 index 0000000000..dc7b470887 --- /dev/null +++ b/libs/mangle/input/event.hpp @@ -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 EventPtr; + } +} +#endif diff --git a/libs/mangle/input/filters/eventlist.hpp b/libs/mangle/input/filters/eventlist.hpp new file mode 100644 index 0000000000..b3e2ff8f24 --- /dev/null +++ b/libs/mangle/input/filters/eventlist.hpp @@ -0,0 +1,47 @@ +#ifndef MANGLE_INPUT_EVENTLIST_H +#define MANGLE_INPUT_EVENTLIST_H + +#include "../event.hpp" +#include + +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 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::iterator it; + + for(it=list.begin(); it!=list.end(); it++) + { + if(type & it->flags) + it->evt->event(type,index,p); + } + } + }; + + typedef boost::shared_ptr EventListPtr; + } +} +#endif diff --git a/libs/mangle/input/servers/ois_driver.cpp b/libs/mangle/input/servers/ois_driver.cpp new file mode 100644 index 0000000000..2071b91ea6 --- /dev/null +++ b/libs/mangle/input/servers/ois_driver.cpp @@ -0,0 +1,148 @@ +#include "ois_driver.hpp" + +#include +#include +#include +#include + +#ifdef __APPLE_CC__ +#include +#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(inputMgr->createInputObject + ( OISKeyboard, true )); + mouse = static_cast(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); +} diff --git a/libs/mangle/input/servers/ois_driver.hpp b/libs/mangle/input/servers/ois_driver.hpp new file mode 100644 index 0000000000..ba780c39e6 --- /dev/null +++ b/libs/mangle/input/servers/ois_driver.hpp @@ -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 diff --git a/libs/mangle/input/servers/sdl_driver.cpp b/libs/mangle/input/servers/sdl_driver.cpp new file mode 100644 index 0000000000..93884a6e6c --- /dev/null +++ b/libs/mangle/input/servers/sdl_driver.cpp @@ -0,0 +1,54 @@ +#include "sdl_driver.hpp" + +#include + +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); +} diff --git a/libs/mangle/input/servers/sdl_driver.hpp b/libs/mangle/input/servers/sdl_driver.hpp new file mode 100644 index 0000000000..b71346cba1 --- /dev/null +++ b/libs/mangle/input/servers/sdl_driver.hpp @@ -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 diff --git a/libs/mangle/input/tests/.gitignore b/libs/mangle/input/tests/.gitignore new file mode 100644 index 0000000000..460c76f00b --- /dev/null +++ b/libs/mangle/input/tests/.gitignore @@ -0,0 +1,2 @@ +*_test +ogre.cfg diff --git a/libs/mangle/input/tests/Makefile b/libs/mangle/input/tests/Makefile new file mode 100644 index 0000000000..8760adfe73 --- /dev/null +++ b/libs/mangle/input/tests/Makefile @@ -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 diff --git a/libs/mangle/input/tests/common.cpp b/libs/mangle/input/tests/common.cpp new file mode 100644 index 0000000000..0c7c76466b --- /dev/null +++ b/libs/mangle/input/tests/common.cpp @@ -0,0 +1,35 @@ +#include +#include "../driver.hpp" +#include +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"; +} diff --git a/libs/mangle/input/tests/evtlist_test.cpp b/libs/mangle/input/tests/evtlist_test.cpp new file mode 100644 index 0000000000..fbd980cbd9 --- /dev/null +++ b/libs/mangle/input/tests/evtlist_test.cpp @@ -0,0 +1,45 @@ +#include +#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; +} diff --git a/libs/mangle/input/tests/ois_driver_test.cpp b/libs/mangle/input/tests/ois_driver_test.cpp new file mode 100644 index 0000000000..386f24055e --- /dev/null +++ b/libs/mangle/input/tests/ois_driver_test.cpp @@ -0,0 +1,51 @@ +#include "common.cpp" + +#include "../servers/ois_driver.hpp" +#include +#include +#include + +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; +} diff --git a/libs/mangle/input/tests/output/evtlist_test.out b/libs/mangle/input/tests/output/evtlist_test.out new file mode 100644 index 0000000000..180dcc58a8 --- /dev/null +++ b/libs/mangle/input/tests/output/evtlist_test.out @@ -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 diff --git a/libs/mangle/input/tests/output/ois_driver_test.out b/libs/mangle/input/tests/output/ois_driver_test.out new file mode 100644 index 0000000000..7d273fd46d --- /dev/null +++ b/libs/mangle/input/tests/output/ois_driver_test.out @@ -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! diff --git a/libs/mangle/input/tests/output/sdl_driver_test.out b/libs/mangle/input/tests/output/sdl_driver_test.out new file mode 100644 index 0000000000..2df2e4014e --- /dev/null +++ b/libs/mangle/input/tests/output/sdl_driver_test.out @@ -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! diff --git a/libs/mangle/input/tests/plugins.cfg b/libs/mangle/input/tests/plugins.cfg new file mode 100644 index 0000000000..57ec54e1a0 --- /dev/null +++ b/libs/mangle/input/tests/plugins.cfg @@ -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 + + diff --git a/libs/mangle/input/tests/sdl_driver_test.cpp b/libs/mangle/input/tests/sdl_driver_test.cpp new file mode 100644 index 0000000000..5db6dbba8f --- /dev/null +++ b/libs/mangle/input/tests/sdl_driver_test.cpp @@ -0,0 +1,16 @@ +#include "common.cpp" + +#include "../servers/sdl_driver.hpp" +#include + +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; +} diff --git a/libs/mangle/input/tests/test.sh b/libs/mangle/input/tests/test.sh new file mode 100755 index 0000000000..2d07708adc --- /dev/null +++ b/libs/mangle/input/tests/test.sh @@ -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 diff --git a/libs/mangle/rend2d/driver.hpp b/libs/mangle/rend2d/driver.hpp new file mode 100644 index 0000000000..08a15b0aeb --- /dev/null +++ b/libs/mangle/rend2d/driver.hpp @@ -0,0 +1,63 @@ +#ifndef MANGLE_REND2D_DRIVER_H +#define MANGLE_REND2D_DRIVER_H + +#include +#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 diff --git a/libs/mangle/rend2d/servers/sdl_driver.cpp b/libs/mangle/rend2d/servers/sdl_driver.cpp new file mode 100644 index 0000000000..aa1ff6c6dc --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_driver.cpp @@ -0,0 +1,259 @@ +#include "sdl_driver.hpp" + +#include +#include +#include +#include + +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(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(); } diff --git a/libs/mangle/rend2d/servers/sdl_driver.hpp b/libs/mangle/rend2d/servers/sdl_driver.hpp new file mode 100644 index 0000000000..0f205ba34c --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_driver.hpp @@ -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 diff --git a/libs/mangle/rend2d/servers/sdl_gl_driver.cpp b/libs/mangle/rend2d/servers/sdl_gl_driver.cpp new file mode 100644 index 0000000000..2bcb1d6777 --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_gl_driver.cpp @@ -0,0 +1,311 @@ +#include "sdl_gl_driver.hpp" + +#include +#include +#include +#include +#include + +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(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(); } diff --git a/libs/mangle/rend2d/servers/sdl_gl_driver.hpp b/libs/mangle/rend2d/servers/sdl_gl_driver.hpp new file mode 100644 index 0000000000..d116e3659b --- /dev/null +++ b/libs/mangle/rend2d/servers/sdl_gl_driver.hpp @@ -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 diff --git a/libs/mangle/rend2d/sprite.hpp b/libs/mangle/rend2d/sprite.hpp new file mode 100644 index 0000000000..f49da6cb6d --- /dev/null +++ b/libs/mangle/rend2d/sprite.hpp @@ -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 diff --git a/libs/mangle/rend2d/tests/.gitignore b/libs/mangle/rend2d/tests/.gitignore new file mode 100644 index 0000000000..8144904045 --- /dev/null +++ b/libs/mangle/rend2d/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/rend2d/tests/Makefile b/libs/mangle/rend2d/tests/Makefile new file mode 100644 index 0000000000..d430f60a93 --- /dev/null +++ b/libs/mangle/rend2d/tests/Makefile @@ -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 diff --git a/libs/mangle/rend2d/tests/output/sdl_move_test.out b/libs/mangle/rend2d/tests/output/sdl_move_test.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/mangle/rend2d/tests/output/sdl_test.out b/libs/mangle/rend2d/tests/output/sdl_test.out new file mode 100644 index 0000000000..4528e1a98a --- /dev/null +++ b/libs/mangle/rend2d/tests/output/sdl_test.out @@ -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. diff --git a/libs/mangle/rend2d/tests/output/sdlgl_move_test.out b/libs/mangle/rend2d/tests/output/sdlgl_move_test.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/mangle/rend2d/tests/sdl_move_test.cpp b/libs/mangle/rend2d/tests/sdl_move_test.cpp new file mode 100644 index 0000000000..bfbca98fa7 --- /dev/null +++ b/libs/mangle/rend2d/tests/sdl_move_test.cpp @@ -0,0 +1,30 @@ +#include +#include + +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; +} diff --git a/libs/mangle/rend2d/tests/sdl_test.cpp b/libs/mangle/rend2d/tests/sdl_test.cpp new file mode 100644 index 0000000000..0355112e61 --- /dev/null +++ b/libs/mangle/rend2d/tests/sdl_test.cpp @@ -0,0 +1,65 @@ +#include +#include + +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; +} diff --git a/libs/mangle/rend2d/tests/sdlgl_move_test.cpp b/libs/mangle/rend2d/tests/sdlgl_move_test.cpp new file mode 100644 index 0000000000..b769ee837d --- /dev/null +++ b/libs/mangle/rend2d/tests/sdlgl_move_test.cpp @@ -0,0 +1,31 @@ +#include +#include + +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; +} diff --git a/libs/mangle/rend2d/tests/test.sh b/libs/mangle/rend2d/tests/test.sh new file mode 100755 index 0000000000..2d07708adc --- /dev/null +++ b/libs/mangle/rend2d/tests/test.sh @@ -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 diff --git a/libs/mangle/rend2d/tests/tile1-blue.png b/libs/mangle/rend2d/tests/tile1-blue.png new file mode 100644 index 0000000000..066e6f8eb9 Binary files /dev/null and b/libs/mangle/rend2d/tests/tile1-blue.png differ diff --git a/libs/mangle/rend2d/tests/tile1-yellow.png b/libs/mangle/rend2d/tests/tile1-yellow.png new file mode 100644 index 0000000000..2aaf9015d3 Binary files /dev/null and b/libs/mangle/rend2d/tests/tile1-yellow.png differ diff --git a/libs/mangle/sound/.gitignore b/libs/mangle/sound/.gitignore new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/libs/mangle/sound/.gitignore @@ -0,0 +1 @@ + diff --git a/libs/mangle/sound/clients/ogre_listener_mover.hpp b/libs/mangle/sound/clients/ogre_listener_mover.hpp new file mode 100644 index 0000000000..739c08a13f --- /dev/null +++ b/libs/mangle/sound/clients/ogre_listener_mover.hpp @@ -0,0 +1,79 @@ +#ifndef MANGLE_SOUND_OGRELISTENERMOVER_H +#define MANGLE_SOUND_OGRELISTENERMOVER_H + +#include +#include +#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 diff --git a/libs/mangle/sound/clients/ogre_output_updater.hpp b/libs/mangle/sound/clients/ogre_output_updater.hpp new file mode 100644 index 0000000000..b73168c759 --- /dev/null +++ b/libs/mangle/sound/clients/ogre_output_updater.hpp @@ -0,0 +1,31 @@ +#ifndef MANGLE_SOUND_OGREUPDATER_H +#define MANGLE_SOUND_OGREUPDATER_H + +/* + This Ogre FrameListener calls update on a SoundFactory + */ + +#include +#include "../output.hpp" +#include + +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 diff --git a/libs/mangle/sound/filters/input_filter.hpp b/libs/mangle/sound/filters/input_filter.hpp new file mode 100644 index 0000000000..00ee187660 --- /dev/null +++ b/libs/mangle/sound/filters/input_filter.hpp @@ -0,0 +1,68 @@ +#ifndef MANGLE_INPUT_FILTER_H +#define MANGLE_INPUT_FILTER_H + +#include "../output.hpp" + +#include + +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 diff --git a/libs/mangle/sound/filters/openal_audiere.hpp b/libs/mangle/sound/filters/openal_audiere.hpp new file mode 100644 index 0000000000..5b9b518249 --- /dev/null +++ b/libs/mangle/sound/filters/openal_audiere.hpp @@ -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 diff --git a/libs/mangle/sound/filters/openal_ffmpeg.hpp b/libs/mangle/sound/filters/openal_ffmpeg.hpp new file mode 100644 index 0000000000..42c76af0cd --- /dev/null +++ b/libs/mangle/sound/filters/openal_ffmpeg.hpp @@ -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 diff --git a/libs/mangle/sound/filters/openal_mpg123.hpp b/libs/mangle/sound/filters/openal_mpg123.hpp new file mode 100644 index 0000000000..bfd926c0bb --- /dev/null +++ b/libs/mangle/sound/filters/openal_mpg123.hpp @@ -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 diff --git a/libs/mangle/sound/filters/openal_sndfile.hpp b/libs/mangle/sound/filters/openal_sndfile.hpp new file mode 100644 index 0000000000..fd7e780259 --- /dev/null +++ b/libs/mangle/sound/filters/openal_sndfile.hpp @@ -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 diff --git a/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp b/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp new file mode 100644 index 0000000000..6e5db4d0e1 --- /dev/null +++ b/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp @@ -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 diff --git a/libs/mangle/sound/filters/openal_various.hpp b/libs/mangle/sound/filters/openal_various.hpp new file mode 100644 index 0000000000..945b3dabda --- /dev/null +++ b/libs/mangle/sound/filters/openal_various.hpp @@ -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 diff --git a/libs/mangle/sound/filters/pure_filter.hpp b/libs/mangle/sound/filters/pure_filter.hpp new file mode 100644 index 0000000000..1e8b9f92c2 --- /dev/null +++ b/libs/mangle/sound/filters/pure_filter.hpp @@ -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 diff --git a/libs/mangle/sound/filters/source_splicer.hpp b/libs/mangle/sound/filters/source_splicer.hpp new file mode 100644 index 0000000000..9c76230865 --- /dev/null +++ b/libs/mangle/sound/filters/source_splicer.hpp @@ -0,0 +1,90 @@ +#ifndef MANGLE_SOUND_SOURCE_SPLICE_H +#define MANGLE_SOUND_SOURCE_SPLICE_H + +#include "../source.hpp" +#include +#include +#include +#include + +namespace Mangle +{ + namespace Sound + { + class SourceSplicer : public SampleSourceLoader + { + struct SourceType + { + std::string type; + SampleSourceLoaderPtr loader; + }; + + typedef std::list 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; iloader->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 diff --git a/libs/mangle/sound/output.hpp b/libs/mangle/sound/output.hpp new file mode 100644 index 0000000000..9bbdebb2c0 --- /dev/null +++ b/libs/mangle/sound/output.hpp @@ -0,0 +1,180 @@ +#ifndef MANGLE_SOUND_OUTPUT_H +#define MANGLE_SOUND_OUTPUT_H + +#include +#include + +#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 SoundPtr; +typedef boost::weak_ptr 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 SoundFactoryPtr; + +}} // Namespaces + +#endif diff --git a/libs/mangle/sound/outputs/openal_out.cpp b/libs/mangle/sound/outputs/openal_out.cpp new file mode 100644 index 0000000000..f1cf398385 --- /dev/null +++ b/libs/mangle/sound/outputs/openal_out.cpp @@ -0,0 +1,492 @@ +#include "openal_out.hpp" +#include +#include + +#include "../../stream/filters/buffer_stream.hpp" + +#ifdef _WIN32 +#include +#include +#elif defined(__APPLE__) +#include +#include +#else +#include +#include +#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; iupdate(); +} + +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; inotifyStreaming(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; + } +} diff --git a/libs/mangle/sound/outputs/openal_out.hpp b/libs/mangle/sound/outputs/openal_out.hpp new file mode 100644 index 0000000000..44d03ecf81 --- /dev/null +++ b/libs/mangle/sound/outputs/openal_out.hpp @@ -0,0 +1,44 @@ +#ifndef MANGLE_SOUND_OPENAL_OUT_H +#define MANGLE_SOUND_OPENAL_OUT_H + +#include "../output.hpp" +#include + +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 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 diff --git a/libs/mangle/sound/source.hpp b/libs/mangle/sound/source.hpp new file mode 100644 index 0000000000..fbe7cf958b --- /dev/null +++ b/libs/mangle/sound/source.hpp @@ -0,0 +1,62 @@ +#ifndef MANGLE_SOUND_SOURCE_H +#define MANGLE_SOUND_SOURCE_H + +#include +#include +#include + +#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 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 SampleSourceLoaderPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/sound/sources/audiere_source.cpp b/libs/mangle/sound/sources/audiere_source.cpp new file mode 100644 index 0000000000..faaa3c8c5b --- /dev/null +++ b/libs/mangle/sound/sources/audiere_source.cpp @@ -0,0 +1,77 @@ +#include "audiere_source.hpp" + +#include "../../stream/clients/audiere_file.hpp" + +#include + +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; +} diff --git a/libs/mangle/sound/sources/audiere_source.hpp b/libs/mangle/sound/sources/audiere_source.hpp new file mode 100644 index 0000000000..d797c55c86 --- /dev/null +++ b/libs/mangle/sound/sources/audiere_source.hpp @@ -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 +#include + +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 AudiereLoader; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/sources/ffmpeg_source.cpp b/libs/mangle/sound/sources/ffmpeg_source.cpp new file mode 100644 index 0000000000..6349be6913 --- /dev/null +++ b/libs/mangle/sound/sources/ffmpeg_source.cpp @@ -0,0 +1,189 @@ +#include "ffmpeg_source.hpp" + +#include + +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; +} diff --git a/libs/mangle/sound/sources/ffmpeg_source.hpp b/libs/mangle/sound/sources/ffmpeg_source.hpp new file mode 100644 index 0000000000..d422b98090 --- /dev/null +++ b/libs/mangle/sound/sources/ffmpeg_source.hpp @@ -0,0 +1,52 @@ +#ifndef MANGLE_SOUND_FFMPEG_H +#define MANGLE_SOUND_FFMPEG_H + +#include "../source.hpp" +#include +#include + +extern "C" +{ +#include +#include +} + +namespace Mangle { +namespace Sound { + +class FFMpegSource : public SampleSource +{ + AVFormatContext *FmtCtx; + AVCodecContext *CodecCtx; + unsigned int StreamNum; + + std::vector 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 +{ + 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 diff --git a/libs/mangle/sound/sources/libsndfile.cpp b/libs/mangle/sound/sources/libsndfile.cpp new file mode 100644 index 0000000000..b69a2d4368 --- /dev/null +++ b/libs/mangle/sound/sources/libsndfile.cpp @@ -0,0 +1,48 @@ +#include "libsndfile.hpp" + +#include +#include + +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); +} diff --git a/libs/mangle/sound/sources/libsndfile.hpp b/libs/mangle/sound/sources/libsndfile.hpp new file mode 100644 index 0000000000..7286cf0fe4 --- /dev/null +++ b/libs/mangle/sound/sources/libsndfile.hpp @@ -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 SndFileLoader; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/sources/loadertemplate.hpp b/libs/mangle/sound/sources/loadertemplate.hpp new file mode 100644 index 0000000000..a27a77d106 --- /dev/null +++ b/libs/mangle/sound/sources/loadertemplate.hpp @@ -0,0 +1,28 @@ +#ifndef SSL_TEMPL_H +#define SSL_TEMPL_H + +template +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 diff --git a/libs/mangle/sound/sources/mpg123_source.cpp b/libs/mangle/sound/sources/mpg123_source.cpp new file mode 100644 index 0000000000..24d6ecce1c --- /dev/null +++ b/libs/mangle/sound/sources/mpg123_source.cpp @@ -0,0 +1,115 @@ +#include "mpg123_source.hpp" + +#include + +#include + +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); +} diff --git a/libs/mangle/sound/sources/mpg123_source.hpp b/libs/mangle/sound/sources/mpg123_source.hpp new file mode 100644 index 0000000000..1ac16b5306 --- /dev/null +++ b/libs/mangle/sound/sources/mpg123_source.hpp @@ -0,0 +1,47 @@ +#ifndef MANGLE_SOUND_MPG123_SOURCE_H +#define MANGLE_SOUND_MPG123_SOURCE_H + +#include "../source.hpp" +#include + +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 +{ + /** 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 diff --git a/libs/mangle/sound/sources/sample_reader.cpp b/libs/mangle/sound/sources/sample_reader.cpp new file mode 100644 index 0000000000..c30de654a5 --- /dev/null +++ b/libs/mangle/sound/sources/sample_reader.cpp @@ -0,0 +1,99 @@ +#include "sample_reader.hpp" + +#include + +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; +} diff --git a/libs/mangle/sound/sources/sample_reader.hpp b/libs/mangle/sound/sources/sample_reader.hpp new file mode 100644 index 0000000000..89ddf1f652 --- /dev/null +++ b/libs/mangle/sound/sources/sample_reader.hpp @@ -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 diff --git a/libs/mangle/sound/sources/stream_source.hpp b/libs/mangle/sound/sources/stream_source.hpp new file mode 100644 index 0000000000..43c605a004 --- /dev/null +++ b/libs/mangle/sound/sources/stream_source.hpp @@ -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 diff --git a/libs/mangle/sound/sources/wav_source.cpp b/libs/mangle/sound/sources/wav_source.cpp new file mode 100644 index 0000000000..a46b3d27ec --- /dev/null +++ b/libs/mangle/sound/sources/wav_source.cpp @@ -0,0 +1,99 @@ +#include "wav_source.hpp" + +#include "../../stream/servers/file_stream.hpp" + +#include + +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))); } diff --git a/libs/mangle/sound/sources/wav_source.hpp b/libs/mangle/sound/sources/wav_source.hpp new file mode 100644 index 0000000000..227f4da733 --- /dev/null +++ b/libs/mangle/sound/sources/wav_source.hpp @@ -0,0 +1,49 @@ +#ifndef MANGLE_SOUND_WAV_SOURCE_H +#define MANGLE_SOUND_WAV_SOURCE_H + +#include "../source.hpp" +#include + +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 WavLoader; + +}} // Namespace +#endif diff --git a/libs/mangle/sound/tests/.gitignore b/libs/mangle/sound/tests/.gitignore new file mode 100644 index 0000000000..8144904045 --- /dev/null +++ b/libs/mangle/sound/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/sound/tests/Makefile b/libs/mangle/sound/tests/Makefile new file mode 100644 index 0000000000..6fcac72da7 --- /dev/null +++ b/libs/mangle/sound/tests/Makefile @@ -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 diff --git a/libs/mangle/sound/tests/audiere_source_test.cpp b/libs/mangle/sound/tests/audiere_source_test.cpp new file mode 100644 index 0000000000..637d743b21 --- /dev/null +++ b/libs/mangle/sound/tests/audiere_source_test.cpp @@ -0,0 +1,68 @@ +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../sources/audiere_source.hpp" + +#include +#include + +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; +} diff --git a/libs/mangle/sound/tests/cow.raw b/libs/mangle/sound/tests/cow.raw new file mode 100644 index 0000000000..c4d155bbfb Binary files /dev/null and b/libs/mangle/sound/tests/cow.raw differ diff --git a/libs/mangle/sound/tests/cow.wav b/libs/mangle/sound/tests/cow.wav new file mode 100644 index 0000000000..494e6c4ac1 Binary files /dev/null and b/libs/mangle/sound/tests/cow.wav differ diff --git a/libs/mangle/sound/tests/ffmpeg_source_test.cpp b/libs/mangle/sound/tests/ffmpeg_source_test.cpp new file mode 100644 index 0000000000..f03b15b996 --- /dev/null +++ b/libs/mangle/sound/tests/ffmpeg_source_test.cpp @@ -0,0 +1,62 @@ +#include + +#include "../../stream/servers/file_stream.hpp" +#include "../sources/ffmpeg_source.hpp" + +#include +#include + +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; +} diff --git a/libs/mangle/sound/tests/openal_audiere_test.cpp b/libs/mangle/sound/tests/openal_audiere_test.cpp new file mode 100644 index 0000000000..ced7fe5d23 --- /dev/null +++ b/libs/mangle/sound/tests/openal_audiere_test.cpp @@ -0,0 +1,52 @@ +#include +#include + +#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; +} diff --git a/libs/mangle/sound/tests/openal_ffmpeg_test.cpp b/libs/mangle/sound/tests/openal_ffmpeg_test.cpp new file mode 100644 index 0000000000..d4b8e93003 --- /dev/null +++ b/libs/mangle/sound/tests/openal_ffmpeg_test.cpp @@ -0,0 +1,52 @@ +#include +#include + +#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; +} diff --git a/libs/mangle/sound/tests/openal_mpg123_test.cpp b/libs/mangle/sound/tests/openal_mpg123_test.cpp new file mode 100644 index 0000000000..fef1a5605a --- /dev/null +++ b/libs/mangle/sound/tests/openal_mpg123_test.cpp @@ -0,0 +1,54 @@ +#include +#include + +#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; +} diff --git a/libs/mangle/sound/tests/openal_output_test.cpp b/libs/mangle/sound/tests/openal_output_test.cpp new file mode 100644 index 0000000000..a8059ec652 --- /dev/null +++ b/libs/mangle/sound/tests/openal_output_test.cpp @@ -0,0 +1,59 @@ +#include +#include + +#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; +} diff --git a/libs/mangle/sound/tests/openal_sndfile_test.cpp b/libs/mangle/sound/tests/openal_sndfile_test.cpp new file mode 100644 index 0000000000..bd5f117a59 --- /dev/null +++ b/libs/mangle/sound/tests/openal_sndfile_test.cpp @@ -0,0 +1,52 @@ +#include +#include + +#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; +} diff --git a/libs/mangle/sound/tests/openal_various_test.cpp b/libs/mangle/sound/tests/openal_various_test.cpp new file mode 100644 index 0000000000..9426a672ec --- /dev/null +++ b/libs/mangle/sound/tests/openal_various_test.cpp @@ -0,0 +1,51 @@ +#include +#include + +#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; +} diff --git a/libs/mangle/sound/tests/output/audiere_source_test.out b/libs/mangle/sound/tests/output/audiere_source_test.out new file mode 100644 index 0000000000..47a5a9e418 --- /dev/null +++ b/libs/mangle/sound/tests/output/audiere_source_test.out @@ -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 diff --git a/libs/mangle/sound/tests/output/ffmpeg_source_test.out b/libs/mangle/sound/tests/output/ffmpeg_source_test.out new file mode 100644 index 0000000000..1c7d491139 --- /dev/null +++ b/libs/mangle/sound/tests/output/ffmpeg_source_test.out @@ -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 diff --git a/libs/mangle/sound/tests/output/openal_audiere_test.out b/libs/mangle/sound/tests/output/openal_audiere_test.out new file mode 100644 index 0000000000..4fe01eac2b --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_audiere_test.out @@ -0,0 +1,3 @@ +Playing cow.wav +Playing owl.ogg +Playing cow.wav (from stream) diff --git a/libs/mangle/sound/tests/output/openal_ffmpeg_test.out b/libs/mangle/sound/tests/output/openal_ffmpeg_test.out new file mode 100644 index 0000000000..96e1db0f9a --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_ffmpeg_test.out @@ -0,0 +1,2 @@ +Playing cow.wav +Playing owl.ogg diff --git a/libs/mangle/sound/tests/output/openal_mpg123_test.out b/libs/mangle/sound/tests/output/openal_mpg123_test.out new file mode 100644 index 0000000000..e55dabbb1d --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_mpg123_test.out @@ -0,0 +1 @@ +Please specify an MP3 file diff --git a/libs/mangle/sound/tests/output/openal_output_test.out b/libs/mangle/sound/tests/output/openal_output_test.out new file mode 100644 index 0000000000..04392a72e8 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_output_test.out @@ -0,0 +1,5 @@ +Loading cow.raw + rate=11025 + channels=1 + bits=16 +Playing diff --git a/libs/mangle/sound/tests/output/openal_sndfile_test.out b/libs/mangle/sound/tests/output/openal_sndfile_test.out new file mode 100644 index 0000000000..96e1db0f9a --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_sndfile_test.out @@ -0,0 +1,2 @@ +Playing cow.wav +Playing owl.ogg diff --git a/libs/mangle/sound/tests/output/openal_various_test.out b/libs/mangle/sound/tests/output/openal_various_test.out new file mode 100644 index 0000000000..f25a555138 --- /dev/null +++ b/libs/mangle/sound/tests/output/openal_various_test.out @@ -0,0 +1 @@ +Playing cow.wav diff --git a/libs/mangle/sound/tests/output/wav_source_test.out b/libs/mangle/sound/tests/output/wav_source_test.out new file mode 100644 index 0000000000..b6fc8e6fc8 --- /dev/null +++ b/libs/mangle/sound/tests/output/wav_source_test.out @@ -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 diff --git a/libs/mangle/sound/tests/owl.ogg b/libs/mangle/sound/tests/owl.ogg new file mode 100644 index 0000000000..e992f24d48 Binary files /dev/null and b/libs/mangle/sound/tests/owl.ogg differ diff --git a/libs/mangle/sound/tests/test.sh b/libs/mangle/sound/tests/test.sh new file mode 100755 index 0000000000..2d07708adc --- /dev/null +++ b/libs/mangle/sound/tests/test.sh @@ -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 diff --git a/libs/mangle/sound/tests/wav_source_test.cpp b/libs/mangle/sound/tests/wav_source_test.cpp new file mode 100644 index 0000000000..749af18496 --- /dev/null +++ b/libs/mangle/sound/tests/wav_source_test.cpp @@ -0,0 +1,48 @@ +#include + +#include "../sources/wav_source.hpp" +#include "../../stream/servers/file_stream.hpp" + +#include +#include + +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; +} diff --git a/libs/mangle/stream/clients/audiere_file.cpp b/libs/mangle/stream/clients/audiere_file.cpp new file mode 100644 index 0000000000..16bc7891aa --- /dev/null +++ b/libs/mangle/stream/clients/audiere_file.cpp @@ -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; + +} diff --git a/libs/mangle/stream/clients/audiere_file.hpp b/libs/mangle/stream/clients/audiere_file.hpp new file mode 100644 index 0000000000..61e26f21b2 --- /dev/null +++ b/libs/mangle/stream/clients/audiere_file.hpp @@ -0,0 +1,38 @@ +#ifndef MANGLE_STREAM_AUDIERECLIENT_H +#define MANGLE_STREAM_AUDIERECLIENT_H + +#include +#include + +#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 +{ + 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 diff --git a/libs/mangle/stream/clients/io_stream.cpp b/libs/mangle/stream/clients/io_stream.cpp new file mode 100644 index 0000000000..5f1edc2217 --- /dev/null +++ b/libs/mangle/stream/clients/io_stream.cpp @@ -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; +} diff --git a/libs/mangle/stream/clients/io_stream.hpp b/libs/mangle/stream/clients/io_stream.hpp new file mode 100644 index 0000000000..98c6252ed2 --- /dev/null +++ b/libs/mangle/stream/clients/io_stream.hpp @@ -0,0 +1,43 @@ +#ifndef MANGLE_STREAM_IOSTREAM_H +#define MANGLE_STREAM_IOSTREAM_H + +#include +#include "../stream.hpp" +#include + +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 diff --git a/libs/mangle/stream/clients/ogre_datastream.hpp b/libs/mangle/stream/clients/ogre_datastream.hpp new file mode 100644 index 0000000000..76a6f20cfb --- /dev/null +++ b/libs/mangle/stream/clients/ogre_datastream.hpp @@ -0,0 +1,68 @@ +#ifndef MANGLE_STREAM_OGRECLIENT_H +#define MANGLE_STREAM_OGRECLIENT_H + +#include +#include +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** An OGRE DataStream that wraps a Mangle::Stream input. + + This has been built and tested against OGRE 1.6.2. You might have + to make your own modifications if you're working with newer (or + older) versions. + */ +class Mangle2OgreStream : public Ogre::DataStream +{ + StreamPtr inp; + + void init() + { + // Get the size, if possible + if(inp->hasSize) + mSize = inp->size(); + + // Allow writing if inp supports it + if(inp->isWritable) + mAccess |= Ogre::DataStream::WRITE; + } + + public: + /// Constructor without name + Mangle2OgreStream(StreamPtr _inp) + : inp(_inp) { init(); } + + /// Constructor for a named data stream + Mangle2OgreStream(const Ogre::String &name, StreamPtr _inp) + : Ogre::DataStream(name), inp(_inp) { init(); } + + // Only implement the DataStream functions we have to implement + + size_t read(void *buf, size_t count) + { return inp->read(buf,count); } + + size_t write(const void *buf, size_t count) + { assert(inp->isWritable); return inp->write(buf,count); } + + void skip(long count) + { + assert(inp->isSeekable && inp->hasPosition); + inp->seek(inp->tell() + count); + } + + void seek(size_t pos) + { assert(inp->isSeekable); inp->seek(pos); } + + size_t tell() const + { assert(inp->hasPosition); return inp->tell(); } + + bool eof() const { return inp->eof(); } + + /// Does nothing + void close() {} +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/filters/buffer_stream.hpp b/libs/mangle/stream/filters/buffer_stream.hpp new file mode 100644 index 0000000000..f037212a38 --- /dev/null +++ b/libs/mangle/stream/filters/buffer_stream.hpp @@ -0,0 +1,74 @@ +#ifndef MANGLE_STREAM_BUFFER_H +#define MANGLE_STREAM_BUFFER_H + +#include "../servers/memory_stream.hpp" +#include + +namespace Mangle { +namespace Stream { + +/** A Stream that reads another Stream into a buffer, and serves it as + a MemoryStream. Might be expanded with other capabilities later. + */ + +class BufferStream : public MemoryStream +{ + std::vector buffer; + + public: + /* + input = stream to copy + ADD = each read increment (for streams without size()) + */ + BufferStream(StreamPtr input, size_t ADD = 32*1024) + { + assert(input); + + // Allocate memory, read the stream into it. Then call set() + if(input->hasSize) + { + // We assume that we can get the position as well + assert(input->hasPosition); + + // Calculate how much is left of the stream + size_t left = input->size() - input->tell(); + + // Allocate the buffer and fill it + buffer.resize(left); + input->read(&buffer[0], left); + } + else + { + // We DON'T know how big the stream is. We'll have to read + // it in increments. + size_t len=0, newlen; + + while(!input->eof()) + { + // Read one block + newlen = len + ADD; + buffer.resize(newlen); + size_t read = input->read(&buffer[len], ADD); + + // Increase the total length + len += read; + + // If we read less than expected, we should be at the + // end of the stream + assert(read == ADD || (read < ADD && input->eof())); + } + + // Downsize to match the real length + buffer.resize(len); + } + + // After the buffer has been filled, set up our MemoryStream + // ancestor to reference it. + set(&buffer[0], buffer.size()); + } +}; + +typedef boost::shared_ptr BufferStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/filters/pure_filter.hpp b/libs/mangle/stream/filters/pure_filter.hpp new file mode 100644 index 0000000000..f0ce91f876 --- /dev/null +++ b/libs/mangle/stream/filters/pure_filter.hpp @@ -0,0 +1,46 @@ +#ifndef MANGLE_STREAM_FILTER_H +#define MANGLE_STREAM_FILTER_H + +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** A stream that filters another stream with no changes. Intended as + a base class for other filters. + */ +class PureFilter : public Stream +{ + protected: + StreamPtr src; + + public: + PureFilter() {} + PureFilter(StreamPtr _src) + { setStream(_src); } + + void setStream(StreamPtr _src) + { + src = _src; + isSeekable = src->isSeekable; + isWritable = src->isWritable; + hasPosition = src->hasPosition; + hasSize = src->hasSize; + hasPtr = src->hasPtr; + } + + size_t read(void *buf, size_t count) { return src->read(buf, count); } + size_t write(const void *buf, size_t count) { return src->write(buf,count); } + void flush() { src->flush(); } + void seek(size_t pos) { src->seek(pos); } + size_t tell() const { return src->tell(); } + size_t size() const { return src->size(); } + bool eof() const { return src->eof(); } + const void *getPtr() { return src->getPtr(); } + const void *getPtr(size_t size) { return src->getPtr(size); } + const void *getPtr(size_t pos, size_t size) + { return src->getPtr(pos, size); } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/filters/slice_stream.hpp b/libs/mangle/stream/filters/slice_stream.hpp new file mode 100644 index 0000000000..6337b9d57d --- /dev/null +++ b/libs/mangle/stream/filters/slice_stream.hpp @@ -0,0 +1,101 @@ +#ifndef MANGLE_STREAM_SLICE_H +#define MANGLE_STREAM_SLICE_H + +#include "../stream.hpp" + +namespace Mangle { +namespace Stream { + +/** A Stream that represents a subset (called a slice) of another stream. + */ +class SliceStream : public Stream +{ + StreamPtr src; + size_t offset, length, pos; + + public: + SliceStream(StreamPtr _src, size_t _offset, size_t _length) + : src(_src), offset(_offset), length(_length), pos(0) + { + assert(src->hasSize); + assert(src->isSeekable); + + // Make sure we can actually fit inside the source stream + assert(src->size() >= offset+length); + + isSeekable = true; + hasPosition = true; + hasSize = true; + hasPtr = src->hasPtr; + isWritable = src->isWritable; + } + + size_t read(void *buf, size_t count) + { + // Check that we're not reading past our slice + if(count > length-pos) + count = length-pos; + + // Seek into place and start reading + src->seek(offset+pos); + count = src->read(buf, count); + + pos += count; + assert(pos <= length); + return count; + } + + // Note that writing to a slice does not allow you to append data, + // you may only overwrite existing data. + size_t write(const void *buf, size_t count) + { + assert(isWritable); + // Check that we're not reading past our slice + if(count > length-pos) + count = length-pos; + + // Seek into place and action + src->seek(offset+pos); + count = src->write(buf, count); + + pos += count; + assert(pos <= length); + return count; + } + + void seek(size_t _pos) + { + pos = _pos; + if(pos > length) pos = length; + } + + bool eof() const { return pos == length; } + size_t tell() const { return pos; } + size_t size() const { return length; } + void flush() { src->flush(); } + + const void *getPtr() { return getPtr(0, length); } + const void *getPtr(size_t size) + { + const void *ptr = getPtr(pos, size); + seek(pos+size); + return ptr; + } + const void *getPtr(size_t pos, size_t size) + { + // Boundry checks on pos and size. Bounding the size is + // important even when getting pointers, as the source stream + // may use the size parameter for something (such as memory + // mapping or buffering.) + if(pos > length) pos = length; + if(pos+size > length) size = length-pos; + + // Ask the source to kindly give us a pointer + return src->getPtr(offset+pos, size); + } +}; + +typedef boost::shared_ptr SliceStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/file_stream.hpp b/libs/mangle/stream/servers/file_stream.hpp new file mode 100644 index 0000000000..314a49642b --- /dev/null +++ b/libs/mangle/stream/servers/file_stream.hpp @@ -0,0 +1,32 @@ +#ifndef MANGLE_STREAM_FILESERVER_H +#define MANGLE_STREAM_FILESERVER_H + +#include "std_stream.hpp" +#include +#include + +namespace Mangle { +namespace Stream { + +/** Very simple file input stream, based on std::ifstream + */ +class FileStream : public StdStream +{ + std::ifstream file; + + public: + FileStream(const std::string &name) + : StdStream(&file) + { + file.open(name.c_str(), std::ios::binary); + + if(file.fail()) + throw std::runtime_error("FileStream: failed to open file " + name); + } + ~FileStream() { file.close(); } +}; + +typedef boost::shared_ptr FileStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/memory_stream.hpp b/libs/mangle/stream/servers/memory_stream.hpp new file mode 100644 index 0000000000..0849e4a3cd --- /dev/null +++ b/libs/mangle/stream/servers/memory_stream.hpp @@ -0,0 +1,116 @@ +#ifndef MANGLE_STREAM_MEMSERVER_H +#define MANGLE_STREAM_MEMSERVER_H + +#include +#include "../stream.hpp" +#include + +namespace Mangle { +namespace Stream { + +// Do this before the class declaration, since the class itself +// uses it. +class MemoryStream; +typedef boost::shared_ptr MemoryStreamPtr; + +/** A Stream wrapping a memory buffer + + This will create a fully seekable stream out of any pointer/length + pair you give it. + */ +class MemoryStream : public Stream +{ + const void *data; + size_t length, pos; + + public: + MemoryStream(const void *ptr, size_t len) + : data(ptr), length(len), pos(0) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + hasPtr = true; + } + + MemoryStream() + : data(NULL), length(0), pos(0) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + hasPtr = true; + } + + size_t read(void *buf, size_t count) + { + assert(data != NULL); + assert(pos <= length); + + // Don't read more than we have + if(count > (length - pos)) + count = length - pos; + + // Copy data + if(count) + memcpy(buf, ((char*)data)+pos, count); + + // aaand remember to increase the count + pos += count; + + return count; + } + + void seek(size_t _pos) + { + pos = _pos; + if(pos > length) + pos = length; + } + + size_t tell() const { return pos; } + size_t size() const { return length; } + bool eof() const { return pos == length; } + + const void *getPtr() { return data; } + const void *getPtr(size_t size) + { + // This variant of getPtr must move the position pointer + size_t opos = pos; + pos += size; + if(pos > length) pos = length; + return ((char*)data)+opos; + } + const void *getPtr(size_t pos, size_t size) + { + if(pos > length) pos = length; + return ((char*)data)+pos; + } + + // New members in MemoryStream: + + /// Set a new buffer and length. This will rewind the position to zero. + void set(const void* _data, size_t _length) + { + data = _data; + length = _length; + pos = 0; + } + + /// Clone this memory stream + /** Make a new stream of the same buffer. If setPos is true, we also + set the clone's position to be the same as ours. + + No memory is copied during this operation, the new stream is + just another 'view' into the same shared memory buffer. + */ + MemoryStreamPtr clone(bool setPos=false) const + { + MemoryStreamPtr res(new MemoryStream(data, length)); + if(setPos) res->seek(pos); + return res; + } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/ogre_datastream.hpp b/libs/mangle/stream/servers/ogre_datastream.hpp new file mode 100644 index 0000000000..a5be98c84a --- /dev/null +++ b/libs/mangle/stream/servers/ogre_datastream.hpp @@ -0,0 +1,37 @@ +#ifndef MANGLE_STREAM_OGRESERVER_H +#define MANGLE_STREAM_OGRESERVER_H + +#include + +namespace Mangle { +namespace Stream { + +/** A Stream wrapping an OGRE DataStream. + + This has been built and tested against OGRE 1.6.2. You might have + to make your own modifications if you're working with newer (or + older) versions. + */ +class OgreStream : public Stream +{ + Ogre::DataStreamPtr inp; + + public: + OgreStream(Ogre::DataStreamPtr _inp) : inp(_inp) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + } + + size_t read(void *buf, size_t count) { return inp->read(buf,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(); } +}; + +typedef boost::shared_ptr OgreStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/outfile_stream.hpp b/libs/mangle/stream/servers/outfile_stream.hpp new file mode 100644 index 0000000000..8d953d9041 --- /dev/null +++ b/libs/mangle/stream/servers/outfile_stream.hpp @@ -0,0 +1,41 @@ +#ifndef MANGLE_OSTREAM_FILESERVER_H +#define MANGLE_OSTREAM_FILESERVER_H + +#include "std_ostream.hpp" +#include + +namespace Mangle { +namespace Stream { + +/** File stream based on std::ofstream, only supports writing. + */ +class OutFileStream : public StdOStream +{ + std::ofstream file; + + public: + /** + By default we overwrite the file. If append=true, then we will + open an existing file and seek to the end instead. + */ + OutFileStream(const std::string &name, bool append=false) + : StdOStream(&file) + { + std::ios::openmode mode = std::ios::binary; + if(append) + mode |= std::ios::app; + else + mode |= std::ios::trunc; + + file.open(name.c_str(), mode); + + if(file.fail()) + throw std::runtime_error("OutFileStream: failed to open file " + name); + } + ~OutFileStream() { file.close(); } +}; + +typedef boost::shared_ptr OutFileStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/phys_stream.hpp b/libs/mangle/stream/servers/phys_stream.hpp new file mode 100644 index 0000000000..4312ac0416 --- /dev/null +++ b/libs/mangle/stream/servers/phys_stream.hpp @@ -0,0 +1,36 @@ +#ifndef MANGLE_STREAM_OGRESERVER_H +#define MANGLE_STREAM_OGRESERVER_H + +#include + +namespace Mangle { +namespace Stream { + +/// A Stream wrapping a PHYSFS_file stream from the PhysFS library. +class PhysFile : public Stream +{ + PHYSFS_file *file; + + public: + PhysFile(PHYSFS_file *inp) : file(inp) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + } + + ~PhysFile() { PHYSFS_close(file); } + + size_t read(void *buf, size_t count) + { return PHYSFS_read(file, buf, 1, count); } + + void seek(size_t pos) { PHYSFS_seek(file, pos); } + size_t tell() const { return PHYSFS_tell(file); } + size_t size() const { return PHYSFS_fileLength(file); } + bool eof() const { return PHYSFS_eof(file); } +}; + +typedef boost::shared_ptr PhysFilePtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/std_ostream.hpp b/libs/mangle/stream/servers/std_ostream.hpp new file mode 100644 index 0000000000..f406e1a934 --- /dev/null +++ b/libs/mangle/stream/servers/std_ostream.hpp @@ -0,0 +1,78 @@ +#ifndef MANGLE_OSTREAM_STDIOSERVER_H +#define MANGLE_OSTREAM_STDIOSERVER_H + +#include "../stream.hpp" +#include +#include + +namespace Mangle { +namespace Stream { + +/** Simple wrapper for std::ostream, only supports output. + */ +class StdOStream : public Stream +{ + std::ostream *inf; + + static void fail(const std::string &msg) + { throw std::runtime_error("StdOStream: " + msg); } + + public: + StdOStream(std::ostream *_inf) + : inf(_inf) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + isWritable = true; + isReadable = false; + } + + size_t write(const void* buf, size_t len) + { + inf->write((const char*)buf, len); + if(inf->fail()) + fail("error writing to stream"); + // Just return len, but that is ok. The only cases where we would + // return less than len is when an error occured. + return len; + } + + void flush() + { + inf->flush(); + } + + void seek(size_t pos) + { + inf->seekp(pos); + if(inf->fail()) + fail("seek error"); + } + + size_t tell() const + // Hack around the fact that ifstream->tellp() isn't const + { return ((StdOStream*)this)->inf->tellp(); } + + size_t size() const + { + // Use the standard iostream size hack, terrible as it is. + std::streampos pos = inf->tellp(); + inf->seekp(0, std::ios::end); + size_t res = inf->tellp(); + inf->seekp(pos); + + if(inf->fail()) + fail("could not get stream size"); + + return res; + } + + bool eof() const + { return inf->eof(); } +}; + +typedef boost::shared_ptr StdOStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/servers/std_stream.hpp b/libs/mangle/stream/servers/std_stream.hpp new file mode 100644 index 0000000000..163f023f60 --- /dev/null +++ b/libs/mangle/stream/servers/std_stream.hpp @@ -0,0 +1,70 @@ +#ifndef MANGLE_STREAM_STDIOSERVER_H +#define MANGLE_STREAM_STDIOSERVER_H + +#include "../stream.hpp" +#include +#include + +namespace Mangle { +namespace Stream { + +/** Simple wrapper for std::istream. + */ +class StdStream : public Stream +{ + std::istream *inf; + + static void fail(const std::string &msg) + { throw std::runtime_error("StdStream: " + msg); } + + public: + StdStream(std::istream *_inf) + : inf(_inf) + { + isSeekable = true; + hasPosition = true; + hasSize = true; + } + + size_t read(void* buf, size_t len) + { + inf->read((char*)buf, len); + if(inf->bad()) + fail("error reading from stream"); + return inf->gcount(); + } + + void seek(size_t pos) + { + inf->clear(); + inf->seekg(pos); + if(inf->fail()) + fail("seek error"); + } + + size_t tell() const + // Hack around the fact that ifstream->tellg() isn't const + { return ((StdStream*)this)->inf->tellg(); } + + size_t size() const + { + // Use the standard iostream size hack, terrible as it is. + std::streampos pos = inf->tellg(); + inf->seekg(0, std::ios::end); + size_t res = inf->tellg(); + inf->seekg(pos); + + if(inf->fail()) + fail("could not get stream size"); + + return res; + } + + bool eof() const + { return inf->eof(); } +}; + +typedef boost::shared_ptr StdStreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/stream.hpp b/libs/mangle/stream/stream.hpp new file mode 100644 index 0000000000..2ee4fcbd8d --- /dev/null +++ b/libs/mangle/stream/stream.hpp @@ -0,0 +1,103 @@ +#ifndef MANGLE_STREAM_INPUT_H +#define MANGLE_STREAM_INPUT_H + +#include +#include "../tools/shared_ptr.hpp" +#include + +namespace Mangle { +namespace Stream { + +/// An abstract interface for a stream data. +class Stream +{ + public: + // Feature options. These should be set in the constructor. + + /// If true, seek() works + bool isSeekable; + + /// If true, tell() works + bool hasPosition; + + /// If true, size() works + bool hasSize; + + /// If true, write() works. Writing through pointer operations is + /// not (yet) supported. + bool isWritable; + + /// If true, read() and eof() works. + bool isReadable; + + /// If true, the getPtr() functions work + bool hasPtr; + + /// Initialize all bools to false by default, except isReadable. + Stream() : + isSeekable(false), hasPosition(false), hasSize(false), + isWritable(false), isReadable(true), hasPtr(false) {} + + /// Virtual destructor + virtual ~Stream() {} + + /** Read a given number of bytes from the stream. Returns the actual + number read. If the return value is less than count, then the + stream is empty or an error occured. Only required for readable + streams. + */ + virtual size_t read(void* buf, size_t count) { assert(0); return 0; } + + /** Write a given number of bytes from the stream. Semantics is + similar to read(). Only valid if isWritable is true. + + The returned value is the number of bytes written. However in + most cases, unlike for read(), a write-count less than requested + usually indicates an error. The implementation should throw such + errors as exceptions rather than expect the caller to handle + them. + + Since most implementations do NOT support writing we default to + an assert(0) here. + */ + virtual size_t write(const void *buf, size_t count) { assert(0); return 0; } + + /// Flush an output stream. Does nothing for non-writing streams. + virtual void flush() {} + + /// Seek to an absolute position in this stream. Not all streams are + /// seekable. + virtual void seek(size_t pos) { assert(0); } + + /// Get the current position in the stream. Non-seekable streams are + /// not required to keep track of this. + virtual size_t tell() const { assert(0); return 0; } + + /// Return the total size of the stream. For streams hasSize is + /// false, size() should fail in some way, since it is an error to + /// call it in those cases. + virtual size_t size() const { assert(0); return 0; } + + /// Returns true if the stream is empty. Required for readable + /// streams. + virtual bool eof() const { assert(0); return 0; } + + /// Return a pointer to the entire stream. This function (and the + /// other getPtr() variants below) should only be implemented for + /// memory-based streams where using them would be an optimization. + virtual const void *getPtr() { assert(0); return NULL; } + + /// Get a pointer to a memory region of 'size' bytes starting from + /// position 'pos' + virtual const void *getPtr(size_t pos, size_t size) { assert(0); return NULL; } + + /// Get a pointer to a memory region of 'size' bytes from the + /// current position. Unlike the two other getPtr variants, this + /// will advance the position past the returned area. + virtual const void *getPtr(size_t size) { assert(0); return NULL; } +}; + +typedef boost::shared_ptr StreamPtr; + +}} // namespaces +#endif diff --git a/libs/mangle/stream/tests/.gitignore b/libs/mangle/stream/tests/.gitignore new file mode 100644 index 0000000000..9dfd618e2c --- /dev/null +++ b/libs/mangle/stream/tests/.gitignore @@ -0,0 +1,2 @@ +*_test +test.file diff --git a/libs/mangle/stream/tests/Makefile b/libs/mangle/stream/tests/Makefile new file mode 100644 index 0000000000..5f43978190 --- /dev/null +++ b/libs/mangle/stream/tests/Makefile @@ -0,0 +1,34 @@ +GCC=g++ -I../ -Wall -Werror + +all: ogre_client_test audiere_client_test memory_server_test buffer_filter_test file_server_test slice_filter_test file_write_test iostream_test + +I_OGRE=$(shell pkg-config --cflags OGRE) +L_OGRE=$(shell pkg-config --libs OGRE) +L_AUDIERE=-laudiere + +ogre_client_test: ogre_client_test.cpp ../stream.hpp ../clients/ogre_datastream.hpp + $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) + +audiere_client_test: audiere_client_test.cpp ../stream.hpp ../clients/audiere_file.hpp ../clients/audiere_file.cpp + $(GCC) $< -o $@ ../clients/audiere_file.cpp $(L_AUDIERE) + +iostream_test: iostream_test.cpp ../clients/io_stream.cpp + $(GCC) $^ -o $@ + +file_server_test: file_server_test.cpp ../stream.hpp ../servers/file_stream.hpp ../servers/std_stream.hpp + $(GCC) $< -o $@ + +file_write_test: file_write_test.cpp ../stream.hpp ../servers/outfile_stream.hpp ../servers/std_ostream.hpp + $(GCC) $< -o $@ + +memory_server_test: memory_server_test.cpp ../stream.hpp ../servers/memory_stream.hpp + $(GCC) $< -o $@ + +buffer_filter_test: buffer_filter_test.cpp ../stream.hpp ../servers/memory_stream.hpp ../filters/buffer_stream.hpp + $(GCC) $< -o $@ + +slice_filter_test: slice_filter_test.cpp ../stream.hpp ../servers/memory_stream.hpp ../filters/slice_stream.hpp + $(GCC) $< -o $@ + +clean: + rm *_test diff --git a/libs/mangle/stream/tests/audiere_client_test.cpp b/libs/mangle/stream/tests/audiere_client_test.cpp new file mode 100644 index 0000000000..82be569cc3 --- /dev/null +++ b/libs/mangle/stream/tests/audiere_client_test.cpp @@ -0,0 +1,34 @@ +#include "../servers/memory_stream.hpp" +#include "../clients/audiere_file.hpp" +#include +#include + +using namespace Mangle::Stream; +using namespace audiere; +using namespace std; + +int main() +{ + char str[12]; + memset(str, 0, 12); + StreamPtr inp(new MemoryStream("hello world", 11)); + FilePtr p(new AudiereFile(inp)); + cout << "pos=" << p->tell() << endl; + p->read(str, 2); + cout << "2 bytes: " << str << endl; + cout << "pos=" << p->tell() << endl; + p->seek(4, File::BEGIN); + cout << "pos=" << p->tell() << endl; + p->read(str, 3); + cout << "3 bytes: " << str << endl; + p->seek(-1, File::CURRENT); + cout << "pos=" << p->tell() << endl; + p->seek(-4, File::END); + cout << "pos=" << p->tell() << endl; + p->read(str, 4); + cout << "last 4 bytes: " << str << endl; + p->seek(0, File::BEGIN); + p->read(str, 11); + cout << "entire stream: " << str << endl; + return 0; +} diff --git a/libs/mangle/stream/tests/buffer_filter_test.cpp b/libs/mangle/stream/tests/buffer_filter_test.cpp new file mode 100644 index 0000000000..e53bda6510 --- /dev/null +++ b/libs/mangle/stream/tests/buffer_filter_test.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include "../filters/buffer_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +int main() +{ + StreamPtr orig (new MemoryStream("hello world", 11)); + StreamPtr inp (new BufferStream(orig)); + + cout << "Size: " << inp->size() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking...\n"; + inp->seek(3); + cout << "Pos: " << inp->tell() << endl; + char data[12]; + memset(data, 0, 12); + cout << "Reading: " << inp->read(data, 4) << endl; + cout << "Four bytes: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking again...\n"; + inp->seek(33); + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << "\nSeek to 6\n"; + inp->seek(6); + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Over-reading: " << inp->read(data, 200) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + inp->seek(0); + cout << "Finally, reading the entire string: " << inp->read(data,11) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + + return 0; +} diff --git a/libs/mangle/stream/tests/file_server_test.cpp b/libs/mangle/stream/tests/file_server_test.cpp new file mode 100644 index 0000000000..3e1e3cfa57 --- /dev/null +++ b/libs/mangle/stream/tests/file_server_test.cpp @@ -0,0 +1,18 @@ +#include "../servers/file_stream.hpp" +#include + +using namespace Mangle::Stream; +using namespace std; + +int main() +{ + StreamPtr inp(new FileStream("file_server_test.cpp")); + + char buf[21]; + buf[20] = 0; + cout << "pos=" << inp->tell() << " eof=" << inp->eof() << endl; + inp->read(buf, 20); + cout << "First 20 bytes: " << buf << endl; + cout << "pos=" << inp->tell() << " eof=" << inp->eof() << endl; + return 0; +} diff --git a/libs/mangle/stream/tests/file_write_test.cpp b/libs/mangle/stream/tests/file_write_test.cpp new file mode 100644 index 0000000000..c6c61fccae --- /dev/null +++ b/libs/mangle/stream/tests/file_write_test.cpp @@ -0,0 +1,41 @@ +#include "../servers/outfile_stream.hpp" +#include + +using namespace Mangle::Stream; +using namespace std; + +void print(Stream &str) +{ + cout << "size=" << str.size() + << " pos=" << str.tell() + << " eof=" << str.eof() + << endl; +} + +int main() +{ + { + cout << "\nCreating file\n"; + OutFileStream out("test.file"); + print(out); + out.write("hello",5); + print(out); + } + + { + cout << "\nAppending to file\n"; + OutFileStream out("test.file", true); + print(out); + out.write(" again\n",7); + print(out); + } + + { + cout << "\nOverwriting file\n"; + OutFileStream out("test.file"); + print(out); + out.write("overwrite!\n",11); + print(out); + } + return 0; +} diff --git a/libs/mangle/stream/tests/iostream_test.cpp b/libs/mangle/stream/tests/iostream_test.cpp new file mode 100644 index 0000000000..60648c6c5f --- /dev/null +++ b/libs/mangle/stream/tests/iostream_test.cpp @@ -0,0 +1,176 @@ +#include +#include "../clients/io_stream.hpp" +#include "../servers/memory_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +void test1() +{ + cout << "Testing ASCII reading from memory:\n"; + StreamPtr input(new MemoryStream("hello you world you", 19)); + MangleIStream inp(input); + + string str; + while(!inp.eof()) + { + inp >> str; + cout << "Got: " << str << endl; + } +} + +class Dummy : public Stream +{ + int count; + +public: + + Dummy() : count(0) + { + } + + size_t read(void *ptr, size_t num) + { + char *p = (char*)ptr; + char *start = p; + for(; (count < 2560) && (p-start < (int)num); count++) + { + *p = count / 10; + p++; + } + return p-start; + } + + bool eof() const { return count == 2560; } +}; + +void test2() +{ + cout << "\nTesting binary reading from non-memory:\n"; + + StreamPtr input(new Dummy); + MangleIStream inp(input); + + int x = 0; + while(!inp.eof()) + { + unsigned char buf[5]; + inp.read((char*)buf,5); + + // istream doesn't set eof() until we read _beyond_ the end of + // the stream, so we need an extra check. + if(inp.gcount() == 0) break; + + /* + for(int i=0;i<5;i++) + cout << (int)buf[i] << " "; + cout << endl; + */ + + assert(buf[4] == buf[0]); + assert(buf[0] == x/2); + x++; + } + cout << " Done\n"; +} + +struct Dummy2 : Stream +{ + Dummy2() + { + isWritable = true; + isReadable = false; + } + + size_t write(const void *ptr, size_t num) + { + const char *p = (const char*)ptr; + cout << " Got: "; + for(unsigned i=0;iwrite("testing", 7); + + cout << " Running through MangleOStream:\n"; + MangleOStream out(output); + out << "hello"; + out << " - are you ok?"; + cout << " Flushing:\n"; + out.flush(); + + cout << " Writing a hell of a lot of characters:\n"; + for(int i=0; i<127; i++) + out << "xxxxxxxx"; // 127 * 8 = 1016 + out << "fffffff"; // +7 = 1023 + cout << " Just one more:\n"; + out << "y"; + cout << " And oooone more:\n"; + out << "z"; + + cout << " Flushing again:\n"; + out.flush(); + cout << " Writing some more and exiting:\n"; + out << "blah bleh blob"; +} + +struct Dummy3 : Stream +{ + int pos; + + Dummy3() : pos(0) + { + hasPosition = true; + isSeekable = true; + } + + size_t read(void*, size_t num) + { + cout << " Reading " << num << " bytes from " << pos << endl; + pos += num; + return num; + } + + void seek(size_t npos) { pos = npos; } + size_t tell() const { return pos; } +}; + +void test4() +{ + cout << "\nTesting seeking;\n"; + StreamPtr input(new Dummy3); + + cout << " Direct reading:\n"; + input->read(0,10); + input->read(0,5); + + MangleIStream inp(input); + + cout << " Reading from istream:\n"; + char buf[20]; + inp.read(buf, 20); + inp.read(buf, 20); + inp.read(buf, 20); + + cout << " Seeking to 30 and reading again:\n"; + inp.seekg(30); + inp.read(buf, 20); +} + +int main() +{ + test1(); + test2(); + test3(); + test4(); + return 0; +} diff --git a/libs/mangle/stream/tests/memory_server_test.cpp b/libs/mangle/stream/tests/memory_server_test.cpp new file mode 100644 index 0000000000..24d3bb17e0 --- /dev/null +++ b/libs/mangle/stream/tests/memory_server_test.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include "../servers/memory_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +int main() +{ + Stream* inp = new MemoryStream("hello world\0", 12); + + cout << "Size: " << inp->size() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking...\n"; + inp->seek(3); + cout << "Pos: " << inp->tell() << endl; + char data[12]; + memset(data, 0, 12); + cout << "Reading: " << inp->read(data, 4) << endl; + cout << "Four bytes: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking again...\n"; + inp->seek(33); + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << "\nSeek to 6\n"; + inp->seek(6); + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Over-reading: " << inp->read(data, 200) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + inp->seek(0); + cout << "Finally, reading the entire string: " << inp->read(data,11) << endl; + cout << "Result: " << data << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Pos: " << inp->tell() << endl; + + cout << "Entire stream from pointer: " << (char*)inp->getPtr() << endl; + + return 0; +} diff --git a/libs/mangle/stream/tests/ogre_client_test.cpp b/libs/mangle/stream/tests/ogre_client_test.cpp new file mode 100644 index 0000000000..c8d0442c01 --- /dev/null +++ b/libs/mangle/stream/tests/ogre_client_test.cpp @@ -0,0 +1,22 @@ +#include "../servers/memory_stream.hpp" +#include "../clients/ogre_datastream.hpp" +#include + +using namespace Mangle::Stream; +using namespace Ogre; +using namespace std; + +int main() +{ + StreamPtr inp(new MemoryStream("hello world", 11)); + DataStreamPtr p(new Mangle2OgreStream("hello", inp)); + cout << "Name: " << p->getName() << endl; + cout << "As string: " << p->getAsString() << endl; + cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; + p->seek(0); + cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; + p->skip(5); + p->skip(-2); + cout << "pos=" << p->tell() << " eof=" << p->eof() << endl; + return 0; +} diff --git a/libs/mangle/stream/tests/output/audiere_client_test.out b/libs/mangle/stream/tests/output/audiere_client_test.out new file mode 100644 index 0000000000..2130db9fd0 --- /dev/null +++ b/libs/mangle/stream/tests/output/audiere_client_test.out @@ -0,0 +1,9 @@ +pos=0 +2 bytes: he +pos=2 +pos=4 +3 bytes: o w +pos=6 +pos=7 +last 4 bytes: orld +entire stream: hello world diff --git a/libs/mangle/stream/tests/output/buffer_filter_test.out b/libs/mangle/stream/tests/output/buffer_filter_test.out new file mode 100644 index 0000000000..0ca252f25e --- /dev/null +++ b/libs/mangle/stream/tests/output/buffer_filter_test.out @@ -0,0 +1,22 @@ +Size: 11 +Pos: 0 +Seeking... +Pos: 3 +Reading: 4 +Four bytes: lo w +Eof: 0 +Pos: 7 +Seeking again... +Pos: 11 +Eof: 1 +Seek to 6 +Eof: 0 +Pos: 6 +Over-reading: 5 +Result: world +Eof: 1 +Pos: 11 +Finally, reading the entire string: 11 +Result: hello world +Eof: 1 +Pos: 11 diff --git a/libs/mangle/stream/tests/output/file_server_test.out b/libs/mangle/stream/tests/output/file_server_test.out new file mode 100644 index 0000000000..240f066a31 --- /dev/null +++ b/libs/mangle/stream/tests/output/file_server_test.out @@ -0,0 +1,3 @@ +pos=0 eof=0 +First 20 bytes: #include "../servers +pos=20 eof=0 diff --git a/libs/mangle/stream/tests/output/file_write_test.out b/libs/mangle/stream/tests/output/file_write_test.out new file mode 100644 index 0000000000..34b07c49f6 --- /dev/null +++ b/libs/mangle/stream/tests/output/file_write_test.out @@ -0,0 +1,12 @@ + +Creating file +size=0 pos=0 eof=0 +size=5 pos=5 eof=0 + +Appending to file +size=5 pos=5 eof=0 +size=12 pos=12 eof=0 + +Overwriting file +size=0 pos=0 eof=0 +size=11 pos=11 eof=0 diff --git a/libs/mangle/stream/tests/output/iostream_test.out b/libs/mangle/stream/tests/output/iostream_test.out new file mode 100644 index 0000000000..b6da80c80d --- /dev/null +++ b/libs/mangle/stream/tests/output/iostream_test.out @@ -0,0 +1,32 @@ +Testing ASCII reading from memory: +Got: hello +Got: you +Got: world +Got: you + +Testing binary reading from non-memory: + Done + +Writing to dummy stream: + Pure dummy test: + Got: t e s t i n g + Running through MangleOStream: + Flushing: + Got: h e l l o - a r e y o u o k ? + Writing a hell of a lot of characters: + Just one more: + And oooone more: + Got: x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x f f f f f f f y + Flushing again: + Got: z + Writing some more and exiting: + Got: b l a h b l e h b l o b + +Testing seeking; + Direct reading: + Reading 10 bytes from 0 + Reading 5 bytes from 10 + Reading from istream: + Reading 1024 bytes from 15 + Seeking to 30 and reading again: + Reading 1024 bytes from 30 diff --git a/libs/mangle/stream/tests/output/memory_server_test.out b/libs/mangle/stream/tests/output/memory_server_test.out new file mode 100644 index 0000000000..7cd9533e78 --- /dev/null +++ b/libs/mangle/stream/tests/output/memory_server_test.out @@ -0,0 +1,23 @@ +Size: 12 +Pos: 0 +Seeking... +Pos: 3 +Reading: 4 +Four bytes: lo w +Eof: 0 +Pos: 7 +Seeking again... +Pos: 12 +Eof: 1 +Seek to 6 +Eof: 0 +Pos: 6 +Over-reading: 6 +Result: world +Eof: 1 +Pos: 12 +Finally, reading the entire string: 11 +Result: hello world +Eof: 0 +Pos: 11 +Entire stream from pointer: hello world diff --git a/libs/mangle/stream/tests/output/ogre_client_test.out b/libs/mangle/stream/tests/output/ogre_client_test.out new file mode 100644 index 0000000000..62cd14604c --- /dev/null +++ b/libs/mangle/stream/tests/output/ogre_client_test.out @@ -0,0 +1,5 @@ +Name: hello +As string: hello world +pos=11 eof=1 +pos=0 eof=0 +pos=3 eof=0 diff --git a/libs/mangle/stream/tests/output/slice_filter_test.out b/libs/mangle/stream/tests/output/slice_filter_test.out new file mode 100644 index 0000000000..6d84704a77 --- /dev/null +++ b/libs/mangle/stream/tests/output/slice_filter_test.out @@ -0,0 +1,36 @@ + +Slice 1: +-------- +Size: 6 +Pos: 0 +Seeking... +Reading 6 bytes +Result: hello +Pos: 6 +Eof: 1 +Seeking: +Pos: 2 +Eof: 0 +Reading 4 bytes +Result: llo +Pos: 6 +Eof: 1 +Entire stream as pointer: hello + +Slice 2: +-------- +Size: 6 +Pos: 0 +Seeking... +Reading 6 bytes +Result: world +Pos: 6 +Eof: 1 +Seeking: +Pos: 2 +Eof: 0 +Reading 4 bytes +Result: rld +Pos: 6 +Eof: 1 +Entire stream as pointer: world diff --git a/libs/mangle/stream/tests/slice_filter_test.cpp b/libs/mangle/stream/tests/slice_filter_test.cpp new file mode 100644 index 0000000000..da90e24ad0 --- /dev/null +++ b/libs/mangle/stream/tests/slice_filter_test.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include "../filters/slice_stream.hpp" +#include "../servers/memory_stream.hpp" + +using namespace Mangle::Stream; +using namespace std; + +void test(StreamPtr inp) +{ + cout << "Size: " << inp->size() << endl; + cout << "Pos: " << inp->tell() << "\nSeeking...\n"; + char data[6]; + memset(data, 0, 6); + cout << "Reading " << inp->read(data, 6) << " bytes\n"; + cout << "Result: " << data << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << endl; + inp->seek(2); + cout << "Seeking:\nPos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Reading " << inp->read(data, 6) << " bytes\n"; + cout << "Result: " << data << endl; + cout << "Pos: " << inp->tell() << endl; + cout << "Eof: " << inp->eof() << endl; + cout << "Entire stream as pointer: " << (char*)inp->getPtr() << endl; +} + +int main() +{ + StreamPtr orig (new MemoryStream("hello\0world\0", 12)); + StreamPtr slice1 (new SliceStream(orig,0,6)); + StreamPtr slice2 (new SliceStream(orig,6,6)); + + cout << "\nSlice 1:\n--------\n"; + test(slice1); + cout << "\nSlice 2:\n--------\n"; + test(slice2); + + return 0; +} diff --git a/libs/mangle/stream/tests/test.sh b/libs/mangle/stream/tests/test.sh new file mode 100755 index 0000000000..2d07708adc --- /dev/null +++ b/libs/mangle/stream/tests/test.sh @@ -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 diff --git a/libs/mangle/testall.sh b/libs/mangle/testall.sh new file mode 100755 index 0000000000..b93fee2159 --- /dev/null +++ b/libs/mangle/testall.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +function run() +{ + echo "TESTING $1" + cd "$1/tests/" + ./test.sh + cd ../../ +} + +run stream +run vfs +run sound +run input +run rend2d +run . diff --git a/libs/mangle/tests/.gitignore b/libs/mangle/tests/.gitignore new file mode 100644 index 0000000000..8144904045 --- /dev/null +++ b/libs/mangle/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/tests/Makefile b/libs/mangle/tests/Makefile new file mode 100644 index 0000000000..d912c0784b --- /dev/null +++ b/libs/mangle/tests/Makefile @@ -0,0 +1,14 @@ +GCC=g++ -I../ + +all: ogrevfs_audiere_openal_test + +I_OGRE=$(shell pkg-config --cflags OGRE) +L_OGRE=$(shell pkg-config --libs OGRE) +L_OPENAL=$(shell pkg-config --libs openal) +L_AUDIERE=-laudiere + +ogrevfs_audiere_openal_test: ogrevfs_audiere_openal_test.cpp ../vfs/servers/ogre_vfs.cpp ../sound/sources/audiere_source.cpp ../sound/outputs/openal_out.cpp ../stream/clients/audiere_file.cpp + $(GCC) $^ -o $@ $(I_OGRE) $(L_OGRE) $(L_OPENAL) $(L_AUDIERE) + +clean: + rm *_test diff --git a/libs/mangle/tests/ogrevfs_audiere_openal_test.cpp b/libs/mangle/tests/ogrevfs_audiere_openal_test.cpp new file mode 100644 index 0000000000..4936538c55 --- /dev/null +++ b/libs/mangle/tests/ogrevfs_audiere_openal_test.cpp @@ -0,0 +1,49 @@ +/* + This example combines: + + - the OGRE VFS system (to read from zip) + - Audiere (for decoding sound data) + - OpenAL (for sound playback) + + */ + +#include "sound/filters/openal_audiere.hpp" +#include "vfs/servers/ogre_vfs.hpp" +#include +#include + +using namespace Ogre; +using namespace Mangle; +using namespace std; + +int main() +{ + // Disable Ogre logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + // Set up Root + Root *root = new Root("","",""); + + // Add zip file with a sound in it + root->addResourceLocation("sound.zip", "Zip", "General"); + + // Ogre file system + VFS::OgreVFS vfs; + + // The main sound system + Sound::OpenAL_Audiere_Factory mg; + Sound::SoundPtr snd = mg.load(vfs.open("owl.ogg")); + + cout << "Playing 'owl.ogg' from 'sound.zip'\n"; + snd->play(); + + while(snd->isPlaying()) + { + usleep(10000); + if(mg.needsUpdate) mg.update(); + } + + return 0; +} diff --git a/libs/mangle/tests/output/ogrevfs_audiere_openal_test.out b/libs/mangle/tests/output/ogrevfs_audiere_openal_test.out new file mode 100644 index 0000000000..28ea8a71b7 --- /dev/null +++ b/libs/mangle/tests/output/ogrevfs_audiere_openal_test.out @@ -0,0 +1 @@ +Playing 'owl.ogg' from 'sound.zip' diff --git a/libs/mangle/tests/sound.zip b/libs/mangle/tests/sound.zip new file mode 100644 index 0000000000..fd32b35299 Binary files /dev/null and b/libs/mangle/tests/sound.zip differ diff --git a/libs/mangle/tests/test.sh b/libs/mangle/tests/test.sh new file mode 100755 index 0000000000..2d07708adc --- /dev/null +++ b/libs/mangle/tests/test.sh @@ -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 diff --git a/libs/mangle/tools/shared_ptr.hpp b/libs/mangle/tools/shared_ptr.hpp new file mode 100644 index 0000000000..3d073fc24f --- /dev/null +++ b/libs/mangle/tools/shared_ptr.hpp @@ -0,0 +1,3 @@ +// This file should include whatever it needs to define the boost/tr1 +// shared_ptr<> and weak_ptr<> templates. +#include diff --git a/libs/mangle/vfs/clients/ogre_archive.cpp b/libs/mangle/vfs/clients/ogre_archive.cpp new file mode 100644 index 0000000000..2d3f7c5204 --- /dev/null +++ b/libs/mangle/vfs/clients/ogre_archive.cpp @@ -0,0 +1,85 @@ +#include "ogre_archive.hpp" + +#include "../../stream/clients/ogre_datastream.hpp" + +using namespace Mangle::VFS; +using namespace Mangle::Stream; + +Ogre::DataStreamPtr MangleArchive::open(const Ogre::String& filename) const +{ + return Ogre::DataStreamPtr(new Mangle2OgreStream + (filename, vfs->open(filename))); +} + +static void fill(Ogre::FileInfoList &out, FileInfoList &in) +{ + int size = in.size(); + out.resize(size); + + for(int i=0; ihasList); + FileInfoListPtr lst = vfs->list("", recursive, dirs); + Ogre::StringVector *res = new Ogre::StringVector; + + fill(*res, *lst); + + return Ogre::StringVectorPtr(res); +} + +Ogre::FileInfoListPtr MangleArchive::listFileInfo(bool recursive, bool dirs) +{ + assert(vfs->hasList); + FileInfoListPtr lst = vfs->list("", recursive, dirs); + Ogre::FileInfoList *res = new Ogre::FileInfoList; + + fill(*res, *lst); + + return Ogre::FileInfoListPtr(res); +} + +// Find functions will only work if vfs->hasFind is set. +Ogre::StringVectorPtr MangleArchive::find(const Ogre::String& pattern, + bool recursive, + bool dirs) +{ + assert(vfs->hasFind); + FileInfoListPtr lst = vfs->find(pattern, recursive, dirs); + Ogre::StringVector *res = new Ogre::StringVector; + + fill(*res, *lst); + + return Ogre::StringVectorPtr(res); +} + +Ogre::FileInfoListPtr MangleArchive::findFileInfo(const Ogre::String& pattern, + bool recursive, + bool dirs) +{ + assert(vfs->hasFind); + FileInfoListPtr lst = vfs->find(pattern, recursive, dirs); + Ogre::FileInfoList *res = new Ogre::FileInfoList; + + fill(*res, *lst); + + return Ogre::FileInfoListPtr(res); +} diff --git a/libs/mangle/vfs/clients/ogre_archive.hpp b/libs/mangle/vfs/clients/ogre_archive.hpp new file mode 100644 index 0000000000..7b371c6efe --- /dev/null +++ b/libs/mangle/vfs/clients/ogre_archive.hpp @@ -0,0 +1,59 @@ +#ifndef MANGLE_VFS_OGRECLIENT_H +#define MANGLE_VFS_OGRECLIENT_H + +#include +#include +#include "../vfs.hpp" + +namespace Mangle { +namespace VFS { + +/** An OGRE Archive implementation that wraps a Mangle::VFS + filesystem. + + This has been built and tested against OGRE 1.6.2, and has been + extended for OGRE 1.7. You might have to make your own + modifications if you're working with newer (or older) versions. + */ +class MangleArchive : public Ogre::Archive +{ + VFSPtr vfs; + + public: + MangleArchive(VFSPtr _vfs, const std::string &name, + const std::string &archType = "Mangle") + : Ogre::Archive(name, archType) + , vfs(_vfs) + {} + + bool isCaseSensitive() const { return vfs->isCaseSensitive; } + + // These do nothing. You have to load / unload the archive in the + // constructor/destructor. + void load() {} + void unload() {} + + bool exists(const Ogre::String& filename) + { return vfs->isFile(filename); } + + time_t getModifiedTime(const Ogre::String& filename) + { return vfs->stat(filename)->time; } + + // With both these we support both OGRE 1.6 and 1.7 + Ogre::DataStreamPtr open(const Ogre::String& filename) const; + Ogre::DataStreamPtr open(const Ogre::String& filename, bool readOnly) const + { return open(filename); } + + Ogre::StringVectorPtr list(bool recursive = true, bool dirs = false); + Ogre::FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false); + + // Find functions will only work if vfs->hasFind is set. + Ogre::StringVectorPtr find(const Ogre::String& pattern, bool recursive = true, + bool dirs = false); + Ogre::FileInfoListPtr findFileInfo(const Ogre::String& pattern, + bool recursive = true, + bool dirs = false); +}; + +}} // namespaces +#endif diff --git a/libs/mangle/vfs/servers/ogre_vfs.cpp b/libs/mangle/vfs/servers/ogre_vfs.cpp new file mode 100644 index 0000000000..7f728c1b0f --- /dev/null +++ b/libs/mangle/vfs/servers/ogre_vfs.cpp @@ -0,0 +1,61 @@ +#include "ogre_vfs.hpp" +#include "../../stream/servers/ogre_datastream.hpp" + +using namespace Mangle::VFS; +using namespace Mangle::Stream; + +OgreVFS::OgreVFS(const std::string &_group) + : group(_group) +{ + hasList = true; + hasFind = true; + isCaseSensitive = true; + + // Get the group manager once + gm = Ogre::ResourceGroupManager::getSingletonPtr(); + + // Use the default group if none was specified + if(group.empty()) + group = gm->getWorldResourceGroupName(); +} + +StreamPtr OgreVFS::open(const std::string &name) +{ + Ogre::DataStreamPtr data = gm->openResource(name, group); + return StreamPtr(new OgreStream(data)); +} + +static void fill(FileInfoList &out, Ogre::FileInfoList &in, bool dirs) +{ + int size = in.size(); + out.resize(size); + + for(int i=0; ilistResourceFileInfo(group, dirs); + FileInfoListPtr res(new FileInfoList); + fill(*res, *olist, dirs); + return res; +} + +FileInfoListPtr OgreVFS::find(const std::string& pattern, + bool recursive, + bool dirs) const +{ + Ogre::FileInfoListPtr olist = gm->findResourceFileInfo(group, pattern, dirs); + FileInfoListPtr res(new FileInfoList); + fill(*res, *olist, dirs); + return res; +} diff --git a/libs/mangle/vfs/servers/ogre_vfs.hpp b/libs/mangle/vfs/servers/ogre_vfs.hpp new file mode 100644 index 0000000000..7ab64169df --- /dev/null +++ b/libs/mangle/vfs/servers/ogre_vfs.hpp @@ -0,0 +1,70 @@ +#ifndef MANGLE_VFS_OGRESERVER_H +#define MANGLE_VFS_OGRESERVER_H + +#include "../vfs.hpp" +#include + +namespace Mangle { +namespace VFS { + +/** @brief An interface into the OGRE VFS system. + + This class does NOT wrap a single Ogre::Archive, but rather an + entire resource group in Ogre. You can use this class to tap into + all paths, Zip files, custom archives on so on that have been + inserted into Ogre as resource locations. + + This has been built and tested against OGRE 1.6.2. You might have + to make your own modifications if you're working with newer (or + older) versions. + */ +class OgreVFS : public VFS +{ + std::string group; + Ogre::ResourceGroupManager *gm; + + public: + /** @brief Constructor + + OGRE must be initialized (ie. you must have created an + Ogre::Root object somewhere) before calling this. + + @param group Optional resource group name. If none is given, + OGRE's default (or 'World') resource group is used. + */ + OgreVFS(const std::string &_group = ""); + + /// Open a new data stream. Deleting the object should be enough to + /// close it. + virtual Stream::StreamPtr open(const std::string &name); + + /// Check for the existence of a file + virtual bool isFile(const std::string &name) const + { return gm->resourceExists(group, name); } + + /// This doesn't work, always returns false. + virtual bool isDir(const std::string &name) const + { return false; } + + /// This doesn't work. + virtual FileInfoPtr stat(const std::string &name) const + { return FileInfoPtr(); } + + /// List all entries in a given directory. A blank dir should be + /// interpreted as a the root/current directory of the archive. If + /// dirs is true, list directories instead of files. OGRE note: The + /// ogre resource systemd does not support recursive listing of + /// files. We might make a separate filter for this later. + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const; + + /// Find files after a given pattern. Wildcards (*) are + /// supported. + virtual FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const; +}; + +}} // namespaces +#endif diff --git a/libs/mangle/vfs/servers/physfs_vfs.hpp b/libs/mangle/vfs/servers/physfs_vfs.hpp new file mode 100644 index 0000000000..8535088e06 --- /dev/null +++ b/libs/mangle/vfs/servers/physfs_vfs.hpp @@ -0,0 +1,71 @@ +#ifndef MANGLE_VFS_PHYSFS_SERVER_H +#define MANGLE_VFS_PHYSFS_SERVER_H + +#include "../vfs.hpp" +#include "../../stream/servers/phys_stream.hpp" + +#include +#include + +namespace Mangle { +namespace VFS { + +/** @brief An interface into the PhysFS virtual file system library + + You have to set up PhysFS on your own before using this class. + */ +class PhysVFS : public VFS +{ + public: + PhysVFS() + { + hasList = true; + hasFind = false; + isCaseSensitive = true; + } + + /// Open a new data stream. Deleting the object should be enough to + /// close it. + virtual Stream::StreamPtr open(const std::string &name) + { return Stream::StreamPtr(new Stream::PhysFile(PHYSFS_openRead(name.c_str()))); } + + /// Check for the existence of a file + virtual bool isFile(const std::string &name) const + { return PHYSFS_exists(name.c_str()); } + + /// Checks for a directory + virtual bool isDir(const std::string &name) const + { return PHYSFS_isDirectory(name.c_str()); } + + /// This doesn't work + virtual FileInfoPtr stat(const std::string &name) const + { assert(0); return FileInfoPtr(); } + + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const + { + char **files = PHYSFS_enumerateFiles(dir.c_str()); + FileInfoListPtr lst(new FileInfoList); + + // Add all teh files + int i = 0; + while(files[i] != NULL) + { + FileInfo fi; + fi.name = files[i]; + fi.isDir = false; + + lst->push_back(fi); + } + return lst; + } + + virtual FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const + { assert(0); } +}; + +}} // namespaces +#endif diff --git a/libs/mangle/vfs/tests/.gitignore b/libs/mangle/vfs/tests/.gitignore new file mode 100644 index 0000000000..8144904045 --- /dev/null +++ b/libs/mangle/vfs/tests/.gitignore @@ -0,0 +1 @@ +*_test diff --git a/libs/mangle/vfs/tests/Makefile b/libs/mangle/vfs/tests/Makefile new file mode 100644 index 0000000000..a5d1d17120 --- /dev/null +++ b/libs/mangle/vfs/tests/Makefile @@ -0,0 +1,25 @@ +GCC=g++ -I../ -Wall + +all: dummy_test ogre_client_test ogre_resource_test ogre_server_test physfs_server_test + +I_OGRE=$(shell pkg-config --cflags OGRE) +L_OGRE=$(shell pkg-config --libs OGRE) +L_PHYSFS=-lphysfs + +ogre_client_test: ogre_client_test.cpp dummy_vfs.cpp ../vfs.hpp ../clients/ogre_archive.hpp ../clients/ogre_archive.cpp + $(GCC) $< ../clients/ogre_archive.cpp -o $@ $(I_OGRE) $(L_OGRE) + +ogre_resource_test: ogre_resource_test.cpp + $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) + +ogre_server_test: ogre_server_test.cpp ../vfs.hpp ../servers/ogre_vfs.hpp ../servers/ogre_vfs.cpp + $(GCC) $< -o $@ $(I_OGRE) $(L_OGRE) ../servers/ogre_vfs.cpp + +physfs_server_test: physfs_server_test.cpp ../vfs.hpp ../servers/physfs_vfs.hpp + $(GCC) $< -o $@ $(L_PHYSFS) + +dummy_test: dummy_test.cpp dummy_vfs.cpp ../vfs.hpp + $(GCC) $< -o $@ + +clean: + rm *_test diff --git a/libs/mangle/vfs/tests/dummy_test.cpp b/libs/mangle/vfs/tests/dummy_test.cpp new file mode 100644 index 0000000000..541010c6a3 --- /dev/null +++ b/libs/mangle/vfs/tests/dummy_test.cpp @@ -0,0 +1,42 @@ +#include "dummy_vfs.cpp" + +#include +#include + +using namespace std; + +void print(FileInfo &inf) +{ + cout << "name: " << inf.name << endl; + cout << "basename: " << inf.basename << endl; + cout << "isDir: " << inf.isDir << endl; + cout << "size: " << inf.size << endl; + cout << "time: " << inf.time << endl; +} +void print(FileInfoPtr inf) { print(*inf); } + +void print(FileInfoList &lst) +{ + for(unsigned i=0; isize() << endl; + + return 0; +} diff --git a/libs/mangle/vfs/tests/dummy_vfs.cpp b/libs/mangle/vfs/tests/dummy_vfs.cpp new file mode 100644 index 0000000000..0448e96a55 --- /dev/null +++ b/libs/mangle/vfs/tests/dummy_vfs.cpp @@ -0,0 +1,117 @@ +// This file is shared between several test programs +#include "vfs.hpp" +#include +#include + +#include "../../stream/servers/memory_stream.hpp" + +using namespace Mangle::VFS; +using namespace Mangle::Stream; + +class DummyVFS : public VFS +{ +public: + DummyVFS() + { + hasFind = false; + hasList = true; + isCaseSensitive = true; + } + + // We only support opening 'file1' at the moment. + StreamPtr open(const std::string &name) + { + assert(name == "file1"); + return StreamPtr(new MemoryStream("hello world", 11)); + } + + bool isFile(const std::string &name) const + { + return (name == "file1" || + name == "dir/file2"); + } + + bool isDir(const std::string &name) const + { + return name == "dir"; + } + + /// Get info about a single file + FileInfoPtr stat(const std::string &name) const + { + FileInfoPtr fi(new FileInfo); + fi->name = name; + fi->time = 0; + + if(isFile(name)) + { + if(name == "dir/file2") + { + fi->basename = "file2"; + fi->size = 2; + } + else + { + fi->basename = "file1"; + fi->size = 1; + } + fi->isDir = false; + } + else if(isDir(name)) + { + fi->basename = "dir"; + fi->isDir = true; + fi->size = 0; + } + else assert(0); + + return fi; + } + + /// List all entries in a given directory. A blank dir should be + /// interpreted as a the root/current directory of the archive. If + /// dirs is true, list directories instead of files. + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const + { + assert(dir == ""); + + FileInfoListPtr fl(new FileInfoList); + + FileInfo fi; + + if(!dirs) + { + fi.name = "file1"; + fi.basename = "file1"; + fi.isDir = false; + fi.size = 1; + fi.time = 0; + fl->push_back(fi); + + if(recurse) + { + fi.name = "dir/file2"; + fi.basename = "file2"; + fi.size = 2; + fl->push_back(fi); + } + } + else + { + fi.name = "dir"; + fi.basename = "dir"; + fi.isDir = true; + fi.size = 0; + fi.time = 0; + fl->push_back(fi); + } + return fl; + } + + FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const + { assert(0); } +}; diff --git a/libs/mangle/vfs/tests/ogre_client_test.cpp b/libs/mangle/vfs/tests/ogre_client_test.cpp new file mode 100644 index 0000000000..8e1269b56f --- /dev/null +++ b/libs/mangle/vfs/tests/ogre_client_test.cpp @@ -0,0 +1,39 @@ +#include "dummy_vfs.cpp" +#include "../clients/ogre_archive.hpp" +#include + +using namespace Ogre; +using namespace std; + +void print(StringVectorPtr lst) +{ + int s = lst->size(); + + for(int i=0; isize() << endl; + cout << "contents: " << file->getAsString() << endl; + + return 0; +} diff --git a/libs/mangle/vfs/tests/ogre_resource_test.cpp b/libs/mangle/vfs/tests/ogre_resource_test.cpp new file mode 100644 index 0000000000..8965adaa1e --- /dev/null +++ b/libs/mangle/vfs/tests/ogre_resource_test.cpp @@ -0,0 +1,61 @@ +#include +#include +#include + +/* + This isn't really a test of our implementation, but a test of using + the Ogre resource system to find files. If the Ogre interface + changes and you have to change this test, you will have to change + the servers/ogre_vfs.cpp implementation equivalently. + + */ + +using namespace std; +using namespace Ogre; + +ResourceGroupManager *gm; +String group; + +void find(const std::string &fileName) +{ + cout << "\nFile: " << fileName << endl; + + if(!gm->resourceExists(group, fileName)) + { + cout << "Does not exist\n"; + return; + } + + DataStreamPtr data = gm->openResource(fileName, group); + + cout << "Size: " << data->size() << endl; + cout << "First line: " << data->getLine() << "\n"; +} + +int main() +{ + // Disable logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + // Set up Ogre + Root root("","",""); + + root.addResourceLocation("./", "FileSystem", "General"); + + gm = ResourceGroupManager::getSingletonPtr(); + group = gm->getWorldResourceGroupName(); + + find("Makefile"); + find("ogre_resource_test.cpp"); + find("bleh"); + + cout << "\nAll source files:\n"; + FileInfoListPtr list = gm->findResourceFileInfo(group, "*.cpp"); + FileInfoList::iterator it, end; + it = list->begin(); + end = list->end(); + for(; it != end; it++) + cout << " " << it->filename << endl; +} diff --git a/libs/mangle/vfs/tests/ogre_server_test.cpp b/libs/mangle/vfs/tests/ogre_server_test.cpp new file mode 100644 index 0000000000..b846eef96e --- /dev/null +++ b/libs/mangle/vfs/tests/ogre_server_test.cpp @@ -0,0 +1,38 @@ +#include "../servers/ogre_vfs.hpp" + +#include + +#include "server_common.cpp" + +Ogre::Root *root; + +void setupOgre() +{ + using namespace Ogre; + + // Disable logging + new LogManager; + Log *log = LogManager::getSingleton().createLog(""); + log->setDebugOutputEnabled(false); + + // Set up Root + root = new Root("","",""); + + // Add a zip file and the current directory + root->addResourceLocation("test.zip", "Zip", "General"); + root->addResourceLocation("./", "FileSystem", "General"); +} + +int main() +{ + // Set up the engine + setupOgre(); + + // This is our entry point into the resource file system + OgreVFS vfs("General"); + + // Run the test + testAll(vfs); + + return 0; +} diff --git a/libs/mangle/vfs/tests/output/dummy_test.out b/libs/mangle/vfs/tests/output/dummy_test.out new file mode 100644 index 0000000000..61f1fda80a --- /dev/null +++ b/libs/mangle/vfs/tests/output/dummy_test.out @@ -0,0 +1,31 @@ +Listing all files: +name: file1 +basename: file1 +isDir: 0 +size: 1 +time: 0 +name: dir/file2 +basename: file2 +isDir: 0 +size: 2 +time: 0 + +Stat for single files: +name: file1 +basename: file1 +isDir: 0 +size: 1 +time: 0 + +name: dir/file2 +basename: file2 +isDir: 0 +size: 2 +time: 0 + +name: dir +basename: dir +isDir: 1 +size: 0 +time: 0 +filesize: 11 diff --git a/libs/mangle/vfs/tests/output/ogre_client_test.out b/libs/mangle/vfs/tests/output/ogre_client_test.out new file mode 100644 index 0000000000..bc10b1e5cc --- /dev/null +++ b/libs/mangle/vfs/tests/output/ogre_client_test.out @@ -0,0 +1,12 @@ +Case: 1 +Name: dummy +Type: Mangle +All files: + file1 + dir/file2 +Non-recursive: + file1 +Dirs: + dir +filesize: 11 +contents: hello world diff --git a/libs/mangle/vfs/tests/output/ogre_resource_test.out b/libs/mangle/vfs/tests/output/ogre_resource_test.out new file mode 100644 index 0000000000..9bfc4ff5b6 --- /dev/null +++ b/libs/mangle/vfs/tests/output/ogre_resource_test.out @@ -0,0 +1,20 @@ + +File: Makefile +Size: 828 +First line: GCC=g++ -I../ + +File: ogre_resource_test.cpp +Size: 1437 +First line: #include + +File: bleh +Does not exist + +All source files: + physfs_server_test.cpp + dummy_test.cpp + ogre_resource_test.cpp + server_common.cpp + dummy_vfs.cpp + ogre_client_test.cpp + ogre_server_test.cpp diff --git a/libs/mangle/vfs/tests/output/ogre_server_test.out b/libs/mangle/vfs/tests/output/ogre_server_test.out new file mode 100644 index 0000000000..74f3508444 --- /dev/null +++ b/libs/mangle/vfs/tests/output/ogre_server_test.out @@ -0,0 +1,11 @@ + +File: Makefile +Size: 828 +First 12 bytes: GCC=g++ -I.. + +File: testfile.txt +Size: 13 +First 12 bytes: hello world! + +File: blah_bleh +File doesn't exist diff --git a/libs/mangle/vfs/tests/output/physfs_server_test.out b/libs/mangle/vfs/tests/output/physfs_server_test.out new file mode 100644 index 0000000000..74f3508444 --- /dev/null +++ b/libs/mangle/vfs/tests/output/physfs_server_test.out @@ -0,0 +1,11 @@ + +File: Makefile +Size: 828 +First 12 bytes: GCC=g++ -I.. + +File: testfile.txt +Size: 13 +First 12 bytes: hello world! + +File: blah_bleh +File doesn't exist diff --git a/libs/mangle/vfs/tests/physfs_server_test.cpp b/libs/mangle/vfs/tests/physfs_server_test.cpp new file mode 100644 index 0000000000..9e9d088cdf --- /dev/null +++ b/libs/mangle/vfs/tests/physfs_server_test.cpp @@ -0,0 +1,21 @@ +#include "../servers/physfs_vfs.hpp" + +#include "server_common.cpp" + +#include + +int main() +{ + // Set up the library and paths + PHYSFS_init("blah"); + PHYSFS_addToSearchPath("test.zip", 1); + PHYSFS_addToSearchPath("./", 1); + + // Create our interface + PhysVFS vfs; + + // Run the test + testAll(vfs); + + return 0; +} diff --git a/libs/mangle/vfs/tests/server_common.cpp b/libs/mangle/vfs/tests/server_common.cpp new file mode 100644 index 0000000000..1834bc25a7 --- /dev/null +++ b/libs/mangle/vfs/tests/server_common.cpp @@ -0,0 +1,33 @@ +#include + +using namespace Mangle::VFS; +using namespace Mangle::Stream; +using namespace std; + +void find(VFS &vfs, const std::string &file) +{ + cout << "\nFile: " << file << endl; + + if(!vfs.isFile(file)) + { + cout << "File doesn't exist\n"; + return; + } + + StreamPtr data = vfs.open(file); + + cout << "Size: " << data->size() << endl; + + char buf[13]; + buf[12] = 0; + data->read(buf, 12); + + cout << "First 12 bytes: " << buf << "\n"; +} + +void testAll(VFS &vfs) +{ + find(vfs, "Makefile"); // From the file system + find(vfs, "testfile.txt"); // From the zip + find(vfs, "blah_bleh"); // Doesn't exist +} diff --git a/libs/mangle/vfs/tests/test.sh b/libs/mangle/vfs/tests/test.sh new file mode 100755 index 0000000000..2d07708adc --- /dev/null +++ b/libs/mangle/vfs/tests/test.sh @@ -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 diff --git a/libs/mangle/vfs/tests/test.zip b/libs/mangle/vfs/tests/test.zip new file mode 100644 index 0000000000..ec82f8bc6b Binary files /dev/null and b/libs/mangle/vfs/tests/test.zip differ diff --git a/libs/mangle/vfs/vfs.hpp b/libs/mangle/vfs/vfs.hpp new file mode 100644 index 0000000000..258a6b0a0e --- /dev/null +++ b/libs/mangle/vfs/vfs.hpp @@ -0,0 +1,87 @@ +#ifndef MANGLE_VFS_H +#define MANGLE_VFS_H + +#include "../stream/stream.hpp" +#include +#include + +namespace Mangle { +namespace VFS { + +/// Generic file info structure +struct FileInfo +{ + /// Full name, including path + std::string name; + + /// Base name, not including path + std::string basename; + + /// Is this a directory? + bool isDir; + + /// File size + size_t size; + + /// Last modification date + time_t time; +}; + +typedef std::vector FileInfoList; + +typedef boost::shared_ptr FileInfoPtr; +typedef boost::shared_ptr FileInfoListPtr; + +/** An interface to any file system or other provider of named data + streams +*/ +class VFS +{ + public: + // Feature options. These should be set in the constructor. + + /// If true, the list() function work + bool hasList; + + /// If true, the find() function work + bool hasFind; + + /// If true, the file system is case sensitive + bool isCaseSensitive; + + /// Virtual destructor + virtual ~VFS() {} + + /// Open a new data stream. Deleting the object (letting all the + /// pointers to it go out of scope) should be enough to close it. + virtual Stream::StreamPtr open(const std::string &name) = 0; + + /// Check for the existence of a file + virtual bool isFile(const std::string &name) const = 0; + + /// Check for the existence of a directory + virtual bool isDir(const std::string &name) const = 0; + + /// Get info about a single file + virtual FileInfoPtr stat(const std::string &name) const = 0; + + /// List all entries in a given directory. A blank dir should be + /// interpreted as a the root/current directory of the archive. If + /// dirs is true, list directories instead of files. + virtual FileInfoListPtr list(const std::string& dir = "", + bool recurse=true, + bool dirs=false) const = 0; + + /// Find files after a given pattern. Wildcards (*) are + /// supported. Only valid if 'hasFind' is true. Don't implement your + /// own pattern matching here if the backend doesn't support it + /// natively; use a filter instead. + virtual FileInfoListPtr find(const std::string& pattern, + bool recursive=true, + bool dirs=false) const = 0; +}; + +typedef boost::shared_ptr VFSPtr; + +}} // namespaces +#endif