Music jukebox is now handled by Monster!

git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@64 ea6a568a-9f4f-0410-981a-c910a81bb256
actorid
nkorslund 16 years ago
parent d1ea2acd93
commit 54f598d2e7

@ -81,15 +81,15 @@ void toggleBattle()
if(battle)
{
writefln("Changing to normal music");
jukebox.resumeMusic();
battleMusic.pauseMusic();
jukebox.resume();
battleMusic.pause();
battle=false;
}
else
{
writefln("Changing to battle music");
jukebox.pauseMusic();
battleMusic.resumeMusic();
jukebox.pause();
battleMusic.resume();
battle=true;
}
}
@ -298,8 +298,8 @@ extern(C) int d_frameStarted(float time)
musCumTime += time;
if(musCumTime > musRefresh)
{
jukebox.addTime(musRefresh);
battleMusic.addTime(musRefresh);
jukebox.updateBuffers();
battleMusic.updateBuffers();
musCumTime -= musRefresh;
}

@ -30,6 +30,7 @@ import monster.vm.error;
import std.string;
import std.uni;
import std.stdio;
import std.utf;
// An index to an array. Array indices may be 0, unlike object indices
// which span from 1 and upwards, and has 0 as the illegal 'null'
@ -152,6 +153,9 @@ struct Arrays
alias createT!(dchar) create;
alias createT!(AIndex) create;
ArrayRef *create(char[] arg)
{ return create(toUTF32(arg)); }
// Generic element size
ArrayRef *create(int[] data, int size)
{

@ -99,7 +99,7 @@ struct CodeStack
fleft = 0;
return;
}
fleft = left + (frm-pos);
fleft = left + (pos-frm);
assert(fleft >= 0 && fleft <= total);
}

@ -0,0 +1,212 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (jukebox.mn) 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/ .
*/
/*
A simple jukebox with a playlist. It can play, stop, pause with
fade in and fade out, and adjust volume.
*/
class Jukebox : Object;
// Between 0 (off) and 1 (full volume)
float fadeLevel = 0.0;
// How much to fade in and out each second
float fadeInRate = 0.10;
float fadeOutRate = 0.25;
// Time between each fade step
float fadeInterval = 0.2;
// List of sounds to play
char[][] playlist;
// Index of current song
int index;
// The music volume, set by the user. Does NOT change to adjust for
// fading, etc. TODO: This should be stored in a configuration class,
// not here.
float musVolume;
bool isPlaying; // Is a song currently playing?
// TODO: Make "isPaused" instead, makes more sense
bool hasSong; // Is a song currently selected (playing or paused)
// TODO: Move to Object for now
native int randInt(int a, int b);
// Native functions to control music
native setSound(char[] filename);
native setVolume(float f);
native playSound();
native stopSound();
idle waitUntilFinished();
// Fade out and then stop the music. TODO: Rename these to resume()
// etc and use super.resume, when this is possible.
pause() { state = fadeOut; }
resume()
{
if(!hasSong) next();
else playSound();
state = fadeIn;
}
// Stop the current song. Calling resume again after this is called
// will start a new song.
stop()
{
stopSound();
hasSong = false;
isPlaying = false;
fadeLevel = 0.0;
state = null;
}
play()
{
if(index >= playlist.length)
return;
setSound(playlist[index]);
playSound();
isPlaying = true;
hasSong = true;
}
// Play the next song in the playlist
next()
{
if(isPlaying)
stop();
// Find the index of the next song, if any
if(playlist.length == 0) return;
if(++index >= playlist.length)
{
index = 0;
randomize();
}
play();
}
// Set the new music volume setting. TODO: This should be read from a
// config object instead.
updateVolume(float vol)
{
musVolume = vol;
if(isPlaying)
setVolume(musVolume*fadeLevel);
}
setPlaylist(char[][] lst)
{
playlist = lst;
randomize();
}
// Randomize playlist.
randomize()
{
if(playlist.length < 2) return;
foreach(int i, char[] s; playlist)
{
// Index to switch with
int idx = randInt(i,playlist.length-1);
// To avoid playing the same song twice in a row, don't set the
// first song to the previous last.
if(i == 0 && idx == playlist.length-1)
idx--;
if(idx == i) // Skip if swapping with self
continue;
playlist[i] = playlist[idx];
playlist[idx] = s;
}
}
// Fade in
state fadeIn
{
begin:
setVolume(musVolume*fadeLevel);
sleep(fadeInterval);
fadeLevel += fadeInterval*fadeInRate;
if(fadeLevel >= 1.0)
{
fadeLevel = 1.0;
setVolume(musVolume);
state = playing;
}
goto begin;
}
// Fade out
state fadeOut
{
begin:
sleep(fadeInterval);
fadeLevel -= fadeInterval*fadeOutRate;
if(fadeLevel <= 0.0)
{
fadeLevel = 0.0;
stopSound();
isPlaying = false;
state = null;
}
setVolume(musVolume*fadeLevel);
goto begin;
}
state playing
{
begin:
// Wait for the song to play. Will return imediately if the song has
// already stopped or if no song is playing
waitUntilFinished();
// Start playing the next song
next();
goto begin;
}

