MuseScore/mscore/exportmp3.cpp

866 lines
28 KiB
C++

//=============================================================================
// MuseScore
// Linux Music Score Editor
// $Id: MP3Exporter.cpp 2992 2010-04-22 14:42:39Z lasconic $
//
// Copyright (C) 2011 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// 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
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
#include "libmscore/score.h"
#include "synthesizer/msynthesizer.h"
#include "libmscore/note.h"
#include "musescore.h"
#include "libmscore/part.h"
#include "preferences.h"
#include "exportmp3.h"
namespace Ms {
//---------------------------------------------------------
// MP3Exporter
//---------------------------------------------------------
MP3Exporter::MP3Exporter()
{
mLibraryLoaded = false;
mEncoding = false;
mGF = NULL;
QSettings settings;
mLibPath = settings.value("/Export/lameMP3LibPath", "").toString();
mBitrate = 128;
mQuality = QUALITY_2;
mChannel = CHANNEL_JOINT;
mMode = MODE_CBR;
mRoutine = ROUTINE_FAST;
}
MP3Exporter::~MP3Exporter()
{
freeLibrary();
}
//---------------------------------------------------------
// findLibrary
//---------------------------------------------------------
bool MP3Exporter::findLibrary()
{
QString path;
QString name;
if (!mLibPath.isEmpty()) {
QFileInfo fi(mLibPath);
path = fi.absolutePath();
name = fi.completeBaseName();
}
else {
path = getLibraryPath();
name = getLibraryName();
}
if (MScore::noGui)
return false;
QString libPath = QFileDialog::getOpenFileName(
0, qApp->translate("MP3Exporter", "Where is %1 ?").arg(getLibraryName()),
path,
getLibraryTypeString());
if (libPath.isEmpty())
return false;
QFileInfo fp(libPath);
if (!fp.exists())
return false;
mLibPath = libPath;
QSettings settings;
settings.setValue("/Export/lameMP3LibPath", mLibPath);
return true;
}
//---------------------------------------------------------
// loadLibrary
//---------------------------------------------------------
bool MP3Exporter::loadLibrary(AskUser askuser)
{
if (validLibraryLoaded()) {
freeLibrary();
mLibraryLoaded = false;
}
// First try loading it from a previously located path
if (!mLibPath.isEmpty()) {
qDebug("Attempting to load LAME from previously defined path");
mLibraryLoaded = initLibrary(mLibPath);
}
// If not successful, try loading using system search paths
if (!validLibraryLoaded()) {
qDebug("Attempting to load LAME from system search paths");
mLibPath = getLibraryName();
mLibraryLoaded = initLibrary(mLibPath);
}
// If not successful, try loading using compiled in path
if (!validLibraryLoaded()) {
qDebug("Attempting to load LAME from builtin path");
QFileInfo fn(QDir(getLibraryPath()), getLibraryName());
mLibPath = fn.absoluteFilePath();
mLibraryLoaded = initLibrary(mLibPath);
}
// If not successful, must ask the user
if (!validLibraryLoaded()) {
qDebug("(Maybe) ask user for library");
int ret = QMessageBox::question(0, qApp->translate("MP3Exporter", "Save as MP3"),
qApp->translate("MP3Exporter", "MuseScore does not export MP3 files directly, but instead uses "
"the freely available LAME library. You must obtain %1 "
"separately (for details check the handbook), and then locate the file for MuseScore.\n"
"You only need to do this once.\n\n"
"Would you like to locate %2 now?").arg(getLibraryName()).arg(getLibraryName()),
QMessageBox::Yes|QMessageBox::No, QMessageBox::NoButton);
if (ret == QMessageBox::Yes && askuser == MP3Exporter::AskUser::MAYBE && findLibrary()) {
mLibraryLoaded = initLibrary(mLibPath);
}
}
// Oh well, just give up
if (!validLibraryLoaded()) {
qDebug("Failed to locate LAME library");
return false;
}
qDebug("LAME library successfully loaded");
return true;
}
bool MP3Exporter::validLibraryLoaded()
{
return mLibraryLoaded;
}
void MP3Exporter::setMode(int mode)
{
mMode = mode;
}
void MP3Exporter::setBitrate(int rate)
{
mBitrate = rate;
}
void MP3Exporter::setQuality(int q, int r)
{
mQuality = q;
mRoutine = r;
}
void MP3Exporter::setChannel(int mode)
{
mChannel = mode;
}
//---------------------------------------------------------
// initLibrary
//---------------------------------------------------------
bool MP3Exporter::initLibrary(QString libpath)
{
qDebug("Loading LAME from %s", qPrintable(libpath));
lame_lib = new QLibrary(libpath, 0);
if (!lame_lib->load()) {
qDebug("load failed <%s>", qPrintable(lame_lib->errorString()));
return false;
}
/*qDebug("Actual LAME path %s",
FileNames::PathFromAddr(lame_lib->resolve("lame_init")));*/
lame_init = (lame_init_t *)
lame_lib->resolve("lame_init");
get_lame_version = (get_lame_version_t *)
lame_lib->resolve("get_lame_version");
lame_init_params = (lame_init_params_t *)
lame_lib->resolve("lame_init_params");
lame_encode_buffer_float = (lame_encode_buffer_float_t *)
lame_lib->resolve("lame_encode_buffer_float");
lame_encode_flush = (lame_encode_flush_t *)
lame_lib->resolve("lame_encode_flush");
lame_close = (lame_close_t *)
lame_lib->resolve("lame_close");
lame_set_in_samplerate = (lame_set_in_samplerate_t *)
lame_lib->resolve("lame_set_in_samplerate");
lame_set_out_samplerate = (lame_set_out_samplerate_t *)
lame_lib->resolve("lame_set_out_samplerate");
lame_set_num_channels = (lame_set_num_channels_t *)
lame_lib->resolve("lame_set_num_channels");
lame_set_quality = (lame_set_quality_t *)
lame_lib->resolve("lame_set_quality");
lame_set_brate = (lame_set_brate_t *)
lame_lib->resolve("lame_set_brate");
lame_set_VBR = (lame_set_VBR_t *)
lame_lib->resolve("lame_set_VBR");
lame_set_VBR_q = (lame_set_VBR_q_t *)
lame_lib->resolve("lame_set_VBR_q");
lame_set_VBR_min_bitrate_kbps = (lame_set_VBR_min_bitrate_kbps_t *)
lame_lib->resolve("lame_set_VBR_min_bitrate_kbps");
lame_set_mode = (lame_set_mode_t *)
lame_lib->resolve("lame_set_mode");
lame_set_preset = (lame_set_preset_t *)
lame_lib->resolve("lame_set_preset");
lame_set_error_protection = (lame_set_error_protection_t *)
lame_lib->resolve("lame_set_error_protection");
lame_set_disable_reservoir = (lame_set_disable_reservoir_t *)
lame_lib->resolve("lame_set_disable_reservoir");
lame_set_padding_type = (lame_set_padding_type_t *)
lame_lib->resolve("lame_set_padding_type");
lame_set_bWriteVbrTag = (lame_set_bWriteVbrTag_t *)
lame_lib->resolve("lame_set_bWriteVbrTag");
// These are optional
lame_get_lametag_frame = (lame_get_lametag_frame_t *)
lame_lib->resolve("lame_get_lametag_frame");
lame_mp3_tags_fid = (lame_mp3_tags_fid_t *)
lame_lib->resolve("lame_mp3_tags_fid");
#if defined(Q_OS_WIN)
beWriteInfoTag = (beWriteInfoTag_t *)
lame_lib->resolve("beWriteInfoTag");
beVersion = (beVersion_t *)
lame_lib->resolve("beVersion");
#endif
if (!lame_init ||
!get_lame_version ||
!lame_init_params ||
!lame_encode_buffer_float ||
!lame_encode_flush ||
!lame_close ||
!lame_set_in_samplerate ||
!lame_set_out_samplerate ||
!lame_set_num_channels ||
!lame_set_quality ||
!lame_set_brate ||
!lame_set_VBR ||
!lame_set_VBR_q ||
!lame_set_mode ||
!lame_set_preset ||
!lame_set_error_protection ||
!lame_set_disable_reservoir ||
!lame_set_padding_type ||
!lame_set_bWriteVbrTag) {
qDebug("Failed to find a required symbol in the LAME library");
#if defined(Q_OS_WIN)
if (beVersion) {
be_version v;
beVersion(&v);
mBladeVersion = QString("You are linking to lame_enc.dll v%d.%d. This version is not compatible with MuseScore %d.\nPlease download the latest version of the LAME MP3 library.")
.arg(v.byMajorVersion)
.arg(v.byMinorVersion)
.arg(1); //TODO
}
#endif
lame_lib->unload();
delete lame_lib;
return false;
}
mGF = lame_init();
if (mGF == NULL) {
lame_lib->unload();
delete lame_lib;
return false;
}
return true;
}
//---------------------------------------------------------
// freeLibrary
//---------------------------------------------------------
void MP3Exporter::freeLibrary()
{
if (mGF) {
lame_close(mGF);
mGF = NULL;
lame_lib->unload();
delete lame_lib;
}
return;
}
//---------------------------------------------------------
// getLibraryVersion
//---------------------------------------------------------
QString MP3Exporter::getLibraryVersion()
{
if (!mLibraryLoaded)
return QString("");
return QString("LAME %s").arg(get_lame_version());
}
//---------------------------------------------------------
// initializeStream
//---------------------------------------------------------
int MP3Exporter::initializeStream(int channels, int sampleRate)
{
if (!mLibraryLoaded)
return -1;
if (channels > 2)
return -1;
lame_set_error_protection(mGF, false);
lame_set_num_channels(mGF, channels);
lame_set_in_samplerate(mGF, sampleRate);
lame_set_out_samplerate(mGF, sampleRate);
lame_set_disable_reservoir(mGF, true);
lame_set_padding_type(mGF, PAD_NO);
// Add the VbrTag for all types. For ABR/VBR, a Xing tag will be created.
// For CBR, it will be a Lame Info tag.
lame_set_bWriteVbrTag(mGF, true);
// Set the VBR quality or ABR/CBR bitrate
switch (mMode) {
case MODE_SET:
{
int preset;
if (mQuality == PRESET_INSANE)
preset = INSANE;
else if (mRoutine == ROUTINE_FAST) {
if (mQuality == PRESET_EXTREME)
preset = EXTREME_FAST;
else if (mQuality == PRESET_STANDARD)
preset = STANDARD_FAST;
else
preset = 1007; // Not defined until 3.96
}
else {
if (mQuality == PRESET_EXTREME)
preset = EXTREME;
else if (mQuality == PRESET_STANDARD)
preset = STANDARD;
else
preset = 1006; // Not defined until 3.96
}
lame_set_preset(mGF, preset);
}
break;
case MODE_VBR:
lame_set_VBR(mGF, (mRoutine == ROUTINE_STANDARD ? vbr_rh : vbr_mtrh ));
lame_set_VBR_q(mGF, mQuality);
break;
case MODE_ABR:
lame_set_preset(mGF, mBitrate );
break;
default:
lame_set_VBR(mGF, vbr_off);
lame_set_brate(mGF, mBitrate);
break;
}
// Set the channel mode
MPEG_mode mode;
if (channels == 1)
mode = MONO;
else if (mChannel == CHANNEL_JOINT)
mode = JOINT_STEREO;
else
mode = STEREO;
lame_set_mode(mGF, mode);
int rc = lame_init_params(mGF);
if (rc < 0)
return rc;
#if 0
dump_config(mGF);
#endif
mInfoTagLen = 0;
mEncoding = true;
return mSamplesPerChunk;
}
//---------------------------------------------------------
// getOutBufferSize
//---------------------------------------------------------
int MP3Exporter::getOutBufferSize()
{
if (!mEncoding)
return -1;
return mOutBufferSize;
}
//---------------------------------------------------------
// bufferPreamp
//---------------------------------------------------------
void MP3Exporter::bufferPreamp(float buffer[], int nSamples)
{
for (int i = 0; i < nSamples; i++)
buffer[i] = buffer[i] * 32768;
}
//---------------------------------------------------------
// encodeBuffer
//---------------------------------------------------------
int MP3Exporter::encodeBuffer(float inbufferL[], float inbufferR[], unsigned char outbuffer[])
{
if (!mEncoding)
return -1;
bufferPreamp(inbufferL, mSamplesPerChunk);
bufferPreamp(inbufferR, mSamplesPerChunk);
return lame_encode_buffer_float(mGF, inbufferL, inbufferR, mSamplesPerChunk,
outbuffer, mOutBufferSize);
}
//---------------------------------------------------------
// encodeRemainder
//---------------------------------------------------------
int MP3Exporter::encodeRemainder(float inbufferL[], float inbufferR[], int nSamples,
unsigned char outbuffer[])
{
if (!mEncoding)
return -1;
bufferPreamp(inbufferL, nSamples);
bufferPreamp(inbufferR, nSamples);
return lame_encode_buffer_float(mGF, inbufferL, inbufferR, nSamples, outbuffer,
mOutBufferSize);
}
//---------------------------------------------------------
// encodeBufferMono
//---------------------------------------------------------
int MP3Exporter::encodeBufferMono(float inbuffer[], unsigned char outbuffer[])
{
if (!mEncoding)
return -1;
bufferPreamp(inbuffer, mSamplesPerChunk);
return lame_encode_buffer_float(mGF, inbuffer, inbuffer, mSamplesPerChunk,
outbuffer, mOutBufferSize);
}
//---------------------------------------------------------
// encodeRemainderMono
//---------------------------------------------------------
int MP3Exporter::encodeRemainderMono(float inbuffer[], int nSamples,
unsigned char outbuffer[])
{
if (!mEncoding)
return -1;
bufferPreamp(inbuffer, nSamples);
return lame_encode_buffer_float(mGF, inbuffer, inbuffer, nSamples, outbuffer,
mOutBufferSize);
}
//---------------------------------------------------------
// finishStream
//---------------------------------------------------------
int MP3Exporter::finishStream(unsigned char outbuffer[])
{
if (!mEncoding)
return -1;
mEncoding = false;
int result = lame_encode_flush(mGF, outbuffer, mOutBufferSize);
if (lame_get_lametag_frame)
mInfoTagLen = lame_get_lametag_frame(mGF, mInfoTagBuf, sizeof(mInfoTagBuf));
return result;
}
//---------------------------------------------------------
// cancelEncoding
//---------------------------------------------------------
void MP3Exporter::cancelEncoding()
{
mEncoding = false;
}
/*void MP3Exporter::PutInfoTag(QFile f, qint64 off)
{
QDataStream out(&f);
if (mGF) {
if (mInfoTagLen > 0) {
out.skipRawData (off);
out.writeRawData(mInfoTagBuf, mInfoTagLen);
}
#if defined(Q_OS_WIN)
else if (beWriteInfoTag) {
f.flush();
QFileInfo fi(f);
beWriteInfoTag(mGF, qPrintable(fi.completeBaseName()));
mGF = NULL;
}
#endif
else if (lame_mp3_tags_fid) {
std::FILE *fp;
if ((fp = std::fdopen(file.handle(), "w+")) != NULL)
lame_mp3_tags_fid(mGF, fp);
}
}
f.seek(f.size());
}*/
#if defined(Q_OS_WIN)
/* values for Windows */
QString MP3Exporter::getLibraryPath()
{
QSettings settings("HKEY_LOCAL_MACHINE\\Software\\Lame for Audacity", QSettings::NativeFormat);
QString sReturnedValue = settings.value( "InstallPath", "" ).toString();
if (! sReturnedValue.isEmpty()) {
return sReturnedValue;
}
return QDir::rootPath();
}
QString MP3Exporter::getLibraryName()
{
return QString("lame_enc.dll");
}
QString MP3Exporter::getLibraryTypeString()
{
return QString("Only lame_enc.dll (lame_enc.dll);;Dynamically Linked Libraries (*.dll);;All Files (*.*)");
}
#elif defined(Q_OS_MAC)
/* values for Mac OS X */
QString MP3Exporter::getLibraryPath()
{
return QString("/usr/local/lib/audacity");
}
QString MP3Exporter::getLibraryName()
{
return QString("libmp3lame.dylib");
}
QString MP3Exporter::getLibraryTypeString()
{
return QString("Only libmp3lame.dylib (libmp3lame.dylib);;Dynamic Libraries (*.dylib);;All Files (*)");
}
#else //!Q_OS_MAC
/* Values for Linux / Unix systems */
QString MP3Exporter::getLibraryPath()
{
return QString("/usr/lib");
}
QString MP3Exporter::getLibraryName()
{
return QString("libmp3lame.so.0");
}
QString MP3Exporter::getLibraryTypeString()
{
return QString("Only libmp3lame.so.0 (libmp3lame.so.0);;Primary Shared Object files (*.so);;Extended Libraries (*.so*);;All Files (*)");
}
#endif //mac
//---------------------------------------------------------
// saveMp3
//---------------------------------------------------------
bool MuseScore::saveMp3(Score* score, const QString& name)
{
EventMap events;
score->renderMidi(&events);
if(events.size() == 0)
return false;
MP3Exporter exporter;
if (!exporter.loadLibrary(MP3Exporter::AskUser::MAYBE)) {
QSettings settings;
settings.setValue("/Export/lameMP3LibPath", "");
if(!MScore::noGui)
QMessageBox::warning(0,
tr("Error Opening LAME library"),
tr("Could not open MP3 encoding library!"),
QString::null, QString::null);
qDebug("Could not open MP3 encoding library!");
return false;
}
if (!exporter.validLibraryLoaded()) {
QSettings settings;
settings.setValue("/Export/lameMP3LibPath", "");
if(!MScore::noGui)
QMessageBox::warning(0,
tr("Error Opening LAME library"),
tr("Not a valid or supported MP3 encoding library!"),
QString::null, QString::null);
qDebug("Not a valid or supported MP3 encoding library!");
return false;
}
// Retrieve preferences
// int highrate = 48000;
// int lowrate = 8000;
// int bitrate = 64;
// int brate = 128;
// int rmode = MODE_CBR;
// int vmode = ROUTINE_FAST;
// int cmode = CHANNEL_STEREO;
int channels = 2;
int oldSampleRate = MScore::sampleRate;
int sampleRate = preferences.exportAudioSampleRate;
int inSamples = exporter.initializeStream(channels, sampleRate);
if (inSamples < 0) {
if (!MScore::noGui) {
QMessageBox::warning(0, tr("Encoding Error"),
tr("Unable to initialize MP3 stream"),
QString::null, QString::null);
}
qDebug("Unable to initialize MP3 stream");
MScore::sampleRate = oldSampleRate;
return false;
}
QFile file(name);
if (!file.open(QIODevice::WriteOnly)) {
if (!MScore::noGui) {
QMessageBox::warning(0,
tr("Encoding Error"),
tr("Unable to open target file for writing"),
QString::null, QString::null);
}
MScore::sampleRate = oldSampleRate;
return false;
}
int bufferSize = exporter.getOutBufferSize();
uchar* bufferOut = new uchar[bufferSize];
MasterSynthesizer* synti = synthesizerFactory();
synti->init();
synti->setSampleRate(sampleRate);
bool r = synti->setState(score->synthesizerState());
if (!r)
synti->init();
MScore::sampleRate = sampleRate;
QProgressDialog progress(this);
progress.setWindowFlags(Qt::WindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint));
progress.setWindowModality(Qt::ApplicationModal);
//progress.setCancelButton(0);
progress.setCancelButtonText(tr("Cancel"));
progress.setLabelText(tr("Exporting..."));
if (!MScore::noGui)
progress.show();
static const int FRAMES = 512;
float bufferL[FRAMES];
float bufferR[FRAMES];
float peak = 0.0;
double gain = 1.0;
EventMap::const_iterator endPos = events.cend();
--endPos;
const int et = (score->utick2utime(endPos->first) + 1) * MScore::sampleRate;
const int maxEndTime = (score->utick2utime(endPos->first) + 3) * MScore::sampleRate;
progress.setRange(0, et);
for (int pass = 0; pass < 2; ++pass) {
EventMap::const_iterator playPos;
playPos = events.cbegin();
synti->allSoundsOff(-1);
//
// init instruments
//
foreach(Part* part, score->parts()) {
const InstrumentList* il = part->instruments();
for(auto i = il->begin(); i!= il->end(); i++) {
foreach(const Channel* a, i->second->channel()) {
a->updateInitList();
foreach(MidiCoreEvent e, a->init) {
if (e.type() == ME_INVALID)
continue;
e.setChannel(a->channel);
int syntiIdx= synti->index(score->masterScore()->midiMapping(a->channel)->articulation->synti);
synti->play(e, syntiIdx);
}
}
}
}
int playTime = 0.0;
for (;;) {
unsigned frames = FRAMES;
float max = 0;
//
// collect events for one segment
//
memset(bufferL, 0, sizeof(float) * FRAMES);
memset(bufferR, 0, sizeof(float) * FRAMES);
double endTime = playTime + frames;
float* l = bufferL;
float* r = bufferR;
for (; playPos != events.cend(); ++playPos) {
double f = score->utick2utime(playPos->first) * MScore::sampleRate;
if (f >= endTime)
break;
int n = f - playTime;
if (n) {
float bu[n * 2];
memset(bu, 0, sizeof(float) * 2 * n);
synti->process(n, bu);
float* sp = bu;
for (int i = 0; i < n; ++i) {
*l++ = *sp++;
*r++ = *sp++;
}
playTime += n;
frames -= n;
}
const NPlayEvent& e = playPos->second;
if (e.isChannelEvent()) {
int channelIdx = e.channel();
Channel* c = score->masterScore()->midiMapping(channelIdx)->articulation;
if (!c->mute) {
synti->play(e, synti->index(c->synti));
}
}
}
if (frames) {
float bu[frames * 2];
memset(bu, 0, sizeof(float) * 2 * frames);
synti->process(frames, bu);
float* sp = bu;
for (unsigned i = 0; i < frames; ++i) {
*l++ = *sp++;
*r++ = *sp++;
}
playTime += frames;
}
if (pass == 1) {
for (int i = 0; i < FRAMES; ++i) {
max = qMax(max, qAbs(bufferL[i]));
max = qMax(max, qAbs(bufferR[i]));
bufferL[i] *= gain;
bufferR[i] *= gain;
}
long bytes;
if (FRAMES < inSamples)
bytes = exporter.encodeRemainder(bufferL, bufferR, FRAMES , bufferOut);
else
bytes = exporter.encodeBuffer(bufferL, bufferR, bufferOut);
if (bytes < 0) {
if (MScore::noGui)
qDebug("exportmp3: error from encoder: %ld", bytes);
else
QMessageBox::warning(0,
tr("Encoding Error"),
tr("Error %1 returned from MP3 encoder").arg(bytes),
QString::null, QString::null);
break;
}
else
file.write((char*)bufferOut, bytes);
}
else {
for (int i = 0; i < FRAMES; ++i) {
max = qMax(max, qAbs(bufferL[i]));
max = qMax(max, qAbs(bufferR[i]));
peak = qMax(peak, qAbs(bufferL[i]));
peak = qMax(peak, qAbs(bufferR[i]));
}
}
playTime = endTime;
if (!MScore::noGui) {
if (progress.wasCanceled())
break;
progress.setValue((pass * et + playTime) / 2);
qApp->processEvents();
}
if (playTime >= et)
synti->allNotesOff(-1);
// create sound until the sound decays
if (playTime >= et && max * peak < 0.000001)
break;
// hard limit
if (playTime > maxEndTime)
break;
}
if (progress.wasCanceled())
break;
if (pass == 0 && peak == 0.0) {
qDebug("song is empty");
break;
}
gain = 0.99 / peak;
}
long bytes = exporter.finishStream(bufferOut);
if (bytes > 0L)
file.write((char*)bufferOut, bytes);
bool wasCanceled = progress.wasCanceled();
progress.close();
delete synti;
delete[] bufferOut;
file.close();
if (wasCanceled)
file.remove();
MScore::sampleRate = oldSampleRate;
return true;
}
}