mirror of
https://github.com/rwengine/openrw.git
synced 2024-11-26 04:12:41 +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}
|
||||
)
|
||||
|
||||
target_link_libraries(rwengine sfml-window GLEW)
|
||||
target_link_libraries(rwengine sfml-window sfml-audio GLEW mad)
|
||||
|
||||
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 name;
|
||||
|
||||
/// The origin for coordinates in the cutscene
|
||||
glm::vec3 sceneOffset;
|
||||
|
||||
|
@ -13,6 +13,8 @@
|
||||
#include <data/CollisionModel.hpp>
|
||||
#include <data/GameTexts.hpp>
|
||||
|
||||
#include <audio/MADStream.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
struct DynamicObjectData;
|
||||
@ -148,6 +150,8 @@ public:
|
||||
*/
|
||||
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,
|
||||
* the memory must be freed by the caller.
|
||||
|
@ -256,9 +256,12 @@ public:
|
||||
* @param name
|
||||
*/
|
||||
void loadCutscene(const std::string& name);
|
||||
|
||||
void startCutscene();
|
||||
void clearCutscene();
|
||||
|
||||
MADStream fgAudio;
|
||||
void playForegroundAudio(const std::string& name);
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
auto i = _knownFiles.find(name);
|
||||
|
@ -695,9 +695,16 @@ void GameWorld::loadCutscene(const std::string &name)
|
||||
delete state.currentCutscene;
|
||||
}
|
||||
state.currentCutscene = cutscene;
|
||||
state.currentCutscene->meta.name = name;
|
||||
std::cout << "Loaded cutscene: " << name << std::endl;
|
||||
}
|
||||
|
||||
void GameWorld::startCutscene()
|
||||
{
|
||||
state.cutsceneStartTime = gameTime;
|
||||
playForegroundAudio(state.currentCutscene->meta.name);
|
||||
}
|
||||
|
||||
void GameWorld::clearCutscene()
|
||||
{
|
||||
/// @todo replace with the queued deletion from the projectile branch
|
||||
@ -707,11 +714,20 @@ void GameWorld::clearCutscene()
|
||||
}
|
||||
}
|
||||
|
||||
fgAudio.stop();
|
||||
|
||||
delete state.currentCutscene;
|
||||
state.currentCutscene = nullptr;
|
||||
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)
|
||||
{
|
||||
std::string lowerName(name);
|
||||
|
@ -425,7 +425,7 @@ VM_OPCODE_DEF( 0x02E6 )
|
||||
}
|
||||
VM_OPCODE_DEF( 0x02E7 )
|
||||
{
|
||||
m->getWorld()->state.cutsceneStartTime = m->getWorld()->gameTime;
|
||||
m->getWorld()->startCutscene();
|
||||
}
|
||||
VM_OPCODE_DEF( 0x02E8 )
|
||||
{
|
||||
|
@ -506,5 +506,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
}
|
||||
|
||||
delete gta;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user