mirror of
https://github.com/rwengine/openrw.git
synced 2024-11-09 20:32:43 +01:00
Add Cutscene Audio, via libmad
+ MADStream probably needs a good look over, for saftey's sake
This commit is contained in:
parent
51fa23a04c
commit
97ad0414f7
@ -9,6 +9,6 @@ add_library(rwengine
|
|||||||
${RENDERWARE_HEADERS}
|
${RENDERWARE_HEADERS}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(rwengine sfml-window GLEW)
|
target_link_libraries(rwengine sfml-window sfml-audio GLEW mad)
|
||||||
|
|
||||||
include_directories(include /usr/include/bullet)
|
include_directories(include /usr/include/bullet)
|
||||||
|
161
rwengine/include/audio/MADStream.hpp
Normal file
161
rwengine/include/audio/MADStream.hpp
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef _MADSTREAM_HPP_
|
||||||
|
#define _MADSTREAM_HPP_
|
||||||
|
#include <mad.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <SFML/Audio.hpp>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
static inline
|
||||||
|
signed int scale(mad_fixed_t sample)
|
||||||
|
{
|
||||||
|
double s = mad_f_todouble(sample);
|
||||||
|
|
||||||
|
/* round */
|
||||||
|
sample += (1L << (MAD_F_FRACBITS - 16));
|
||||||
|
|
||||||
|
/* clip */
|
||||||
|
if (sample >= MAD_F_ONE)
|
||||||
|
sample = MAD_F_ONE - 1;
|
||||||
|
else if (sample < -MAD_F_ONE)
|
||||||
|
sample = -MAD_F_ONE;
|
||||||
|
|
||||||
|
/* quantize */
|
||||||
|
return sample >> (MAD_F_FRACBITS + 1 - 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class MADStream : public sf::SoundStream
|
||||||
|
{
|
||||||
|
mad_decoder mDecoder;
|
||||||
|
unsigned int mMadSampleRate;
|
||||||
|
unsigned int mMadChannels;
|
||||||
|
unsigned char* mFdm;
|
||||||
|
struct stat mStat;
|
||||||
|
unsigned int mReadProgress;
|
||||||
|
std::vector<int16_t> mCurrentSamples;
|
||||||
|
|
||||||
|
static mad_flow ms_header(void* user, mad_header const* header)
|
||||||
|
{
|
||||||
|
MADStream* stream = static_cast<MADStream*>(user);
|
||||||
|
|
||||||
|
stream->mMadSampleRate = header->samplerate;
|
||||||
|
// see enum mad_mode
|
||||||
|
stream->mMadChannels = header->mode + 1;
|
||||||
|
|
||||||
|
return MAD_FLOW_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mad_flow ms_input(void* user, mad_stream* stream)
|
||||||
|
{
|
||||||
|
MADStream* self = static_cast<MADStream*>(user);
|
||||||
|
|
||||||
|
if(! self->mReadProgress ) {
|
||||||
|
return MAD_FLOW_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rd = self->mReadProgress;
|
||||||
|
self->mReadProgress = 0;
|
||||||
|
|
||||||
|
mad_stream_buffer(stream, self->mFdm, rd);
|
||||||
|
|
||||||
|
return MAD_FLOW_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mad_flow ms_output(void* user, mad_header const* header,
|
||||||
|
mad_pcm* pcm)
|
||||||
|
{
|
||||||
|
MADStream* self = static_cast<MADStream*>(user);
|
||||||
|
|
||||||
|
int nsamples = pcm->length;
|
||||||
|
mad_fixed_t const *left, *right;
|
||||||
|
|
||||||
|
left = pcm->samples[0];
|
||||||
|
right = pcm->samples[1];
|
||||||
|
|
||||||
|
int s = 0;
|
||||||
|
while( (nsamples) -- ) {
|
||||||
|
signed int sample = *left++;
|
||||||
|
self->mCurrentSamples.push_back(scale(sample));
|
||||||
|
|
||||||
|
sample = *right++;
|
||||||
|
self->mCurrentSamples.push_back(scale(sample));
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MAD_FLOW_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mad_flow ms_error(void* user, mad_stream* stream, mad_frame* frame)
|
||||||
|
{
|
||||||
|
sf::err() << "libmad error: " << mad_stream_errorstr(stream);
|
||||||
|
return MAD_FLOW_BREAK;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool onGetData(sf::SoundStream::Chunk& data)
|
||||||
|
{
|
||||||
|
data.samples = mCurrentSamples.data();
|
||||||
|
data.sampleCount = mCurrentSamples.size();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void onSeek(sf::Time timeOffset)
|
||||||
|
{
|
||||||
|
/// @todo support seeking.
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
MADStream()
|
||||||
|
: mFdm(nullptr)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
~MADStream()
|
||||||
|
{
|
||||||
|
if( mFdm )
|
||||||
|
{
|
||||||
|
munmap( mFdm, mStat.st_size );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool open(const std::string& loc)
|
||||||
|
{
|
||||||
|
if( mFdm ) {
|
||||||
|
munmap( mFdm, mStat.st_size );
|
||||||
|
mCurrentSamples.clear();
|
||||||
|
mad_decoder_finish(&mDecoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd = ::open(loc.c_str(), O_RDONLY);
|
||||||
|
|
||||||
|
if( fstat(fd, &mStat) == -1 || mStat.st_size == 0) {
|
||||||
|
sf::err() << "Fstat failed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void* m = mmap(0, mStat.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
|
if( m == MAP_FAILED ) {
|
||||||
|
sf::err() << "mmap failed\n";
|
||||||
|
}
|
||||||
|
mFdm = (unsigned char*)m;
|
||||||
|
mReadProgress = mStat.st_size;
|
||||||
|
|
||||||
|
mad_decoder_init(&mDecoder, this,
|
||||||
|
ms_input, ms_header, 0, ms_output, ms_error, 0);
|
||||||
|
|
||||||
|
int res = mad_decoder_run(&mDecoder, MAD_DECODER_MODE_SYNC);
|
||||||
|
|
||||||
|
this->initialize(2, mMadSampleRate);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -19,6 +19,8 @@ struct CutsceneMetadata
|
|||||||
std::string gxt;
|
std::string gxt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
|
||||||
/// The origin for coordinates in the cutscene
|
/// The origin for coordinates in the cutscene
|
||||||
glm::vec3 sceneOffset;
|
glm::vec3 sceneOffset;
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
#include <data/CollisionModel.hpp>
|
#include <data/CollisionModel.hpp>
|
||||||
#include <data/GameTexts.hpp>
|
#include <data/GameTexts.hpp>
|
||||||
|
|
||||||
|
#include <audio/MADStream.hpp>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
struct DynamicObjectData;
|
struct DynamicObjectData;
|
||||||
@ -148,6 +150,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
void loadWeaponDAT(const std::string& name);
|
void loadWeaponDAT(const std::string& name);
|
||||||
|
|
||||||
|
bool loadAudio(MADStream &music, const std::string& name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a pointer to the named file if it is available,
|
* Returns a pointer to the named file if it is available,
|
||||||
* the memory must be freed by the caller.
|
* the memory must be freed by the caller.
|
||||||
|
@ -256,9 +256,12 @@ public:
|
|||||||
* @param name
|
* @param name
|
||||||
*/
|
*/
|
||||||
void loadCutscene(const std::string& name);
|
void loadCutscene(const std::string& name);
|
||||||
|
void startCutscene();
|
||||||
void clearCutscene();
|
void clearCutscene();
|
||||||
|
|
||||||
|
MADStream fgAudio;
|
||||||
|
void playForegroundAudio(const std::string& name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief loads a model into a special character slot.
|
* @brief loads a model into a special character slot.
|
||||||
*/
|
*/
|
||||||
|
@ -617,6 +617,11 @@ void GameData::loadWeaponDAT(const std::string &name)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GameData::loadAudio(MADStream& music, const std::string &name)
|
||||||
|
{
|
||||||
|
return music.open(datpath + "/audio/" + name);
|
||||||
|
}
|
||||||
|
|
||||||
char* GameData::openFile(const std::string& name)
|
char* GameData::openFile(const std::string& name)
|
||||||
{
|
{
|
||||||
auto i = _knownFiles.find(name);
|
auto i = _knownFiles.find(name);
|
||||||
|
@ -695,9 +695,16 @@ void GameWorld::loadCutscene(const std::string &name)
|
|||||||
delete state.currentCutscene;
|
delete state.currentCutscene;
|
||||||
}
|
}
|
||||||
state.currentCutscene = cutscene;
|
state.currentCutscene = cutscene;
|
||||||
|
state.currentCutscene->meta.name = name;
|
||||||
std::cout << "Loaded cutscene: " << name << std::endl;
|
std::cout << "Loaded cutscene: " << name << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameWorld::startCutscene()
|
||||||
|
{
|
||||||
|
state.cutsceneStartTime = gameTime;
|
||||||
|
playForegroundAudio(state.currentCutscene->meta.name);
|
||||||
|
}
|
||||||
|
|
||||||
void GameWorld::clearCutscene()
|
void GameWorld::clearCutscene()
|
||||||
{
|
{
|
||||||
/// @todo replace with the queued deletion from the projectile branch
|
/// @todo replace with the queued deletion from the projectile branch
|
||||||
@ -707,11 +714,20 @@ void GameWorld::clearCutscene()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fgAudio.stop();
|
||||||
|
|
||||||
delete state.currentCutscene;
|
delete state.currentCutscene;
|
||||||
state.currentCutscene = nullptr;
|
state.currentCutscene = nullptr;
|
||||||
state.cutsceneStartTime = -1.f;
|
state.cutsceneStartTime = -1.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameWorld::playForegroundAudio(const std::string &name)
|
||||||
|
{
|
||||||
|
if( gameData.loadAudio(fgAudio, name+".mp3") ) {
|
||||||
|
fgAudio.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GameWorld::loadSpecialCharacter(const unsigned short index, const std::string &name)
|
void GameWorld::loadSpecialCharacter(const unsigned short index, const std::string &name)
|
||||||
{
|
{
|
||||||
std::string lowerName(name);
|
std::string lowerName(name);
|
||||||
|
@ -425,7 +425,7 @@ VM_OPCODE_DEF( 0x02E6 )
|
|||||||
}
|
}
|
||||||
VM_OPCODE_DEF( 0x02E7 )
|
VM_OPCODE_DEF( 0x02E7 )
|
||||||
{
|
{
|
||||||
m->getWorld()->state.cutsceneStartTime = m->getWorld()->gameTime;
|
m->getWorld()->startCutscene();
|
||||||
}
|
}
|
||||||
VM_OPCODE_DEF( 0x02E8 )
|
VM_OPCODE_DEF( 0x02E8 )
|
||||||
{
|
{
|
||||||
|
@ -506,5 +506,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete gta;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user