From 6988814728387d5565d077d948a36c322de71839 Mon Sep 17 00:00:00 2001 From: nkorslund Date: Sat, 12 Jul 2008 09:43:38 +0000 Subject: [PATCH] First attempt at OpenAL (thanks to ChrisRobinson) git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@20 ea6a568a-9f4f-0410-981a-c910a81bb256 --- COMPILE-linux.txt | 28 ++++--- Makefile | 77 ++++++++++++++--- README.txt | 36 +++++--- build_openmw.bat | 8 +- build_openmw.sh | 6 +- dsss.conf | 6 +- esm/filereader.d | 6 +- esmtool.d | 2 + input/events.d | 8 +- nif/data.d | 2 +- ogre/bindings.d | 3 +- ogre/cpp_framelistener.cpp | 14 ++++ openmw.d | 6 ++ scene/soundlist.d | 38 ++++++--- sound/al.d | 2 + sound/alc.d | 2 + sound/audiere.d | 72 ---------------- sound/audio.d | 43 +++++++++- sound/cpp_audiere.cpp | 118 -------------------------- sound/music.d | 167 ++++++++++++++++++++++++++++--------- sound/sfx.d | 136 ++++++++++++------------------ 21 files changed, 400 insertions(+), 380 deletions(-) delete mode 100644 sound/audiere.d delete mode 100644 sound/cpp_audiere.cpp diff --git a/COMPILE-linux.txt b/COMPILE-linux.txt index 39a357bea..b04056c67 100644 --- a/COMPILE-linux.txt +++ b/COMPILE-linux.txt @@ -37,8 +37,8 @@ Dependencies: Dependencies needed to build OpenMW: OGRE 1.4.5 (3d engine) -Audiere 1.9.4 (sound engine) OIS-1.0.0 (input system) +OpenAL (3d sound system) gcc and g++ (C++ compiler) GNU make (build tool for C++ files) DMD 1.031 (D compiler) @@ -46,15 +46,15 @@ DMD 1.031 (D compiler) The above versions are the ones I have tested recently, but other -versions might work. OGRE, Audiere and OIS will require their own set -of dependencies. I recommend using an automated package tool to -install as many of these as possible. On ubuntu, try typing: +versions might work. OGRE and OIS will require their own set of +dependencies. I recommend using an automated package tool to install +as many of these as possible. On ubuntu, try typing: -sudo apt-get install libogre-dev libaudiere-dev libois-dev build-essential g++ gdc -If you want to install Ogre, Audiere or OIS manually, try: +sudo apt-get install libogre-dev libalut0 libois-dev build-essential g++ gdc +If you want to install Ogre, OpenAL or OIS manually, try: -OGRE: http://ogre3d.org -Audiere: http://audiere.sourceforge.net/ +OGRE: http://ogre3d.org/ +Audiere: http://openal.org/ OIS: http://sourceforge.net/projects/wgois/ @@ -87,11 +87,19 @@ installed (a D build tool), type: dsss build -If you do NOT have DSSS, you can compile using the script +If you do NOT have DSSS, try using make + +make all + +You might need to edit the Makefile to match your setup. If you are +using DMD instead of GDC, try changing the compiler from "gdmd" to +"dmd" in the Makefile. + +If all else fails, you can try the build script: ./build_openmw.sh -(Currently only works with the GDC compiler) +This build method is deprecated and only works with gdc. diff --git a/Makefile b/Makefile index 0773eca9f..d6677b5e0 100644 --- a/Makefile +++ b/Makefile @@ -3,28 +3,81 @@ # Compiler settings CXX?= g++ CXXFLAGS?= -Wall -g +DMD=gdmd -version=Posix + +# Some extra flags for niftool and bsatool +NIFFLAGS= # Compiler settings for Ogre + OIS. Change as needed. -OGCC=$(CXX) $(CXXFLAGS) `pkg-config --cflags OGRE OIS` - -# Compiler settings for Audiere -AGCC=$(CXX) $(CXXFLAGS) `audiere-config --cxxflags` +OGCC=$(CXX) $(CXXFLAGS) `pkg-config --cflags OGRE OIS openal` # Ogre C++ files, on the form ogre/cpp_X.cpp. Only the first file is # passed to the compiler, the rest are dependencies. ogre_cpp=ogre framelistener interface overlay bsaarchive -# Audiere C++ files, on the form sound/cpp_X.cpp. -audiere_cpp=audiere - ## The rest of this file is automatic ## -ogre_cpp_files=$(ogre_cpp:%=ogre/cpp_%.cpp) -audiere_cpp_files=$(audiere_cpp:%=sound/cpp_%.cpp) -all: cpp_ogre.o cpp_audiere.o +ogre_cpp_files=$(ogre_cpp:%=ogre/cpp_%.cpp) + +d_files=$(wildcard */*.d) $(wildcard monster/util/*.d) +d_files_nif=$(wildcard nif/*.d) $(wildcard util/*.d) $(wildcard core/memory.d) $(wildcard monster/util/*.d) + +# The NIF object files for niftool and bsatool are put in a separate +# directory, since they are built with different flags. +d_objs=$(d_files:%.d=objs/%.o) +d_objs_nif=$(d_files_nif:%.d=nifobjs/%.o) + +.PHONY: cpp all clean makedirs + +# By default, make will only build the Ogre C++ sources. +cpp: cpp_ogre.o + +all: makedirs openmw esmtool niftool bsatool bored cpp_ogre.o: $(ogre_cpp_files) $(OGCC) -c $< -cpp_audiere.o: $(audiere_cpp_files) - $(AGCC) -c $< +objs/%.o: %.d + $(DMD) -c $< -of$@ + +nifobjs/%.o: %.d + $(DMD) -debug=warnstd -debug=check -debug=statecheck -debug=strict -debug=verbose -c $< -of$@ + +# This is a hack for gdmd (dmd-like frontend to gdc), since it does +# not automatically create directories as it should. +makedirs: + mkdir -p objs/bsa + mkdir -p objs/core + mkdir -p objs/esm + mkdir -p objs/input + mkdir -p objs/monster/util + mkdir -p objs/nif + mkdir -p objs/ogre + mkdir -p objs/scene + mkdir -p objs/sound + mkdir -p objs/util + mkdir -p nifobjs/nif + mkdir -p nifobjs/util + mkdir -p nifobjs/core + mkdir -p nifobjs/monster/util + mkdir -p nifobjs/bsa + +openmw: openmw.d cpp_ogre.o $(d_objs) + $(DMD) $^ -of$@ -L-lalut -L-lopenal -L-lOgreMain -L-lOIS + +esmtool: esmtool.d cpp_ogre.o $(d_objs) + $(DMD) $^ -of$@ -L-lalut -L-lopenal -L-lOgreMain -L-lOIS + +niftool: niftool.d $(d_objs_nif) + $(DMD) $^ -of$@ + +bsatool: bsatool.d $(d_objs_nif) bsa/bsafile.d + $(DMD) $^ -of$@ + +bored: bored.d + $(DMD) $^ + +clean: + -rm cpp_ogre.o + -rm openmw esmtool niftool bsatool bored + -rm -r objs/ nifobjs/ dsss_objs/ diff --git a/README.txt b/README.txt index c90abb910..49caed009 100644 --- a/README.txt +++ b/README.txt @@ -5,8 +5,8 @@ Written by Nicolay Korslund Email: korslund@gmail.com WWW: http://openmw.snaptoad.com License: See GPL3.txt -Current version: 0.3 (still very pre-alpha) -Date: 2008 jul. 10 +Current version: 0.4 (still very pre-alpha) +Date: 2008 jul. 12 @@ -18,16 +18,24 @@ Morrowind installed on your system! -Release notes for 0.3 -===================== +IMPORTANT: Subversion notes +=========================== -As of this release, OpenMW officially builds and runs on Windows. The -installation instructions have been split into the files -COMPILE-win32.txt and README-win32.txt for the source and binary -windows releases respectively, and COMPILE-linux.txt for Linux / Unix -systems. +The subversion code is currently in the process of switching from +Audiere to OpenAL. This means that: -See the changelog at the end for more changes. +- you need to install OpenAL and ALUT +- you no longer need Audiere +- music does not (currently) work + +Generally true for all SVN versions is that: + +- a given SVN revision is not guaranteed to work or compile +- windows compilation scripts are unlikely to work since they are + updated less often +- README and instructions might be out of date + +See the changelog at the end for an up-to-date list of changes. Note: if you are using a localized (non-English) version of Morrowind, the default starting cell (Sud) might not exist, and the esmtool @@ -105,14 +113,18 @@ Thanks goes out to: - Bastien Jansen for continued testing on 64 bit linux. -- Bethesda Softworks for creating Morrowind! - +- Chris Robinson for OpenAL and ALUT support Changelog: ========== +0.4 (Work in progress) +- switched from Audiere to OpenAL (BIG thanks to Chris Robinson) +- added complete Makefile (again) as a alternative build tool +- cosmetic changes to placate gdc -Wall + 0.3 (2008 jul. 10) - latest release - built and tested on Windows XP - partial support for FreeBSD (exceptions do not work) diff --git a/build_openmw.bat b/build_openmw.bat index 17b34f3b6..b8fddd477 100755 --- a/build_openmw.bat +++ b/build_openmw.bat @@ -1,9 +1,9 @@ @echo off -rem See BUILDING-win32.txt for instructions. +rem See COMPILE-win32.txt for instructions. -rem This file assumes it can find Ogre in ..\ogre and -rem Audiere in ..\audiere +rem This file assumes it can find Ogre in ..\ogro +rem TODO: Not updated to OpenAL echo Compiling C++ files g++ -c sound\cpp_audiere.cpp -I..\audiere\include @@ -16,4 +16,4 @@ copy ..\audiere\bin\audiere.dll . copy \windows\system32\d3dx9_30.dll d3dx9d_30.dll echo Compiling main program (openmw.exe) -gdc openmw.d bsa\*.d core\*.d esm\*.d input\*.d nif\*.d ogre\*.d scene\*.d sound\*.d util\*.d cpp_audiere.o cpp_ogre.o monster\util\*.d ogremain_d.dll ..\audiere\lib\audiere.lib OIS_d.dll -lstdc++ -o openmw.exe +gdc -Wall -g openmw.d bsa\*.d core\*.d esm\*.d input\*.d nif\*.d ogre\*.d scene\*.d sound\*.d util\*.d cpp_ogre.o monster\util\*.d ogremain_d.dll OIS_d.dll -lstdc++ -o openmw.exe diff --git a/build_openmw.sh b/build_openmw.sh index 438862c66..0e8a41638 100755 --- a/build_openmw.sh +++ b/build_openmw.sh @@ -1,9 +1,9 @@ #!/bin/sh -# See INSTALL-linux.txt for instructions +# See COMPILE-linux.txt for instructions make || exit 1 -gdc -Wall -Wextra -O2 -g -fversion=Posix -o openmw openmw.d core/*.d ogre/*.d nif/*.d util/*.d bsa/*.d monster/util/*.d input/*.d sound/*.d scene/*.d esm/*.d cpp_*.o -laudiere -lm -lOgreMain -lOIS -lstdc++ +gdc -Wall -g -fversion=Posix -o openmw openmw.d core/*.d ogre/*.d nif/*.d util/*.d bsa/*.d monster/util/*.d input/*.d sound/*.d scene/*.d esm/*.d cpp_ogre.o -lalut -lopenal -lm -lOgreMain -lOIS -lstdc++ -gdc -Wall -Wextra -O2 -g -fversion=Posix -o esmtool esmtool.d core/*.d ogre/*.d nif/*.d util/*.d bsa/*.d monster/util/*.d input/*.d sound/*.d scene/*.d esm/*.d cpp_*.o -laudiere -lm -lOgreMain -lOIS -lstdc++ +gdc -Wall -g -fversion=Posix -o esmtool esmtool.d core/*.d ogre/*.d nif/*.d util/*.d bsa/*.d monster/util/*.d input/*.d sound/*.d scene/*.d esm/*.d cpp_ogre.o -lalut -lopenal -lm -lOgreMain -lOIS -lstdc++ diff --git a/dsss.conf b/dsss.conf index 665ac6a22..c93397774 100644 --- a/dsss.conf +++ b/dsss.conf @@ -4,7 +4,7 @@ [openmw.d] # Add libraries and the two C++ object files -buildflags = -llOgreMain -llaudiere -llOIS cpp_audiere.o cpp_ogre.o +buildflags = -llOgreMain -llalut -llopenal -llOIS cpp_ogre.o # Make sure the C++ object files are built first version(Windows) { @@ -34,7 +34,7 @@ prebuild += dsss clean niftool [esmtool.d] # Because of interdepencies between the ESM code and the resource # manager, we have to include all the C++ stuff. -buildflags = -llOgreMain -llaudiere -llOIS cpp_audiere.o cpp_ogre.o +buildflags = -llOgreMain -llalut -llopenal -llOIS cpp_ogre.o @@ -47,6 +47,4 @@ buildflags = -debug=warnstd -debug=check -debug=statecheck -debug=strict -debug= # Clean the nif object files to make sure they are recompiled in debug mode prebuild = dsss clean niftool - - [bored.d] diff --git a/esm/filereader.d b/esm/filereader.d index 01666c9c2..88301c1e7 100644 --- a/esm/filereader.d +++ b/esm/filereader.d @@ -87,12 +87,12 @@ struct TES3FileContext { char[] filename; uint leftRec, leftSub; - size_t leftFile; + ulong leftFile; NAME recName, subName; FileType type; Version ver; - size_t filepos; + ulong filepos; } /** @@ -118,7 +118,7 @@ struct TES3File // These are only used by getRecHeader and getSubHeader for // asserting the file's integrity. - uint leftFile; // Number of unread bytes in file + ulong leftFile; // Number of unread bytes in file uint leftRec; // Number of unread bytes in record // This is used by sub-record readers for integrity checking. diff --git a/esmtool.d b/esmtool.d index c63e583ee..34f7b52b8 100644 --- a/esmtool.d +++ b/esmtool.d @@ -154,6 +154,7 @@ void main(char[][] args) case WT.MarksmanThrown: writef("Thrown weapon"); break; case WT.Arrow: writef("Arrow"); break; case WT.Bolt: writef("Bolt"); break; + default: assert(0); } writefln(" id '%s': name '%s'", n, m.name); @@ -278,6 +279,7 @@ void printRaw() case FileType.Master: writefln("Master"); break; case FileType.Savegame: writefln("Savegame"); break; case FileType.Unknown: writefln("Unknown"); break; + default: assert(0); } writef("Version: "); if(isVer12()) writefln("1.2"); diff --git a/input/events.d b/input/events.d index a4849613b..eec90371a 100644 --- a/input/events.d +++ b/input/events.d @@ -309,9 +309,13 @@ extern(C) int d_frameStarted(float time) if(sndCumTime > sndRefresh) { float x, y, z; - cpp_getCameraPos(&x, &y, &z); + float fx, fy, fz; + float ux, uy, uz; - soundScene.update(x,y,z); + cpp_getCameraPos(&x, &y, &z); + cpp_getCameraOrientation(&fx, &fy, &fz, &ux, &uy, &uz); + + soundScene.update(x,y,z,fx,fy,fz,ux,uy,uz); sndCumTime -= sndRefresh; } diff --git a/nif/data.d b/nif/data.d index 0b876c602..e5d935850 100644 --- a/nif/data.d +++ b/nif/data.d @@ -167,7 +167,7 @@ class NiAutoNormalParticlesData : ShapeData debug(verbose) writef("Active vertices: "); // Number of active vertices (always matches count?) - activeCount = nifFile.wgetUshortIs(cast(ushort)vertices.length/3); + activeCount = nifFile.wgetUshortIs(cast(ushort)(vertices.length/3)); nifFile.wgetFloat(); // Active radius (?) nifFile.wgetShort(); // Number of valid entries in the following arrays diff --git a/ogre/bindings.d b/ogre/bindings.d index e09041903..6af7237fd 100644 --- a/ogre/bindings.d +++ b/ogre/bindings.d @@ -140,10 +140,11 @@ void cpp_createMesh( // Save a screen shot to the given file name void cpp_screenshot(char *filename); -// Camera controll +// Camera control and information void cpp_rotateCamera(float x, float y); void cpp_moveCamera(float x, float y, float z, float r1, float r2, float r3); void cpp_getCameraPos(float *x, float *y, float *z); +void cpp_getCameraOrientation(float *fx, float *fy, float *fz, float *ux, float *uy, float *uz); void cpp_moveCameraRel(float x, float y, float z); // Do some debug action. Check the menu for today's specials! diff --git a/ogre/cpp_framelistener.cpp b/ogre/cpp_framelistener.cpp index 2440140aa..8abb88735 100644 --- a/ogre/cpp_framelistener.cpp +++ b/ogre/cpp_framelistener.cpp @@ -132,6 +132,20 @@ extern "C" void cpp_getCameraPos(float *x, float *y, float *z) *z = pos[1]; } +// Get current camera orientation +extern "C" void cpp_getCameraOrientation(float *fx, float *fy, float *fz, + float *ux, float *uy, float *uz) +{ + Vector3 front = mCamera->getDirection(); + Vector3 up = mCamera->getUp(); + *fx = front[0]; + *fy = -front[2]; + *fz = front[1]; + *ux = up[0]; + *uy = -up[2]; + *uz = up[1]; +} + // Move and rotate camera in place. Transforms Morrowind coordinates // to OGRE coordinates. extern "C" void cpp_moveCamera(float x, float y, float z, diff --git a/openmw.d b/openmw.d index c1be385be..1418df343 100644 --- a/openmw.d +++ b/openmw.d @@ -359,9 +359,15 @@ void main(char[][] args) // Run it until the user tells us to quit startRendering(); + + jukebox.disableMusic(); + battleMusic.disableMusic(); } else debug(verbose) writefln("Skipping rendering"); + soundScene.kill(); + shutdownSound(); + debug(verbose) { writefln(); diff --git a/scene/soundlist.d b/scene/soundlist.d index c90828369..92694af43 100644 --- a/scene/soundlist.d +++ b/scene/soundlist.d @@ -37,10 +37,7 @@ SoundList soundScene; // very long. struct SoundList { - // TODO: This is really just a test, a hack. Will be replaced by a - // list or similar later. - SoundInstance list[50]; - int index = 0; + SoundInstance[] list; // Get a sound instance from a Sound struct static SoundInstance getInstance(Sound *s, bool loop=false) @@ -57,16 +54,33 @@ struct SoundList SoundInstance *insert(Sound *snd, bool loop=false) { - if(index == 50) return null; - - SoundInstance *s = &list[index++]; - *s = getInstance(snd, loop); - return s; + // Reuse a dead instance if one exists + foreach(ref s; list) + { + if(s.owner == null) + { + s = getInstance(snd, loop); + return &s; + } + } + // Otherwise append a new one + list ~= getInstance(snd, loop); + return &list[$-1]; } - void update(float x, float y, float z) + void update(float x, float y, float z, + float frontx, float fronty, float frontz, + float upx, float upy, float upz) { - foreach(ref s; list[0..index]) - s.setPlayerPos(x,y,z); + SoundInstance.setPlayerPos(x,y,z,frontx,fronty,frontz,upx,upy,upz); + } + + void kill() + { + foreach(ref s; list) + { + if(s.owner) s.kill(); + } + list = null; } } diff --git a/sound/al.d b/sound/al.d index 7ad5af98b..459b563cd 100644 --- a/sound/al.d +++ b/sound/al.d @@ -1,3 +1,5 @@ +module sound.al; + extern(System): //Defines diff --git a/sound/alc.d b/sound/alc.d index 9aa75d7c6..7a2a51dce 100644 --- a/sound/alc.d +++ b/sound/alc.d @@ -1,3 +1,5 @@ +module sound.alc; + extern(System): //Definitions diff --git a/sound/audiere.d b/sound/audiere.d deleted file mode 100644 index a30d35c76..000000000 --- a/sound/audiere.d +++ /dev/null @@ -1,72 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.snaptoad.com/ - - This file (audiere.d) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -// This file exposes the C++ functions that deal directly with the -// sound library. The functions are implemented in cpp_audiere.cpp - -module sound.audiere; - -// A sound resource is the handle that represents the resource, -// ie. the file. -typedef void* AudiereResource; - -// A sound instance is an instance of this resource. We can have -// several instances of the same file playing at once. Each instance -// has its own volume, file position, pitch, etc. -typedef void* AudiereInstance; - -extern(C) -{ - // Open the music device. Returns 0 on success, 1 on failure. - int cpp_openDevice(); - - // Open a new sound resource. Returns null on failure. - AudiereResource cpp_openSound(char* filename); - - // Close a resource. - void cpp_closeSound(AudiereResource sound); - - // Create an instance of a sound. - AudiereInstance cpp_createInstance(AudiereResource sound); - - // Create an instance by streaming directly from file, and play it. - AudiereInstance cpp_playStream(char* filename, float volume); - - // Destroy a previously created instance - void cpp_destroyInstance(AudiereInstance instance); - - // Is this instance currently playing? - int cpp_isPlaying(AudiereInstance sound); - - // Adjust parameters for this instance. - void cpp_setParams(AudiereInstance sound, float volume, float pan); - - // Play a sound. - void cpp_playSound(AudiereInstance sound); - - // Set repeat mode on - void cpp_setRepeat(AudiereInstance sound); - - // Stop a sound - void cpp_stopSound(AudiereInstance sound); -} diff --git a/sound/audio.d b/sound/audio.d index 97f1d1a85..9ebd0dfae 100644 --- a/sound/audio.d +++ b/sound/audio.d @@ -26,7 +26,15 @@ module sound.audio; public import sound.sfx; public import sound.music; -import sound.audiere; +import sound.al; +import sound.alc; + +ALCdevice *Device = null; +ALCcontext *Context = null; + +// Temporarilly use ALUT for data loading until something better is picked +extern (C) ALboolean alutInitWithoutContext(int *argc, char **argv); +extern (C) ALboolean alutExit(); class SoundException : Exception { @@ -38,9 +46,40 @@ MusicManager battleMusic; void initializeSound() { - if(cpp_openDevice()) + Device = alcOpenDevice(null); + Context = alcCreateContext(Device, null); + + if(!Device || !Context) throw new SoundException("initializeSound()", "Failed to initialize music device"); + + alcMakeContextCurrent(Context); + alutInitWithoutContext(null, null); + + // Gross HACK: We should use the default model (inverse distance clamped). + // But without a proper rolloff factor, distance attenuation is completely + // wrong. This at least makes sure the max distance is the 'silence' point + alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); + jukebox.initialize("Main"); battleMusic.initialize("Battle"); } + +void shutdownSound() +{ + alutExit(); + alcMakeContextCurrent(null); + if(Context) alcDestroyContext(Context); + Context = null; + if(Device) alcCloseDevice(Device); + Device = null; +} + +ALenum checkALError() +{ + ALenum err = alGetError(); + if(err != AL_NO_ERROR) + writefln("WARNING: Received AL error (%x): %s", err, + toString(alGetString(err))); + return err; +} diff --git a/sound/cpp_audiere.cpp b/sound/cpp_audiere.cpp deleted file mode 100644 index e4e1620ae..000000000 --- a/sound/cpp_audiere.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.snaptoad.com/ - - This file (cpp_audiere.cpp) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -#include -#include -using namespace audiere; -using namespace std; - -/* - * Sound, using Audiere. Creates a very simple C interface for - * opening, closing and manipulating sounds. - */ - -AudioDevicePtr device; - -extern "C" int32_t cpp_openDevice() -{ - device = OpenDevice(""); - if (!device) return 1; - return 0; -} - -// Opens a new sample buffer from a file -extern "C" SampleBuffer *cpp_openSound(char* filename) -{ - SampleSourcePtr sample = OpenSampleSource(filename); - if(!sample) return NULL; - SampleBufferPtr buf = CreateSampleBuffer(sample); - buf->ref(); - return buf.get(); -} - -// Delete a sample buffer -extern "C" void cpp_closeSound(SampleBuffer *buf) -{ - buf->unref(); -} - -// Get an output stream from a sample buffer. -extern "C" OutputStream *cpp_createInstance(SampleBuffer *buf) -{ - SampleSourcePtr sample = buf->openStream(); - if(!sample) return NULL; - - OutputStreamPtr sound = OpenSound(device, sample, false); - - sound->ref(); - return sound.get(); -} - -// Stream a file directly. Used for music. -extern "C" OutputStream *cpp_playStream(char* filename, float volume) -{ - OutputStreamPtr sound = OpenSound(device, filename, true); - if(sound) - { - sound->ref(); - sound->setVolume(volume); - sound->play(); - } - return sound.get(); -} - -extern "C" void cpp_destroyInstance(OutputStream *sound) -{ - sound->unref(); -} - -extern "C" int32_t cpp_isPlaying(OutputStream *sound) -{ - if(sound && sound->isPlaying()) return 1; - return 0; -} - -extern "C" void cpp_setParams(OutputStream *sound, float vol, float pan) -{ - if(sound) - { - sound->setVolume(vol); - sound->setPan(pan); - } -} - -extern "C" void cpp_playSound(OutputStream *sound) -{ - sound->play(); -} - -extern "C" void cpp_setRepeat(OutputStream *sound) -{ - sound->setRepeat(true); -} - -// Stop the sound -extern "C" void cpp_stopSound(OutputStream *sound) -{ - if(sound) sound->stop(); -} diff --git a/sound/music.d b/sound/music.d index 86eee9cdd..51d638aa5 100644 --- a/sound/music.d +++ b/sound/music.d @@ -23,34 +23,29 @@ module sound.music; -import sound.audiere; import sound.audio; +import sound.al; import std.string; import core.config; import core.resource; +extern (C) ALuint alutCreateBufferFromFile(char *filename); +extern (C) ALenum alutGetError(); +extern (C) ALchar *alutGetErrorString(ALenum err); + // Simple music player, has a playlist and can pause/resume music. struct MusicManager { private: - // How often we check if the music has died. - const float pollInterval = 2.0; - - // Max file size to load. Files larger than this are streamed. - const int loadSize = 1024*1024; - // How much to add to the volume each second when fading const float fadeInRate = 0.10; const float fadeOutRate = 0.35; // Volume - float volume, maxVolume; - - // Time since last time we polled - float sumTime; + ALfloat volume, maxVolume; char[] name; @@ -73,7 +68,10 @@ struct MusicManager int index; // Index of next song to play bool musicOn; - AudiereInstance music; + ALuint sID; + ALuint bIDs[1]; + + ubyte[] readData; // Which direction are we currently fading, if any enum Fade { None = 0, In, Out } @@ -85,6 +83,8 @@ struct MusicManager void initialize(char[] name) { this.name = name; + sID = 0; + foreach(ref b; bIDs) b = 0; musicOn = false; updateVolume(); } @@ -100,7 +100,7 @@ struct MusicManager // a fade. Even if we are fading, though, the volume should never // be over the max. if(fading == Fade.None || volume > maxVolume) volume = maxVolume; - cpp_setParams(music, volume, 0.0); + alSourcef(sID, AL_GAIN, volume); } // Give a music play list @@ -120,7 +120,8 @@ struct MusicManager { if(playlist.length < 2) return; - char[] last = playlist[0]; + // Get the name of the last song played + char[] last = playlist[(index==0) ? ($-1) : (index-1)]; int left = playlist.length; rndList.length = left; @@ -166,13 +167,25 @@ struct MusicManager // If music is disabled, do nothing if(!musicOn) return; - // Kill current track - if(music) cpp_destroyInstance(music); - music = null; - // No tracks to play? if(!playlist.length) return; + // Generate a source to play back with if needed + if(!sID) + { + alGenSources(1, &sID); + if(checkALError() != AL_NO_ERROR) + return; + alSourcei(sID, AL_SOURCE_RELATIVE, AL_TRUE); + } + + // Kill current track + alSourceStop(sID); + alSourcei(sID, AL_BUFFER, 0); + alDeleteBuffers(bIDs.length, bIDs.ptr); + foreach(ref b; bIDs) b = 0; + checkALError(); + // End of list? Randomize and start over if(index == playlist.length) { @@ -180,9 +193,35 @@ struct MusicManager index = 0; } - music = cpp_playStream(toStringz(playlist[index]), volume); + readData.length = 128*1024 / bIDs.length; - if(!music) fail("Unable to start music track " ~ playlist[index]); + // FIXME: Should load up and queue 3 or 4 buffers here instead of trying to + // load it all into one (when we switch away from ALUT). + char *fname = toStringz(playlist[index]); + bIDs[0] = alutCreateBufferFromFile(fname); + if(!bIDs[0]) + { + writefln("Unable to load music track %s: %s", playlist[index], + toString(alutGetErrorString(alutGetError()))); + alDeleteSources(1, &sID); + checkALError(); + sID = 0; + index++; + return; + } + + alSourcei(sID, AL_BUFFER, bIDs[0]); + alSourcePlay(sID); + if(checkALError() != AL_NO_ERROR) + { + writefln("Unable to start music track %s", playlist[index]); + alSourceStop(sID); + alDeleteSources(1, &sID); + alDeleteBuffers(bIDs.length, bIDs.ptr); + checkALError(); + sID = 0; + foreach(ref b; bIDs) b = 0; + } index++; } @@ -192,7 +231,6 @@ struct MusicManager { if(!config.useMusic) return; - sumTime = 0; musicOn = true; volume = maxVolume; fading = Fade.None; @@ -202,9 +240,16 @@ struct MusicManager // Disable music void disableMusic() { - if(music) cpp_destroyInstance(music); - music = null; - musicOn = false; + if(sID) + { + alSourceStop(sID); + alDeleteSources(1, &sID); + alDeleteBuffers(bIDs.length, bIDs.ptr); + checkALError(); + sID = 0; + foreach(ref b; bIDs) b = 0; + } + musicOn = false; } // Pause current track @@ -218,29 +263,67 @@ struct MusicManager { if(!config.useMusic) return; - sumTime = 0; volume = 0.0; fading = Fade.In; musicOn = true; - if(music) cpp_playSound(music); + if(sID) addTime(0); else playNext(); } - // Add time since last frame to the counter. If the accumulated time - // has passed the polling interval, then check if the music has - // died. The Audiere library has a callback functionality, but it - // turned out not to be terribly reliable. Sometimes it was never - // called at all. So we have to poll at regular intervals .. :( This - // function is also used for fading. + // Checks if a stream is playing, filling more data as needed, and restarting + // if it stalled or was paused. + private bool isPlaying() + { + if(!sID) return false; + /* Use this when we can do streaming.. + ALint count; + alGetSourcei(sID, AL_BUFFERS_PROCESSED, &count); + if(checkALError() != AL_NO_ERROR) return false; + + for(int i = 0;i < count;i++) + { + int length = GetData(readData.ptr, readData.length); + if(length <= 0) + { + if(i == 0) + { + ALint state; + alGetSourcei(sID, AL_SOURCE_STATE, &state); + if(checkALError() != AL_NO_ERROR || state == AL_STOPPED) + return false; + } + break; + } + + ALuint bid; + alSourceUnqueueBuffers(sID, 1, &bid); + if(checkALError() == AL_NO_ERROR) + { + alBufferData(bid, dataFreq, dataFormat, length, readData.ptr); + alSourceQueueBuffers(sID, 1, &bid); + checkALError(); + } + } + + ALint state = AL_PLAYING; + alGetSourcei(sID, AL_SOURCE_STATE, &state); + if(state != AL_PLAYING) alSourcePlay(sID); + return (checkALError() == AL_NO_ERROR); + */ + ALint state; + alGetSourcei(sID, AL_SOURCE_STATE, &state); + if(checkALError() != AL_NO_ERROR || state == AL_STOPPED) return false; + + if(state != AL_PLAYING) alSourcePlay(sID); + return (checkALError() == AL_NO_ERROR); + } + + // Check if the music has died. This function is also used for fading. void addTime(float time) { if(!musicOn) return; - sumTime += time; - if(sumTime > pollInterval) - { - sumTime = 0; - if(!cpp_isPlaying(music)) playNext(); - } + + if(!isPlaying()) playNext(); if(fading) { @@ -263,14 +346,16 @@ struct MusicManager fading = Fade.None; volume = 0.0; - // We are done fading out, disable music. - cpp_stopSound(music); + // We are done fading out, disable music. Don't call + // enableMusic (or isPlaying) unless you want it to start + // again. + if(sID) alSourcePause(sID); musicOn = false; } } // Set the new volume - cpp_setParams(music, volume, 0.0); + if(sID) alSourcef(sID, AL_GAIN, volume); } } } diff --git a/sound/sfx.d b/sound/sfx.d index 683609ff7..613544a1d 100644 --- a/sound/sfx.d +++ b/sound/sfx.d @@ -23,14 +23,16 @@ module sound.sfx; -import sound.audiere; import sound.audio; +import sound.al; import core.config; import core.resource; import std.string; +extern (C) ALuint alutCreateBufferFromFile(char *filename); + // Handle for a sound resource. This struct represents one sound // effect file (not a music file, those are handled differently // because of their size.) From this handle we may get instances, @@ -39,10 +41,10 @@ import std.string; // file, when to kill resources, etc. struct SoundFile { - AudiereResource res; + ALuint bID; char[] name; bool loaded; - + private int refs; private void fail(char[] msg) @@ -53,36 +55,44 @@ struct SoundFile // Load a sound resource. void load(char[] file) { - // Make sure the string is null terminated - assert(*(file.ptr+file.length) == 0); - name = file; loaded = true; refs = 0; - res = cpp_openSound(file.ptr); - - if(!res) fail("Failed to open sound file " ~ file); + bID = alutCreateBufferFromFile(toStringz(file)); + if(!bID) fail("Failed to open sound file " ~ file); } // Get an instance of this resource. + // FIXME: Should not call fail() here since it's quite possible for this to + // fail (on hardware drivers). When it does, it should check for an existing + // sound it doesn't need and kill it, then try again SoundInstance getInstance() { SoundInstance si; si.owner = this; - si.inst = cpp_createInstance(res); - if(!si.inst) fail("Failed to instantiate sound resource"); + alGenSources(1, &si.inst); + if(checkALError() != AL_NO_ERROR || !si.inst) + fail("Failed to instantiate sound resource"); + + alSourcei(si.inst, AL_BUFFER, cast(ALint)bID); + if(checkALError() != AL_NO_ERROR) + { + alDeleteSources(1, &si.inst); + fail("Failed to load sound resource"); + } refs++; return si; } // Return the sound instance when you're done with it - private void returnInstance(AudiereInstance inst) + private void returnInstance(ALuint sid) { refs--; - cpp_destroyInstance(inst); + alSourceStop(sid); + alDeleteSources(1, &sid); if(refs == 0) unload(); } @@ -90,38 +100,34 @@ struct SoundFile void unload() { loaded = false; - cpp_closeSound(res); + alDeleteBuffers(1, &bID); } } struct SoundInstance { - AudiereInstance inst; + ALuint inst; SoundFile *owner; - float volume, min, max; - float xx, yy, zz; // 3D position - bool playing; - bool repeat; // Return this instance to the owner void kill() { owner.returnInstance(inst); + owner = null; } // Start playing a sound. void play() { - playing = true; - cpp_playSound(inst); - if(repeat) cpp_setRepeat(inst); + alSourcePlay(inst); + checkALError(); } // Go buy a cookie void stop() { - cpp_stopSound(inst); - playing = false; + alSourceStop(inst); + checkALError(); } // Set parameters such as max volume and range @@ -132,71 +138,35 @@ struct SoundInstance } body { - this.volume = volume; - min = minRange; - max = maxRange; - this.repeat = repeat; - playing = false; + alSourcef(inst, AL_GAIN, volume); + alSourcef(inst, AL_REFERENCE_DISTANCE, minRange); + alSourcef(inst, AL_MAX_DISTANCE, maxRange); + alSourcei(inst, AL_LOOPING, repeat ? AL_TRUE : AL_FALSE); + alSourcePlay(inst); + checkALError(); } - // Set 3D position of sound + // Set 3D position of sounds. Need to convert from app's world coords to + // standard left-hand coords void setPos(float x, float y, float z) { - xx = x; - yy = y; - zz = z; + alSource3f(inst, AL_POSITION, x, z, -y); + checkALError(); } - // Currently VERY experimental, panning disabled. At some point we - // will likely switch to OpenAL with built-in 3D sound and dump this - // entirely. - void setPlayerPos(float x, float y, float z) + static void setPlayerPos(float x, float y, float z, + float frontx, float fronty, float frontz, + float upx, float upy, float upz) { - //writef("{%s %s %s} ", x, y, z); - // Distance squared - x -= xx; - y -= yy; - z -= zz; - //writef("[%s %s %s] ", x, y, z); - float r2 = (x*x + y*y + z*z); - //writefln(r2, " (%s)", max*max); - - // If outside range, disable - if(r2 > max*max) - { - // We just moved out of range - if(playing) - { - //writefln("Out of range"); - stop(); - } - } - else - { - // We just moved into range - if(!playing) - { - //writefln("In range!"); - play(); - } - } - - if(!playing) return; - - // Invert distance - if(r2 < 1) r2 = 1; - else r2 = 1/r2; - - float vol = 2*r2*min*min; - float pan = 0;//80*x*r2; - - //writefln("x=%s, vol=%s, pan=%s", x, vol, pan); - - if(vol>1.0) vol = 1.0; - if(pan<-1.0) pan = -1.0; - else if(pan > 1.0) pan = 1.0; - //writefln("vol=", vol, " volume=", vol*volume*config.calcSfxVolume()); - - cpp_setParams(inst, vol*volume*config.calcSfxVolume(), pan); + ALfloat orient[6]; + orient[0] = frontx; + orient[1] = frontz; + orient[2] =-fronty; + orient[3] = upx; + orient[4] = upz; + orient[5] =-upy; + alListener3f(AL_POSITION, x, z, -y); + alListenerfv(AL_ORIENTATION, orient.ptr); + checkALError(); } }