mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-24 00:39:49 +00:00
Music jukebox is now handled by Monster!
git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@64 ea6a568a-9f4f-0410-981a-c910a81bb256
This commit is contained in:
parent
d1ea2acd93
commit
54f598d2e7
@ -81,15 +81,15 @@ void toggleBattle()
|
|||||||
if(battle)
|
if(battle)
|
||||||
{
|
{
|
||||||
writefln("Changing to normal music");
|
writefln("Changing to normal music");
|
||||||
jukebox.resumeMusic();
|
jukebox.resume();
|
||||||
battleMusic.pauseMusic();
|
battleMusic.pause();
|
||||||
battle=false;
|
battle=false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
writefln("Changing to battle music");
|
writefln("Changing to battle music");
|
||||||
jukebox.pauseMusic();
|
jukebox.pause();
|
||||||
battleMusic.resumeMusic();
|
battleMusic.resume();
|
||||||
battle=true;
|
battle=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,8 +298,8 @@ extern(C) int d_frameStarted(float time)
|
|||||||
musCumTime += time;
|
musCumTime += time;
|
||||||
if(musCumTime > musRefresh)
|
if(musCumTime > musRefresh)
|
||||||
{
|
{
|
||||||
jukebox.addTime(musRefresh);
|
jukebox.updateBuffers();
|
||||||
battleMusic.addTime(musRefresh);
|
battleMusic.updateBuffers();
|
||||||
musCumTime -= musRefresh;
|
musCumTime -= musRefresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ import monster.vm.error;
|
|||||||
import std.string;
|
import std.string;
|
||||||
import std.uni;
|
import std.uni;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
import std.utf;
|
||||||
|
|
||||||
// An index to an array. Array indices may be 0, unlike object indices
|
// 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'
|
// 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!(dchar) create;
|
||||||
alias createT!(AIndex) create;
|
alias createT!(AIndex) create;
|
||||||
|
|
||||||
|
ArrayRef *create(char[] arg)
|
||||||
|
{ return create(toUTF32(arg)); }
|
||||||
|
|
||||||
// Generic element size
|
// Generic element size
|
||||||
ArrayRef *create(int[] data, int size)
|
ArrayRef *create(int[] data, int size)
|
||||||
{
|
{
|
||||||
|
@ -99,7 +99,7 @@ struct CodeStack
|
|||||||
fleft = 0;
|
fleft = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fleft = left + (frm-pos);
|
fleft = left + (pos-frm);
|
||||||
assert(fleft >= 0 && fleft <= total);
|
assert(fleft >= 0 && fleft <= total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
212
mscripts/jukebox.mn
Normal file
212
mscripts/jukebox.mn
Normal file
@ -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;
|
module mscripts.object;
|
||||||
|
|
||||||
import monster.monster;
|
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.
|
// This is the base class of all OpenMW Monster classes.
|
||||||
class Object;
|
class Object;
|
||||||
|
|
||||||
|
2
openmw.d
2
openmw.d
@ -347,7 +347,7 @@ void main(char[][] args)
|
|||||||
initializeInput();
|
initializeInput();
|
||||||
|
|
||||||
// Start swangin'
|
// Start swangin'
|
||||||
if(!noSound) jukebox.enableMusic();
|
if(!noSound) jukebox.play();
|
||||||
|
|
||||||
// Run it until the user tells us to quit
|
// Run it until the user tells us to quit
|
||||||
startRendering();
|
startRendering();
|
||||||
|
@ -26,8 +26,6 @@ module sound.audio;
|
|||||||
public import sound.sfx;
|
public import sound.sfx;
|
||||||
public import sound.music;
|
public import sound.music;
|
||||||
|
|
||||||
import monster.monster;
|
|
||||||
|
|
||||||
import sound.al;
|
import sound.al;
|
||||||
import sound.alc;
|
import sound.alc;
|
||||||
|
|
||||||
@ -56,14 +54,16 @@ void initializeSound()
|
|||||||
|
|
||||||
alcMakeContextCurrent(Context);
|
alcMakeContextCurrent(Context);
|
||||||
|
|
||||||
|
MusicManager.sinit();
|
||||||
|
|
||||||
jukebox.initialize("Main");
|
jukebox.initialize("Main");
|
||||||
battleMusic.initialize("Battle");
|
battleMusic.initialize("Battle");
|
||||||
}
|
}
|
||||||
|
|
||||||
void shutdownSound()
|
void shutdownSound()
|
||||||
{
|
{
|
||||||
jukebox.disableMusic();
|
jukebox.shutdown();
|
||||||
battleMusic.disableMusic();
|
battleMusic.shutdown();
|
||||||
|
|
||||||
alcMakeContextCurrent(null);
|
alcMakeContextCurrent(null);
|
||||||
if(Context) alcDestroyContext(Context);
|
if(Context) alcDestroyContext(Context);
|
||||||
@ -72,14 +72,14 @@ void shutdownSound()
|
|||||||
Device = null;
|
Device = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkALError(char[] what = "")
|
void checkALError(char[] what = "")
|
||||||
{
|
{
|
||||||
ALenum err = alGetError();
|
ALenum err = alGetError();
|
||||||
if(what.length) what = " while " ~ what;
|
if(what.length) what = " while " ~ what;
|
||||||
if(err != AL_NO_ERROR)
|
if(err != AL_NO_ERROR)
|
||||||
writefln("WARNING: OpenAL error%s: (%x) %s", what, err,
|
throw new Exception(format("OpenAL error%s: (%x) %s", what, err,
|
||||||
toString(alGetString(err)));
|
toString(alGetString(err))));
|
||||||
|
|
||||||
|
|
||||||
return err != AL_NO_ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool noALError()
|
||||||
|
{ return alGetError() == AL_NO_ERROR; }
|
||||||
|
388
sound/music.d
388
sound/music.d
@ -27,27 +27,36 @@ import sound.avcodec;
|
|||||||
import sound.audio;
|
import sound.audio;
|
||||||
import sound.al;
|
import sound.al;
|
||||||
|
|
||||||
|
import monster.monster;
|
||||||
|
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
import std.string;
|
import std.string;
|
||||||
|
|
||||||
import core.config;
|
import core.config;
|
||||||
import core.resource;
|
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.
|
// Simple music player, has a playlist and can pause/resume music.
|
||||||
struct MusicManager
|
struct MusicManager
|
||||||
{
|
{
|
||||||
private:
|
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
|
// Maximum buffer length, divided up among OpenAL buffers
|
||||||
const uint bufLength = 128*1024;
|
const uint bufLength = 128*1024;
|
||||||
|
|
||||||
// Volume
|
|
||||||
ALfloat volume, maxVolume;
|
|
||||||
|
|
||||||
char[] name;
|
char[] name;
|
||||||
|
|
||||||
void fail(char[] msg)
|
void fail(char[] msg)
|
||||||
@ -55,115 +64,122 @@ struct MusicManager
|
|||||||
throw new SoundException(name ~ " Jukebox", msg);
|
throw new SoundException(name ~ " Jukebox", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// List of songs to play
|
ALuint sID; // Sound id
|
||||||
char[][] playlist;
|
ALuint bIDs[4]; // Buffers
|
||||||
int index; // Index of next song to play
|
|
||||||
|
|
||||||
bool musicOn;
|
|
||||||
ALuint sID;
|
|
||||||
ALuint bIDs[4];
|
|
||||||
|
|
||||||
ALenum bufFormat;
|
ALenum bufFormat;
|
||||||
ALint bufRate;
|
ALint bufRate;
|
||||||
|
|
||||||
AVFile fileHandle;
|
AVFile fileHandle;
|
||||||
AVAudio audioHandle;
|
AVAudio audioHandle;
|
||||||
ubyte[] outData;
|
|
||||||
|
|
||||||
// Which direction are we currently fading, if any
|
static ubyte[] outData;
|
||||||
enum Fade { None = 0, In, Out }
|
|
||||||
Fade fading;
|
// The Jukebox class
|
||||||
|
static MonsterClass mc;
|
||||||
|
|
||||||
|
// The jukebox Monster object
|
||||||
|
MonsterObject *mo;
|
||||||
|
|
||||||
public:
|
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
|
// Initialize the jukebox
|
||||||
void initialize(char[] name)
|
void initialize(char[] name)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
sID = 0;
|
sID = 0;
|
||||||
foreach(ref b; bIDs) b = 0;
|
bIDs[] = 0;
|
||||||
outData.length = bufLength / bIDs.length;
|
|
||||||
fileHandle = null;
|
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()
|
void updateVolume()
|
||||||
{
|
{
|
||||||
maxVolume = config.calcMusicVolume();
|
stack.pushFloat(config.calcMusicVolume());
|
||||||
|
mo.call("updateVolume");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give a music play list
|
// Give a music play list
|
||||||
void setPlaylist(char[][] pl)
|
void setPlaylist(char[][] pl)
|
||||||
{
|
{
|
||||||
playlist = pl;
|
AIndex arr[];
|
||||||
index = 0;
|
arr.length = pl.length;
|
||||||
|
|
||||||
randomize();
|
// Create the array indices for each element string
|
||||||
|
foreach(i, ref elm; arr)
|
||||||
|
elm = arrays.create(pl[i]).getIndex();
|
||||||
|
|
||||||
|
// Push the final array
|
||||||
|
stack.pushArray(arr);
|
||||||
|
|
||||||
|
mo.call("setPlaylist");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Randomize playlist. If the argument is true, then we don't want
|
// Pause current track
|
||||||
// the old last to be the new first.
|
void pause() { mo.call("pause"); }
|
||||||
private void randomize(bool checklast = false)
|
|
||||||
{
|
|
||||||
if(playlist.length < 2) return;
|
|
||||||
|
|
||||||
// Get the index of the last song played
|
// Resume. Starts playing sound, with fade in
|
||||||
int lastidx = ((index==0) ? (playlist.length-1) : (index-1));
|
void resume()
|
||||||
|
|
||||||
foreach(int i, char[] s; playlist)
|
|
||||||
{
|
{
|
||||||
int idx = rnd.randInt(i,playlist.length-1);
|
if(!config.useMusic) return;
|
||||||
|
mo.call("resume");
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip to the next track
|
void play()
|
||||||
void playNext()
|
|
||||||
{
|
{
|
||||||
// If music is disabled, do nothing
|
if(!config.useMusic) return;
|
||||||
if(!musicOn) return;
|
mo.call("play");
|
||||||
|
}
|
||||||
|
|
||||||
// No tracks to play?
|
void setSound()
|
||||||
if(!playlist.length) return;
|
{
|
||||||
|
char[] fname = stack.popString8();
|
||||||
|
|
||||||
// Generate a source to play back with if needed
|
// Generate a source to play back with if needed
|
||||||
if(!sID)
|
if(!sID)
|
||||||
{
|
{
|
||||||
alGenSources(1, &sID);
|
alGenSources(1, &sID);
|
||||||
if(checkALError())
|
checkALError("generating buffers");
|
||||||
fail("Couldn't generate music sources");
|
|
||||||
|
// Set listner relative coordinates (sound follows the player)
|
||||||
alSourcei(sID, AL_SOURCE_RELATIVE, AL_TRUE);
|
alSourcei(sID, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||||
|
|
||||||
|
alGenBuffers(bIDs.length, bIDs.ptr);
|
||||||
|
|
||||||
updateVolume();
|
updateVolume();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Kill current track
|
// Kill current track, but keep the sID source.
|
||||||
alSourceStop(sID);
|
alSourceStop(sID);
|
||||||
alSourcei(sID, AL_BUFFER, 0);
|
alSourcei(sID, AL_BUFFER, 0);
|
||||||
alDeleteBuffers(bIDs.length, bIDs.ptr);
|
//alDeleteBuffers(bIDs.length, bIDs.ptr);
|
||||||
bIDs[] = 0;
|
//bIDs[] = 0;
|
||||||
checkALError("killing current track");
|
checkALError("killing current track");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,57 +187,30 @@ struct MusicManager
|
|||||||
fileHandle = null;
|
fileHandle = null;
|
||||||
audioHandle = null;
|
audioHandle = null;
|
||||||
|
|
||||||
// End of list? Randomize and start over
|
//alGenBuffers(bIDs.length, bIDs.ptr);
|
||||||
if(index == playlist.length)
|
|
||||||
{
|
|
||||||
randomize(true);
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
alGenBuffers(bIDs.length, bIDs.ptr);
|
|
||||||
|
|
||||||
// If something fails, clean everything up.
|
// If something fails, clean everything up.
|
||||||
scope(failure)
|
scope(failure) shutdown();
|
||||||
{
|
|
||||||
// This block is only executed if an exception is thrown.
|
|
||||||
|
|
||||||
if(fileHandle) avc_closeAVFile(fileHandle);
|
fileHandle = avc_openAVFile(toStringz(fname));
|
||||||
|
|
||||||
fileHandle = null;
|
|
||||||
audioHandle = null;
|
|
||||||
|
|
||||||
alSourceStop(sID);
|
|
||||||
alDeleteSources(1, &sID);
|
|
||||||
alDeleteBuffers(bIDs.length, bIDs.ptr);
|
|
||||||
|
|
||||||
checkALError("cleaning up after music failure");
|
|
||||||
|
|
||||||
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]));
|
|
||||||
if(!fileHandle)
|
if(!fileHandle)
|
||||||
fail("Unable to open " ~ playlist[index]);
|
fail("Unable to open " ~ fname);
|
||||||
|
|
||||||
audioHandle = avc_getAVAudioStream(fileHandle, 0);
|
audioHandle = avc_getAVAudioStream(fileHandle, 0);
|
||||||
if(!audioHandle)
|
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)
|
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;
|
bufRate = rate;
|
||||||
bufFormat = 0;
|
bufFormat = 0;
|
||||||
|
|
||||||
|
// TODO: These don't really fail gracefully for 4 and 6 channels
|
||||||
|
// if these aren't supported.
|
||||||
if(bits == 8)
|
if(bits == 8)
|
||||||
{
|
{
|
||||||
if(ch == 1) bufFormat = AL_FORMAT_MONO8;
|
if(ch == 1) bufFormat = AL_FORMAT_MONO8;
|
||||||
@ -244,16 +233,17 @@ struct MusicManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(bufFormat == 0)
|
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)
|
foreach(int i, ref b; bIDs)
|
||||||
{
|
{
|
||||||
int length = avc_getAVAudioData(audioHandle, outData.ptr, outData.length);
|
int length = avc_getAVAudioData(audioHandle, outData.ptr, outData.length);
|
||||||
if(length) alBufferData(b, bufFormat, outData.ptr, length, bufRate);
|
if(length) alBufferData(b, bufFormat, outData.ptr, length, bufRate);
|
||||||
if(length == 0 || checkALError())
|
if(length == 0 || !noALError())
|
||||||
{
|
{
|
||||||
if(i == 0)
|
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);
|
alDeleteBuffers(bIDs.length-i, bIDs.ptr+i);
|
||||||
checkALError("running alDeleteBuffers");
|
checkALError("running alDeleteBuffers");
|
||||||
@ -262,28 +252,74 @@ struct MusicManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Associate the buffers with the sound id
|
||||||
alSourceQueueBuffers(sID, bIDs.length, bIDs.ptr);
|
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 setVolume()
|
||||||
void enableMusic()
|
|
||||||
{
|
{
|
||||||
if(!config.useMusic) return;
|
float volume = stack.popFloat();
|
||||||
|
|
||||||
musicOn = true;
|
// Set the new volume
|
||||||
fading = Fade.None;
|
if(sID) alSourcef(sID, AL_GAIN, volume);
|
||||||
playNext();
|
}
|
||||||
|
|
||||||
|
void playSound()
|
||||||
|
{
|
||||||
|
if(!sID || !config.useMusic)
|
||||||
|
return;
|
||||||
|
|
||||||
|
alSourcePlay(sID);
|
||||||
|
checkALError("starting music");
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopSound()
|
||||||
|
{
|
||||||
|
// How to stop / pause music
|
||||||
|
if(sID) alSourcePause(sID);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPlaying()
|
||||||
|
{
|
||||||
|
ALint state;
|
||||||
|
alGetSourcei(sID, AL_SOURCE_STATE, &state);
|
||||||
|
|
||||||
|
return state == AL_PLAYING;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateBuffers()
|
||||||
|
{
|
||||||
|
if(!sID || !isPlaying)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get the number of processed buffers
|
||||||
|
ALint count;
|
||||||
|
alGetSourcei(sID, AL_BUFFERS_PROCESSED, &count);
|
||||||
|
|
||||||
|
checkALError();
|
||||||
|
|
||||||
|
for(int i = 0;i < count;i++)
|
||||||
|
{
|
||||||
|
int length = avc_getAVAudioData(audioHandle, outData.ptr, outData.length);
|
||||||
|
if(length <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ALuint bid;
|
||||||
|
alSourceUnqueueBuffers(sID, 1, &bid);
|
||||||
|
if(noALError())
|
||||||
|
{
|
||||||
|
alBufferData(bid, bufFormat, outData.ptr, length, bufRate);
|
||||||
|
alSourceQueueBuffers(sID, 1, &bid);
|
||||||
|
checkALError();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable music
|
// Disable music
|
||||||
void disableMusic()
|
void shutdown()
|
||||||
{
|
{
|
||||||
|
mo.call("stop");
|
||||||
|
|
||||||
if(fileHandle) avc_closeAVFile(fileHandle);
|
if(fileHandle) avc_closeAVFile(fileHandle);
|
||||||
fileHandle = null;
|
fileHandle = null;
|
||||||
audioHandle = null;
|
audioHandle = null;
|
||||||
@ -297,109 +333,7 @@ struct MusicManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
alDeleteBuffers(bIDs.length, bIDs.ptr);
|
alDeleteBuffers(bIDs.length, bIDs.ptr);
|
||||||
checkALError("deleting music buffers");
|
|
||||||
bIDs[] = 0;
|
|
||||||
|
|
||||||
musicOn = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pause current track
|
|
||||||
void pauseMusic()
|
|
||||||
{
|
|
||||||
fading = Fade.Out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resume. Can also be called in place of enableMusic for fading in.
|
|
||||||
void resumeMusic()
|
|
||||||
{
|
|
||||||
if(!config.useMusic) return;
|
|
||||||
|
|
||||||
volume = 0.0;
|
|
||||||
fading = Fade.In;
|
|
||||||
musicOn = true;
|
|
||||||
if(sID) addTime(0);
|
|
||||||
else playNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
ALint count;
|
|
||||||
alGetSourcei(sID, AL_BUFFERS_PROCESSED, &count);
|
|
||||||
if(checkALError("in isPlaying()")) return false;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ALuint bid;
|
|
||||||
alSourceUnqueueBuffers(sID, 1, &bid);
|
|
||||||
if(checkALError() == AL_NO_ERROR)
|
|
||||||
{
|
|
||||||
alBufferData(bid, bufFormat, outData.ptr, length, bufRate);
|
|
||||||
alSourceQueueBuffers(sID, 1, &bid);
|
|
||||||
checkALError();
|
checkALError();
|
||||||
}
|
bIDs[] = 0;
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if(!musicOn) return;
|
|
||||||
|
|
||||||
if(!isPlaying()) playNext();
|
|
||||||
|
|
||||||
if(fading)
|
|
||||||
{
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ struct SoundFile
|
|||||||
{
|
{
|
||||||
alGenBuffers(1, &bID);
|
alGenBuffers(1, &bID);
|
||||||
alBufferData(bID, fmt, outData.ptr, total, rate);
|
alBufferData(bID, fmt, outData.ptr, total, rate);
|
||||||
if(checkALError())
|
if(!noALError())
|
||||||
{
|
{
|
||||||
writefln("Unable to load sound %s", file);
|
writefln("Unable to load sound %s", file);
|
||||||
alDeleteBuffers(1, &bID);
|
alDeleteBuffers(1, &bID);
|
||||||
@ -140,11 +140,11 @@ struct SoundFile
|
|||||||
SoundInstance si;
|
SoundInstance si;
|
||||||
si.owner = this;
|
si.owner = this;
|
||||||
alGenSources(1, &si.inst);
|
alGenSources(1, &si.inst);
|
||||||
if(checkALError() || !si.inst)
|
if(!noALError() || !si.inst)
|
||||||
fail("Failed to instantiate sound resource");
|
fail("Failed to instantiate sound resource");
|
||||||
|
|
||||||
alSourcei(si.inst, AL_BUFFER, cast(ALint)bID);
|
alSourcei(si.inst, AL_BUFFER, cast(ALint)bID);
|
||||||
if(checkALError())
|
if(!noALError())
|
||||||
{
|
{
|
||||||
alDeleteSources(1, &si.inst);
|
alDeleteSources(1, &si.inst);
|
||||||
fail("Failed to load sound resource");
|
fail("Failed to load sound resource");
|
||||||
@ -237,7 +237,7 @@ struct SoundInstance
|
|||||||
alGetSourcef(inst, AL_MAX_DISTANCE, &dist);
|
alGetSourcef(inst, AL_MAX_DISTANCE, &dist);
|
||||||
alGetSourcefv(inst, AL_POSITION, p.ptr);
|
alGetSourcefv(inst, AL_POSITION, p.ptr);
|
||||||
alGetListenerfv(AL_POSITION, lp.ptr);
|
alGetListenerfv(AL_POSITION, lp.ptr);
|
||||||
if(!checkALError("updating sound position"))
|
if(noALError())
|
||||||
{
|
{
|
||||||
p[0] -= lp[0];
|
p[0] -= lp[0];
|
||||||
p[1] -= lp[1];
|
p[1] -= lp[1];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user