Skip to content

Commit

Permalink
Add FLAC export and related options (LMMS#3731)
Browse files Browse the repository at this point in the history
* Add FLAC export, based on WAV renderer

* Depend on sndfile>=1.0.18 (previously >=1.0.11)

* Add compression option to FLAC export, if available.

The code related to compression is only generated if LMMS_HAVE_SF_COMPLEVEL is defined(libsndfile>=1.0.26).

* Save into the correct file extension upon single-export.

* Use unique_ptr in FLAC renderer and be more expressive about involved types.

* Use unique_ptr and remove manual memory management in ExportProjectDialog

* Add 'flac' format info to --help and manpage
  • Loading branch information
irrenhaus3 authored and PhysSong committed Aug 3, 2017
1 parent 6ce8ac4 commit ed074cb
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 46 deletions.
11 changes: 8 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,14 @@ ELSE()
ENDIF()

# check for libsndfile
PKG_CHECK_MODULES(SNDFILE REQUIRED sndfile>=1.0.11)
PKG_CHECK_MODULES(SNDFILE REQUIRED sndfile>=1.0.18)
IF(NOT SNDFILE_FOUND)
MESSAGE(FATAL_ERROR "LMMS requires libsndfile1 and libsndfile1-dev >= 1.0.11 - please install, remove CMakeCache.txt and try again!")
ENDIF(NOT SNDFILE_FOUND)
MESSAGE(FATAL_ERROR "LMMS requires libsndfile1 and libsndfile1-dev >= 1.0.18 - please install, remove CMakeCache.txt and try again!")
ENDIF()
# check if we can use SF_SET_COMPRESSION_LEVEL
IF(NOT SNDFILE_VERSION VERSION_LESS 1.0.26)
SET(LMMS_HAVE_SF_COMPLEVEL TRUE)
ENDIF()

IF(WANT_CALF)
SET(LMMS_HAVE_CALF TRUE)
Expand Down Expand Up @@ -625,6 +629,7 @@ MESSAGE(
"Supported file formats for project export\n"
"-----------------------------------------\n"
"* WAVE : OK\n"
"* FLAC : OK\n"
"* OGG/VORBIS : ${STATUS_OGGVORBIS}\n"
"* MP3/Lame : ${STATUS_MP3LAME}\n"
)
Expand Down
2 changes: 1 addition & 1 deletion doc/lmms.1
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Get the configuration from \fIconfigfile\fP instead of ~/.lmmsrc.xml (default)
.IP "\fB\-d, --dump\fP \fIin\fP
Dump XML of compressed file \fIin\fP (i.e. MMPZ-file)
.IP "\fB\-f, --format\fP \fIformat\fP
Specify format of render-output where \fIformat\fP is either 'wav', 'ogg' or 'mp3'.
Specify format of render-output where \fIformat\fP is either 'wav', 'flac', 'ogg' or 'mp3'.
.IP "\fB\ --geometry\fP \fIgeometry\fP
Specify the prefered size and position of the main window
.br
Expand Down
74 changes: 74 additions & 0 deletions include/AudioFileFlac.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* AudioFileFlac.h - Audio device which encodes a wave stream into a FLAC file.
*
* Copyright (c) 2017 to present Levin Oehlmann <irrenhaus3/at/gmail[dot]com> et al.
*
* This file is part of LMMS - https://lmms.io
*
* This program 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.
*
* 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 (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/

#ifndef AUDIO_FILE_FLAC_H
#define AUDIO_FILE_FLAC_H

#include "lmmsconfig.h"

#include "AudioFileDevice.h"
#include <sndfile.h>

class AudioFileFlac: public AudioFileDevice
{
public:
AudioFileFlac(OutputSettings const& outputSettings,
ch_cnt_t const channels,
bool& successful,
QString const& file,
Mixer* mixer
);

virtual ~AudioFileFlac();

static AudioFileDevice* getInst(QString const& outputFilename,
OutputSettings const& outputSettings,
ch_cnt_t const channels,
Mixer* mixer,
bool& successful)
{
return new AudioFileFlac(
outputSettings,
channels,
successful,
outputFilename,
mixer
);
}

private:

SF_INFO m_sfinfo;
SNDFILE* m_sf;

virtual void writeBuffer(surroundSampleFrame const* _ab,
fpp_t const frames,
float master_gain) override;

bool startEncoding();
void finishEncoding();

};

#endif //AUDIO_FILE_FLAC_H
6 changes: 2 additions & 4 deletions include/ExportProjectDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
#define EXPORT_PROJECT_DIALOG_H

#include <QDialog>
#include <vector>
#include <memory>
#include "ui_export_project.h"

#include "ProjectRenderer.h"
Expand All @@ -39,8 +39,6 @@ class ExportProjectDialog : public QDialog, public Ui::ExportProjectDialog
Q_OBJECT
public:
ExportProjectDialog( const QString & _file_name, QWidget * _parent, bool multi_export );
virtual ~ExportProjectDialog();


protected:
virtual void reject( void );
Expand All @@ -62,7 +60,7 @@ private slots:
bool m_multiExport;

ProjectRenderer::ExportFileFormats m_ft;
RenderManager* m_renderManager;
std::unique_ptr<RenderManager> m_renderManager;
} ;

#endif
11 changes: 10 additions & 1 deletion include/OutputSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ class OutputSettings
m_sampleRate(sampleRate),
m_bitRateSettings(bitRateSettings),
m_bitDepth(bitDepth),
m_stereoMode(stereoMode)
m_stereoMode(stereoMode),
m_compressionLevel(0.5)
{
}

Expand All @@ -95,11 +96,19 @@ class OutputSettings
StereoMode getStereoMode() const { return m_stereoMode; }
void setStereoMode(StereoMode stereoMode) { m_stereoMode = stereoMode; }


double getCompressionLevel() const{ return m_compressionLevel; }
void setCompressionLevel(double level){
// legal range is 0.0 to 1.0.
m_compressionLevel = level;
}

private:
sample_rate_t m_sampleRate;
BitRateSettings m_bitRateSettings;
BitDepth m_bitDepth;
StereoMode m_stereoMode;
double m_compressionLevel;
};