@ -1,3 +1,26 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (object.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/ .
*/
module mscripts.object;
import monster.monster;

@ -1,3 +1,26 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (object.mn) 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 is the base class of all OpenMW Monster classes.
class Object;

@ -347,7 +347,7 @@ void main(char[][] args)
initializeInput();
// Start swangin'
if(!noSound) jukebox.enableMusic();
if(!noSound) jukebox.play();
// Run it until the user tells us to quit
startRendering();

@ -26,8 +26,6 @@ module sound.audio;
public import sound.sfx;
public import sound.music;
import monster.monster;
import sound.al;
import sound.alc;
@ -56,14 +54,16 @@ void initializeSound()
alcMakeContextCurrent(Context);
MusicManager.sinit();
jukebox.initialize("Main");
battleMusic.initialize("Battle");
}
void shutdownSound()
{
jukebox.disableMusic();
battleMusic.disableMusic();
jukebox.shutdown();
battleMusic.shutdown();
alcMakeContextCurrent(null);
if(Context) alcDestroyContext(Context);
@ -72,14 +72,14 @@ void shutdownSound()
Device = null;
}
bool checkALError(char[] what = "")
void checkALError(char[] what = "")
{
ALenum err = alGetError();
if(what.length) what = " while " ~ what;
if(err != AL_NO_ERROR)
writefln("WARNING: OpenAL error%s: (%x) %s", what, err,
toString(alGetString(err)));
return err != AL_NO_ERROR;
ALenum err = alGetError();
if(what.length) what = " while " ~ what;
if(err != AL_NO_ERROR)
throw new Exception(format("OpenAL error%s: (%x) %s", what, err,
toString(alGetString(err))));
}
bool noALError()
{ return alGetError() == AL_NO_ERROR; }

