mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-08-07 15:35:40 +02:00

This adds a new dependency with libfuildsynth. MIDI support is built by default, but can be disabled if not desired. All RTP songs should work well, but there are known problems with other files (see README). Also, the pitch shift implementation is somewhat poor and doesn't match RMXP (at least subjectively). A soundfont is not included and must be provided by the user themself.
288 lines
5.7 KiB
C++
288 lines
5.7 KiB
C++
/*
|
|
** vorbissource.cpp
|
|
**
|
|
** This file is part of mkxp.
|
|
**
|
|
** Copyright (C) 2014 Jonas Kulla <Nyocurio@gmail.com>
|
|
**
|
|
** mkxp is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** mkxp 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
|
|
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "aldatasource.h"
|
|
#include "exception.h"
|
|
|
|
#define OV_EXCLUDE_STATIC_CALLBACKS
|
|
#include <vorbis/vorbisfile.h>
|
|
#include <vector>
|
|
|
|
static size_t vfRead(void *ptr, size_t size, size_t nmemb, void *ops)
|
|
{
|
|
return SDL_RWread(static_cast<SDL_RWops*>(ops), ptr, size, nmemb);
|
|
}
|
|
|
|
static int vfSeek(void *ops, ogg_int64_t offset, int whence)
|
|
{
|
|
return SDL_RWseek(static_cast<SDL_RWops*>(ops), offset, whence);
|
|
}
|
|
|
|
static long vfTell(void *ops)
|
|
{
|
|
return SDL_RWtell(static_cast<SDL_RWops*>(ops));
|
|
}
|
|
|
|
static ov_callbacks OvCallbacks =
|
|
{
|
|
vfRead,
|
|
vfSeek,
|
|
0,
|
|
vfTell
|
|
};
|
|
|
|
|
|
struct VorbisSource : ALDataSource
|
|
{
|
|
SDL_RWops &src;
|
|
|
|
OggVorbis_File vf;
|
|
|
|
uint32_t currentFrame;
|
|
|
|
struct
|
|
{
|
|
uint32_t start;
|
|
uint32_t length;
|
|
uint32_t end;
|
|
bool valid;
|
|
bool requested;
|
|
} loop;
|
|
|
|
struct
|
|
{
|
|
int channels;
|
|
int rate;
|
|
int frameSize;
|
|
ALenum alFormat;
|
|
} info;
|
|
|
|
std::vector<int16_t> sampleBuf;
|
|
|
|
VorbisSource(SDL_RWops &ops,
|
|
bool looped)
|
|
: src(ops),
|
|
currentFrame(0)
|
|
{
|
|
int error = ov_open_callbacks(&src, &vf, 0, 0, OvCallbacks);
|
|
|
|
if (error)
|
|
{
|
|
SDL_RWclose(&src);
|
|
throw Exception(Exception::MKXPError,
|
|
"Vorbisfile: Cannot read ogg file");
|
|
}
|
|
|
|
/* Extract bitstream info */
|
|
info.channels = vf.vi->channels;
|
|
info.rate = vf.vi->rate;
|
|
|
|
if (info.channels > 2)
|
|
{
|
|
ov_clear(&vf);
|
|
SDL_RWclose(&src);
|
|
throw Exception(Exception::MKXPError,
|
|
"Cannot handle audio with more than 2 channels");
|
|
}
|
|
|
|
info.alFormat = chooseALFormat(sizeof(int16_t), info.channels);
|
|
info.frameSize = sizeof(int16_t) * info.channels;
|
|
|
|
sampleBuf.resize(STREAM_BUF_SIZE);
|
|
|
|
loop.requested = looped;
|
|
loop.valid = false;
|
|
loop.start = loop.length = 0;
|
|
|
|
if (!loop.requested)
|
|
return;
|
|
|
|
/* Try to extract loop info */
|
|
for (int i = 0; i < vf.vc->comments; ++i)
|
|
{
|
|
char *comment = vf.vc->user_comments[i];
|
|
char *sep = strstr(comment, "=");
|
|
|
|
/* No '=' found */
|
|
if (!sep)
|
|
continue;
|
|
|
|
/* Empty value */
|
|
if (!*(sep+1))
|
|
continue;
|
|
|
|
*sep = '\0';
|
|
|
|
if (!strcmp(comment, "LOOPSTART"))
|
|
loop.start = strtol(sep+1, 0, 10);
|
|
|
|
if (!strcmp(comment, "LOOPLENGTH"))
|
|
loop.length = strtol(sep+1, 0, 10);
|
|
|
|
*sep = '=';
|
|
}
|
|
|
|
loop.end = loop.start + loop.length;
|
|
loop.valid = (loop.start && loop.length);
|
|
}
|
|
|
|
~VorbisSource()
|
|
{
|
|
ov_clear(&vf);
|
|
SDL_RWclose(&src);
|
|
}
|
|
|
|
int sampleRate()
|
|
{
|
|
return info.rate;
|
|
}
|
|
|
|
void seekToOffset(float seconds)
|
|
{
|
|
if (seconds <= 0)
|
|
{
|
|
ov_raw_seek(&vf, 0);
|
|
currentFrame = 0;
|
|
}
|
|
|
|
currentFrame = seconds * info.rate;
|
|
|
|
if (loop.valid && currentFrame > loop.end)
|
|
currentFrame = loop.start;
|
|
|
|
/* If seeking fails, just seek back to start */
|
|
if (ov_pcm_seek(&vf, currentFrame) != 0)
|
|
ov_raw_seek(&vf, 0);
|
|
}
|
|
|
|
Status fillBuffer(AL::Buffer::ID alBuffer)
|
|
{
|
|
void *bufPtr = sampleBuf.data();
|
|
int availBuf = sampleBuf.size();
|
|
int bufUsed = 0;
|
|
|
|
int canRead = availBuf;
|
|
|
|
Status retStatus = ALDataSource::NoError;
|
|
|
|
bool readAgain = false;
|
|
|
|
if (loop.valid)
|
|
{
|
|
int tilLoopEnd = loop.end * info.frameSize;
|
|
|
|
canRead = std::min(availBuf, tilLoopEnd);
|
|
}
|
|
|
|
while (canRead > 16)
|
|
{
|
|
long res = ov_read(&vf, static_cast<char*>(bufPtr),
|
|
canRead, 0, sizeof(int16_t), 1, 0);
|
|
|
|
if (res < 0)
|
|
{
|
|
/* Read error */
|
|
retStatus = ALDataSource::Error;
|
|
|
|
break;
|
|
}
|
|
|
|
if (res == 0)
|
|
{
|
|
/* EOF */
|
|
if (loop.requested)
|
|
{
|
|
retStatus = ALDataSource::WrapAround;
|
|
seekToOffset(0);
|
|
}
|
|
else
|
|
{
|
|
retStatus = ALDataSource::EndOfStream;
|
|
}
|
|
|
|
/* If we sought right to the end of the file,
|
|
* we might be EOF without actually having read
|
|
* any data at all yet (which mustn't happen),
|
|
* so we try to continue reading some data. */
|
|
if (bufUsed > 0)
|
|
break;
|
|
|
|
if (readAgain)
|
|
{
|
|
/* We're still not getting data though.
|
|
* Just error out to prevent an endless loop */
|
|
retStatus = ALDataSource::Error;
|
|
break;
|
|
}
|
|
|
|
readAgain = true;
|
|
}
|
|
|
|
bufUsed += (res / sizeof(int16_t));
|
|
bufPtr = &sampleBuf[bufUsed];
|
|
currentFrame += (res / info.frameSize);
|
|
|
|
if (loop.valid && currentFrame >= loop.end)
|
|
{
|
|
/* Determine how many frames we're
|
|
* over the loop end */
|
|
int discardFrames = currentFrame - loop.end;
|
|
bufUsed -= discardFrames * info.channels;
|
|
|
|
retStatus = ALDataSource::WrapAround;
|
|
|
|
/* Seek to loop start */
|
|
currentFrame = loop.start;
|
|
if (ov_pcm_seek(&vf, currentFrame) != 0)
|
|
retStatus = ALDataSource::Error;
|
|
|
|
break;
|
|
}
|
|
|
|
canRead -= res;
|
|
}
|
|
|
|
if (retStatus != ALDataSource::Error)
|
|
AL::Buffer::uploadData(alBuffer, info.alFormat, sampleBuf.data(),
|
|
bufUsed*sizeof(int16_t), info.rate);
|
|
|
|
return retStatus;
|
|
}
|
|
|
|
uint32_t loopStartFrames()
|
|
{
|
|
if (loop.valid)
|
|
return loop.start;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool setPitch(float)
|
|
{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
ALDataSource *createVorbisSource(SDL_RWops &ops,
|
|
bool looped)
|
|
{
|
|
return new VorbisSource(ops, looped);
|
|
}
|