#endif
1 change: 1 addition & 0 deletions include/ProjectRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ProjectRenderer : public QThread
enum ExportFileFormats: int
{
WaveFile,
FlacFile,
OggFile,
MP3File,
NumFileFormats
Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ set(LMMS_SRCS
core/audio/AudioFileDevice.cpp
core/audio/AudioFileMP3.cpp
core/audio/AudioFileOgg.cpp
core/audio/AudioFileFlac.cpp
core/audio/AudioFileWave.cpp
core/audio/AudioJack.cpp
core/audio/AudioOss.cpp
Expand Down
10 changes: 8 additions & 2 deletions src/core/ProjectRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "AudioFileWave.h"
#include "AudioFileOgg.h"
#include "AudioFileMP3.h"
#include "AudioFileFlac.h"

#ifdef LMMS_HAVE_SCHED_H
#include "sched.h"
Expand All @@ -42,6 +43,11 @@ const ProjectRenderer::FileEncodeDevice ProjectRenderer::fileEncodeDevices[] =
{ ProjectRenderer::WaveFile,
QT_TRANSLATE_NOOP( "ProjectRenderer", "WAV-File (*.wav)" ),
".wav", &AudioFileWave::getInst },
{ ProjectRenderer::FlacFile,
QT_TRANSLATE_NOOP("ProjectRenderer", "FLAC-File (*.flac)"),
".flac",
&AudioFileFlac::getInst
},
{ ProjectRenderer::OggFile,
QT_TRANSLATE_NOOP( "ProjectRenderer", "Compressed OGG-File (*.ogg)" ),
".ogg",
Expand Down Expand Up @@ -176,8 +182,8 @@ void ProjectRenderer::run()

Engine::getSong()->startExport();
Engine::getSong()->updateLength();
//skip first empty buffer
Engine::mixer()->nextBuffer();
//skip first empty buffer
Engine::mixer()->nextBuffer();

const Song::PlayPos & exportPos = Engine::getSong()->getPlayPos(
Song::Mode_PlaySong );
Expand Down
119 changes: 119 additions & 0 deletions src/core/audio/AudioFileFlac.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* AudioFileFlac.cpp - Audio device which encodes a wave stream into a FLAC file (Implementation).
*
* Copyright (c) 2017 to present Levin Oehlmann <irrenhaus3/at/gmail[dot]com> et al.
*
* This file is part of LMMS - https://lmms.io
*
* This program 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.
*
* 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 (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/

#include <memory>

#include "AudioFileFlac.h"
#include "endian_handling.h"
#include "Mixer.h"

AudioFileFlac::AudioFileFlac(OutputSettings const& outputSettings, ch_cnt_t const channels, bool& successful, QString const& file, Mixer* mixer):
AudioFileDevice(outputSettings,channels,file,mixer),
m_sf(nullptr)
{
successful = outputFileOpened() && startEncoding();
}

AudioFileFlac::~AudioFileFlac()
{
finishEncoding();
}

bool AudioFileFlac::startEncoding()
{
m_sfinfo.samplerate=sampleRate();
m_sfinfo.channels=channels();
m_sfinfo.frames = mixer()->framesPerPeriod();
m_sfinfo.sections=1;
m_sfinfo.seekable=0;

m_sfinfo.format = SF_FORMAT_FLAC;

switch (getOutputSettings().getBitDepth())
{
case OutputSettings::Depth_24Bit:
case OutputSettings::Depth_32Bit:
// FLAC does not support 32bit sampling, so take it as 24.
m_sfinfo.format |= SF_FORMAT_PCM_24;
break;
default:
m_sfinfo.format |= SF_FORMAT_PCM_16;
}

#ifdef LMMS_HAVE_SF_COMPLEVEL
double compression = getOutputSettings().getCompressionLevel();
sf_command(m_sf, SFC_SET_COMPRESSION_LEVEL, &compression, sizeof(double));
#endif

m_sf = sf_open(
#ifdef LMMS_BUILD_WIN32
outputFile().toLocal8Bit().constData(),
#else
outputFile().toUtf8().constData(),
#endif
SFM_WRITE,
&m_sfinfo
);

sf_command(m_sf, SFC_SET_CLIPPING, nullptr, SF_TRUE);

sf_set_string(m_sf, SF_STR_SOFTWARE, "LMMS");

return true;
}

void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const frames, float master_gain)
{
OutputSettings::BitDepth depth = getOutputSettings().getBitDepth();

if (depth == OutputSettings::Depth_24Bit || depth == OutputSettings::Depth_32Bit) // Float encoding
{
std::unique_ptr<sample_t[]> buf{ new sample_t[frames*channels()] };
for(fpp_t frame = 0; frame < frames; ++frame)
{
for(ch_cnt_t channel=0; channel<channels(); ++channel)
{
buf[frame*channels() + channel] = _ab[frame][channel] * master_gain;
}
}
sf_writef_float(m_sf,static_cast<float*>(buf.get()),frames);
}
else // integer PCM encoding
{
std::unique_ptr<int_sample_t[]> buf{ new int_sample_t[frames*channels()] };
convertToS16(_ab, frames, master_gain, buf.get(), !isLittleEndian());
sf_writef_short(m_sf, static_cast<short*>(buf.get()), frames);
}

}


void AudioFileFlac::finishEncoding()
{
if (m_sf)
{
sf_write_sync(m_sf);
sf_close(m_sf);
}
}
6 changes: 5 additions & 1 deletion src/core/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ void printHelp()
"-c, --config <configfile> Get the configuration from <configfile>\n"
"-d, --dump <in> Dump XML of compressed file <in>\n"
"-f, --format <format> Specify format of render-output where\n"
" Format is either 'wav', 'ogg' or 'mp3'.\n"
" Format is either 'wav', 'flac', 'ogg' or 'mp3'.\n"
" --geometry <geometry> Specify the size and position of the main window\n"
" geometry is <xsizexysize+xoffset+yoffsety>.\n"
"-h, --help Show this usage information and exit.\n"
Expand Down Expand Up @@ -444,6 +444,10 @@ int main( int argc, char * * argv )
eff = ProjectRenderer::MP3File;
}
#endif
else if (ext == "flac")
{
eff = ProjectRenderer::FlacFile;
}
else
{
printf( "\nInvalid output format %s.\n\n"
Expand Down
Loading

0 comments on commit ed074cb

Please sign in to comment.