@ -27,27 +27,36 @@ import sound.avcodec;
import sound.audio;
import sound.al;
import monster.monster;
import std.stdio;
import std.string;
import core.config;
import core.resource;
class Idle_waitUntilFinished : IdleFunction
{
override:
bool initiate(MonsterObject *mo) { return true; }
bool hasFinished(MonsterObject *mo)
{
MusicManager *mgr = cast(MusicManager*)mo.extra;
// Return when the music is no longer playing
return !mgr.isPlaying();
}
}
// Simple music player, has a playlist and can pause/resume music.
struct MusicManager
{
private:
// How much to add to the volume each second when fading
const float fadeInRate = 0.10;
const float fadeOutRate = 0.20;
// Maximum buffer length, divided up among OpenAL buffers
const uint bufLength = 128*1024;
// Volume
ALfloat volume, maxVolume;
char[] name;
void fail(char[] msg)
@ -55,115 +64,122 @@ struct MusicManager
throw new SoundException(name ~ " Jukebox", msg);
}
// List of songs to play
char[][] playlist;
int index; // Index of next song to play
bool musicOn;
ALuint sID;
ALuint bIDs[4];
ALuint sID; // Sound id
ALuint bIDs[4]; // Buffers
ALenum bufFormat;
ALint bufRate;
AVFile fileHandle;
AVAudio audioHandle;
ubyte[] outData;
// Which direction are we currently fading, if any
enum Fade { None = 0, In, Out }
Fade fading;
static ubyte[] outData;
// The Jukebox class
static MonsterClass mc;
// The jukebox Monster object
MonsterObject *mo;
public:
static MusicManager *get()
{ return cast(MusicManager*)params.obj().extra; }
static void sinit()
{
assert(mc is null);
mc = new MonsterClass("Jukebox", "jukebox.mn");
mc.bind("randInt",
{ stack.pushInt(rnd.randInt
(stack.popInt,stack.popInt));});
mc.bind("waitUntilFinished",
new Idle_waitUntilFinished);
mc.bind("setSound", { get().setSound(); });
mc.bind("setVolume", { get().setVolume(); });
mc.bind("playSound", { get().playSound(); });
mc.bind("stopSound", { get().stopSound(); });
outData.length = bufLength / bIDs.length;
}
// Initialize the jukebox
void initialize(char[] name)
{
this.name = name;
sID = 0;
foreach(ref b; bIDs) b = 0;
outData.length = bufLength / bIDs.length;
bIDs[] = 0;
fileHandle = null;
musicOn = false;
mo = mc.createObject();
mo.extra = this;
}
// Get the new volume setting.
// Called whenever the volume configuration values are changed by
// the user.
void updateVolume()
{
maxVolume = config.calcMusicVolume();
if(!musicOn) return;
// Adjust volume up to new setting, unless we are in the middle of
// a fade. Even if we are fading, though, the volume should never
// be over the max.
if(fading == Fade.None || volume > maxVolume) volume = maxVolume;
if(sID)
alSourcef(sID, AL_GAIN, volume);
stack.pushFloat(config.calcMusicVolume());
mo.call("updateVolume");
}
// Give a music play list
void setPlaylist(char[][] pl)
{
playlist = pl;
index = 0;
AIndex arr[];
arr.length = pl.length;
randomize();
}
// Create the array indices for each element string
foreach(i, ref elm; arr)
elm = arrays.create(pl[i]).getIndex();
// Randomize playlist. If the argument is true, then we don't want
// the old last to be the new first.
private void randomize(bool checklast = false)
{
if(playlist.length < 2) return;
// Push the final array
stack.pushArray(arr);
// Get the index of the last song played
int lastidx = ((index==0) ? (playlist.length-1) : (index-1));
mo.call("setPlaylist");
}
foreach(int i, char[] s; playlist)
{
int idx = rnd.randInt(i,playlist.length-1);
// Pause current track
void pause() { mo.call("pause"); }
// Don't put the last idx as the first entry
if(i == 0 && checklast && lastidx == idx)
{
idx++;
if(idx == playlist.length)
idx = i;
}
if(idx == i) /* skip if swapping with self */
continue;
playlist[i] = playlist[idx];
playlist[idx] = s;
}
// Resume. Starts playing sound, with fade in
void resume()
{
if(!config.useMusic) return;
mo.call("resume");
}
// Skip to the next track
void playNext()
void play()
{
// If music is disabled, do nothing
if(!musicOn) return;
if(!config.useMusic) return;
mo.call("play");
}
// No tracks to play?
if(!playlist.length) return;
void setSound()
{
char[] fname = stack.popString8();
// Generate a source to play back with if needed
if(!sID)
{
alGenSources(1, &sID);
if(checkALError())
fail("Couldn't generate music sources");
checkALError("generating buffers");
// Set listner relative coordinates (sound follows the player)
alSourcei(sID, AL_SOURCE_RELATIVE, AL_TRUE);
alGenBuffers(bIDs.length, bIDs.ptr);
updateVolume();
}
else
{
// Kill current track
// Kill current track, but keep the sID source.
alSourceStop(sID);
alSourcei(sID, AL_BUFFER, 0);
alDeleteBuffers(bIDs.length, bIDs.ptr);
bIDs[] = 0;
//alDeleteBuffers(bIDs.length, bIDs.ptr);
//bIDs[] = 0;
checkALError("killing current track");
}
@ -171,57 +187,30 @@ struct MusicManager
fileHandle = null;
audioHandle = null;
// End of list? Randomize and start over
if(index == playlist.length)
{
randomize(true);
index = 0;
}
alGenBuffers(bIDs.length, bIDs.ptr);
//alGenBuffers(bIDs.length, bIDs.ptr);
// If something fails, clean everything up.
scope(failure)
{
// This block is only executed if an exception is thrown.
if(fileHandle) avc_closeAVFile(fileHandle);
fileHandle = null;
audioHandle = null;
alSourceStop(sID);
alDeleteSources(1, &sID);
alDeleteBuffers(bIDs.length, bIDs.ptr);
checkALError("cleaning up after music failure");
scope(failure) shutdown();
sID = 0;
bIDs[] = 0;
// Try the next track if playNext is called again
index++;
// The function exits here.
}
if(checkALError())
fail("Couldn't generate buffers");
fileHandle = avc_openAVFile(toStringz(playlist[index]));
fileHandle = avc_openAVFile(toStringz(fname));
if(!fileHandle)
fail("Unable to open " ~ playlist[index]);
fail("Unable to open " ~ fname);
audioHandle = avc_getAVAudioStream(fileHandle, 0);
if(!audioHandle)
fail("Unable to load music track " ~ playlist[index]);
fail("Unable to load music track " ~ fname);
int ch, bits, rate;
int rate, ch, bits;
if(avc_getAVAudioInfo(audioHandle, &rate, &ch, &bits) != 0)
fail("Unable to get info for music track " ~ playlist[index]);
fail("Unable to get info for music track " ~ fname);
// Translate format from avformat to OpenAL
bufRate = rate;
bufFormat = 0;
// TODO: These don't really fail gracefully for 4 and 6 channels
// if these aren't supported.
if(bits == 8)
{
if(ch == 1) bufFormat = AL_FORMAT_MONO8;
@ -244,16 +233,17 @@ struct MusicManager
}
if(bufFormat == 0)
fail(format("Unhandled format (%d channels, %d bits) for music track %s", ch, bits, playlist[index]));
fail(format("Unhandled format (%d channels, %d bits) for music track %s", ch, bits, fname));
// Fill the buffers
foreach(int i, ref b; bIDs)
{
int length = avc_getAVAudioData(audioHandle, outData.ptr, outData.length);
if(length) alBufferData(b, bufFormat, outData.ptr, length, bufRate);
if(length == 0 || checkALError())
if(length == 0 || !noALError())
{
if(i == 0)
fail("No audio data in music track " ~ playlist[index]);
fail("No audio data in music track " ~ fname);
alDeleteBuffers(bIDs.length-i, bIDs.ptr+i);
checkALError("running alDeleteBuffers");
@ -262,144 +252,88 @@ struct MusicManager
}
}
// Associate the buffers with the sound id
alSourceQueueBuffers(sID, bIDs.length, bIDs.ptr);
alSourcePlay(sID);
if(checkALError())
fail("Unable to start music track " ~ playlist[index]);
index++;
return;
}
// Start playing the jukebox
void enableMusic()
void setVolume()
{
if(!config.useMusic) return;
float volume = stack.popFloat();
musicOn = true;
fading = Fade.None;
playNext();
// Set the new volume
if(sID) alSourcef(sID, AL_GAIN, volume);
}
// Disable music
void disableMusic()
void playSound()
{
if(fileHandle) avc_closeAVFile(fileHandle);
fileHandle = null;
audioHandle = null;
if(sID)
{
alSourceStop(sID);
alDeleteSources(1, &sID);
checkALError("disabling music");
sID = 0;
}
if(!sID || !config.useMusic)
return;
alDeleteBuffers(bIDs.length, bIDs.ptr);
checkALError("deleting music buffers");
bIDs[] = 0;
musicOn = false;
alSourcePlay(sID);
checkALError("starting music");
}
// Pause current track
void pauseMusic()
void stopSound()
{
fading = Fade.Out;
// How to stop / pause music
if(sID) alSourcePause(sID);
}
// Resume. Can also be called in place of enableMusic for fading in.
void resumeMusic()
bool isPlaying()
{
if(!config.useMusic) return;
ALint state;
alGetSourcei(sID, AL_SOURCE_STATE, &state);
volume = 0.0;
fading = Fade.In;
musicOn = true;
if(sID) addTime(0);
else playNext();
return state == AL_PLAYING;
}
// Checks if a stream is playing, filling more data as needed, and restarting
// if it stalled or was paused.
private bool isPlaying()
void updateBuffers()
{
if(!sID) return false;
if(!sID || !isPlaying)
return;
// Get the number of processed buffers
ALint count;
alGetSourcei(sID, AL_BUFFERS_PROCESSED, &count);
if(checkALError("in isPlaying()")) return false;
checkALError();
for(int i = 0;i < count;i++)
{
int length = avc_getAVAudioData(audioHandle, outData.ptr, outData.length);
if(length <= 0)
{
if(i == 0)
{
ALint state;
alGetSourcei(sID, AL_SOURCE_STATE, &state);
if(checkALError() || state == AL_STOPPED)
return false;
}
break;
}
break;
ALuint bid;
alSourceUnqueueBuffers(sID, 1, &bid);
if(checkALError() == AL_NO_ERROR)
if(noALError())
{
alBufferData(bid, bufFormat, outData.ptr, length, bufRate);
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);
}
// Check if the music has died. This function is also used for fading.
void addTime(float time)
// Disable music
void shutdown()
{
if(!musicOn) return;
mo.call("stop");
if(!isPlaying()) playNext();
if(fileHandle) avc_closeAVFile(fileHandle);
fileHandle = null;
audioHandle = null;
if(fading)
if(sID)
{
// Fade the volume
if(fading == Fade.In)
{
volume += fadeInRate * time;
if(volume >= maxVolume)
{
fading = Fade.None;
volume = maxVolume;
}
}
else
{
assert(fading == Fade.Out);
volume -= fadeOutRate * time;
if(volume <= 0.0)
{
fading = Fade.None;
volume = 0.0;
// 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
if(sID) alSourcef(sID, AL_GAIN, volume);
alSourceStop(sID);
alDeleteSources(1, &sID);
checkALError("disabling music");
sID = 0;
}
alDeleteBuffers(bIDs.length, bIDs.ptr);
checkALError();
bIDs[] = 0;
}
}

@ -119,7 +119,7 @@ struct SoundFile
{
alGenBuffers(1, &bID);
alBufferData(bID, fmt, outData.ptr, total, rate);
if(checkALError())
if(!noALError())
{
writefln("Unable to load sound %s", file);
alDeleteBuffers(1, &bID);
@ -140,11 +140,11 @@ struct SoundFile
SoundInstance si;
si.owner = this;
alGenSources(1, &si.inst);
if(checkALError() || !si.inst)
if(!noALError() || !si.inst)
fail("Failed to instantiate sound resource");
alSourcei(si.inst, AL_BUFFER, cast(ALint)bID);
if(checkALError())
if(!noALError())
{
alDeleteSources(1, &si.inst);
fail("Failed to load sound resource");
@ -237,7 +237,7 @@ struct SoundInstance
alGetSourcef(inst, AL_MAX_DISTANCE, &dist);
alGetSourcefv(inst, AL_POSITION, p.ptr);
alGetListenerfv(AL_POSITION, lp.ptr);
if(!checkALError("updating sound position"))
if(noALError())
{
p[0] -= lp[0];
p[1] -= lp[1];

Loading…
Cancel
Save