Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
94258b4
LmmsExporterSample_adding_class
szeli1 Dec 18, 2024
98cffce
CMakeLists_adding_new_class_target
szeli1 Dec 19, 2024
96a2287
LmmsExporterSample_fixing_printf
szeli1 Dec 19, 2024
9117d77
SampleClip_implementing_static_exporter
szeli1 Dec 19, 2024
43328ea
SampleClipView_implementing_exporting_context_menu_option
szeli1 Dec 19, 2024
020e425
LmmsExporterSample_fixing_typo
szeli1 Dec 19, 2024
5dc1651
SamleClipView_fixing_typo
szeli1 Dec 19, 2024
7cf8bb8
LmmsExporterSample_removing_blank_line
szeli1 Dec 19, 2024
a884d1c
LmmsExporterSample_removing_LMMS_EXPORT
szeli1 Dec 19, 2024
6da5e91
LmmsExporterSample_removing_ifdef
szeli1 Dec 19, 2024
43c19cc
LmmsExporterSample_removing_more_blank_lines
szeli1 Dec 19, 2024
8e39af2
LmmsMassExporter_renaming
szeli1 Jan 4, 2025
083085e
LmmsMassExporter_moving_sndfile_code_to_new_class
szeli1 Jan 4, 2025
e0633e1
CMakeLists_updating_class_names
szeli1 Jan 4, 2025
974634c
SampleClip_updating_class_names
szeli1 Jan 4, 2025
efdb5a6
SampleClipView_updating_class_names
szeli1 Jan 4, 2025
acef235
LmmsMassExporter_removing_include_QFile
szeli1 Jan 4, 2025
d201257
LmmsMassExporter_implementing_callback
szeli1 Jan 4, 2025
94bc1e2
SampleClip_implementing_sample_reloading
szeli1 Jan 4, 2025
fb446ab
SampleClipView_updating_names
szeli1 Jan 4, 2025
ce014e5
LmmsMassExporter_moving_flac_exporter
szeli1 Jan 5, 2025
43661fb
FlacExporter_moving_to_new_file
szeli1 Jan 5, 2025
67918a9
LmmsMassExporter_updating_description
szeli1 Jan 5, 2025
fbe413f
Use FileDialog::getSaveFileName
michaelgregorius Jan 4, 2025
4b8ba9b
CMakeLists_adding_FlacExporter_cmake_target
szeli1 Jan 5, 2025
cca3c0b
LmmsMassExporter_removing_blank_line
szeli1 Jan 5, 2025
8f450ac
Save file with user selected name
michaelgregorius Jan 6, 2025
15ce7cd
LmmsMassExporter_renaming_to_ThreadedExportManager
szeli1 Jan 8, 2025
76fa656
CMakeLists_renaming_class
szeli1 Jan 8, 2025
cdf7b8c
SampleClipView_removing_LmmsMassExporter_include
szeli1 Jan 8, 2025
dbff613
SampleClip_renaming_class
szeli1 Jan 8, 2025
b8916c2
FlacExporter_adding_sync_function_before_destructing
szeli1 Jan 8, 2025
bc3a485
SampleClip_removing_export_callback
szeli1 Mar 11, 2025
e1bad1b
Engine_moving_export_manager_here
szeli1 May 8, 2025
ed6494d
SampleClip_removing_export_manager
szeli1 May 8, 2025
a27a600
SampleClipView_changing_icon
szeli1 May 8, 2025
cc300ad
ThreadedExportManager_reworking_code
szeli1 May 8, 2025
8f35a86
FlacExporter_applying_requested_changes
szeli1 May 8, 2025
252d357
SampleClip_removing_unused_variable
szeli1 May 8, 2025
db54a46
SampleClipView_removing_code
szeli1 May 8, 2025
488078b
ThreadedExportManager_fix_aborting_when_exporting
szeli1 May 10, 2025
f8d5c56
SampleClipView replacing file dialog
szeli1 May 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Mixer;
class PatternStore;
class ProjectJournal;
class Song;
class ThreadedExportManager;
class Ladspa2LMMS;

namespace gui
Expand Down Expand Up @@ -80,6 +81,11 @@ class LMMS_EXPORT Engine : public QObject
{
return s_projectJournal;
}

static ThreadedExportManager* exportManager()
{
return s_exportManager;
}

#ifdef LMMS_HAVE_LV2
static class Lv2Manager * getLv2Manager()
Expand Down Expand Up @@ -137,6 +143,7 @@ class LMMS_EXPORT Engine : public QObject
static Song * s_song;
static PatternStore * s_patternStore;
static ProjectJournal * s_projectJournal;
static ThreadedExportManager* s_exportManager;

#ifdef LMMS_HAVE_LV2
static class Lv2Manager* s_lv2Manager;
Expand Down
53 changes: 53 additions & 0 deletions include/FlacExporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* FlacExporter.h - exports .flac files outside of AudioEngine
*
* Copyright (c) 2024 - 2025 szeli1
*
* 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 LMMS_FLAC_EXPORTER_H
#define LMMS_FLAC_EXPORTER_H

#include <sndfile.h>

#include <QString>

namespace lmms
{

class SampleFrame;

class FlacExporter
{
public:
FlacExporter(int sampleRate, int bitDepth, const QString& outputLocationAndName);
~FlacExporter();

void writeThisBuffer(const SampleFrame* samples, size_t sampleCount);
bool getIsSuccesful() const;

private:
bool m_isSuccesful = false;
SNDFILE* m_fileDescriptor = nullptr;
};

} // namespace lmms

#endif // LMMS_FLAC_EXPORTER_H
3 changes: 3 additions & 0 deletions include/SampleClip.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
namespace lmms
{

class ThreadedExportManager;
class SampleBuffer;

namespace gui
Expand Down Expand Up @@ -96,6 +97,8 @@ public slots:
SampleClip( const SampleClip& orig );

private:
void exportSampleBuffer(const QString& fileName);

Sample m_sample;
BoolModel m_recordModel;
bool m_isPlaying;
Expand Down
1 change: 1 addition & 0 deletions include/SampleClipView.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public slots:
void updateSample();
void reverseSample();
void setAutomationGhost();
void exportSampleBuffer();



Expand Down
68 changes: 68 additions & 0 deletions include/ThreadedExportManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* ThreadedExportManager.h - exports files in .flac format on an other thread
*
* Copyright (c) 2024 - 2025 szeli1
*
* 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 LMMS_THREADED_EXPORT_MANAGER_H
#define LMMS_THREADED_EXPORT_MANAGER_H

#include <atomic>
#include <memory>
#include <thread>
#include <tuple>
#include <vector>
#include <mutex>

#include <sndfile.h>

#include <QString>

namespace lmms
{

class SampleBuffer;

typedef void (*callbackFn)(void*);

class ThreadedExportManager
{
public:
ThreadedExportManager();
~ThreadedExportManager();

//! @param outputLocationAndName: should include path and name, could include ".flac"
void startExporting(const QString& outputLocationAndName, std::shared_ptr<const SampleBuffer> buffer, callbackFn callbackFunction = nullptr, void* callbackObject = nullptr);
void stopExporting();

private:
static void threadedExportMethod(ThreadedExportManager* thisExporter, std::atomic<bool>* shouldRun);

std::vector<std::tuple<QString, std::shared_ptr<const SampleBuffer>, callbackFn, void*>> m_buffers;

std::atomic<bool> m_isThreadRunning = false;
std::mutex m_readMutex;
std::thread* m_thread = nullptr;
};

} // namespace lmms

#endif // LMMS_THREADED_EXPORT_MANAGER_H
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ set(LMMS_SRCS
core/SerializingObject.cpp
core/Song.cpp
core/TempoSyncKnobModel.cpp
core/ThreadedExportManager.cpp
core/ThreadPool.cpp
core/Timeline.cpp
core/TimePos.cpp
Expand Down Expand Up @@ -106,6 +107,7 @@ set(LMMS_SRCS
core/audio/AudioPulseAudio.cpp
core/audio/AudioSampleRecorder.cpp
core/audio/AudioSdl.cpp
core/audio/FlacExporter.cpp

core/lv2/Lv2Basics.cpp
core/lv2/Lv2ControlBase.cpp
Expand Down
5 changes: 5 additions & 0 deletions src/core/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "PresetPreviewPlayHandle.h"
#include "ProjectJournal.h"
#include "Song.h"
#include "ThreadedExportManager.h"
#include "BandLimitedWave.h"
#include "Oscillator.h"

Expand All @@ -46,6 +47,7 @@ Mixer * Engine::s_mixer = nullptr;
PatternStore * Engine::s_patternStore = nullptr;
Song * Engine::s_song = nullptr;
ProjectJournal * Engine::s_projectJournal = nullptr;
ThreadedExportManager* Engine::s_exportManager = nullptr;
#ifdef LMMS_HAVE_LV2
Lv2Manager * Engine::s_lv2Manager = nullptr;
#endif
Expand All @@ -71,6 +73,7 @@ void Engine::init( bool renderOnly )
s_song = new Song;
s_mixer = new Mixer;
s_patternStore = new PatternStore;
s_exportManager = new ThreadedExportManager;

#ifdef LMMS_HAVE_LV2
s_lv2Manager = new Lv2Manager;
Expand Down Expand Up @@ -116,6 +119,8 @@ void Engine::destroy()

deleteHelper( &s_song );

deleteHelper(&s_exportManager);

delete ConfigManager::inst();

// The oscillator FFT plans remain throughout the application lifecycle
Expand Down
15 changes: 11 additions & 4 deletions src/core/SampleClip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <QDomElement>
#include <QFileInfo>

#include "ThreadedExportManager.h"
#include "PathUtil.h"
#include "SampleBuffer.h"
#include "SampleClipView.h"
Expand All @@ -37,10 +38,10 @@
namespace lmms
{

SampleClip::SampleClip(Track* _track, Sample sample, bool isPlaying)
: Clip(_track)
, m_sample(std::move(sample))
, m_isPlaying(false)
SampleClip::SampleClip(Track* _track, Sample sample, bool isPlaying) :
Clip(_track),
m_sample(std::move(sample)),
m_isPlaying(false)
{
saveJournallingState( false );
setSampleFile( "" );
Expand Down Expand Up @@ -79,6 +80,7 @@ SampleClip::SampleClip(Track* _track, Sample sample, bool isPlaying)
setResizable(true);
break;
}

updateTrackClips();
}

Expand Down Expand Up @@ -378,5 +380,10 @@ gui::ClipView * SampleClip::createView( gui::TrackView * _tv )
return new gui::SampleClipView( this, _tv );
}

void SampleClip::exportSampleBuffer(const QString& fileName)
{
Engine::exportManager()->startExporting(fileName, m_sample.buffer());
}


} // namespace lmms
101 changes: 101 additions & 0 deletions src/core/ThreadedExportManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* ThreadedExportManager.cpp - exports files in .flac format on an other thread
*
* Copyright (c) 2024 - 2025 szeli1
*
* 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 "ThreadedExportManager.h"

#include "FlacExporter.h"
#include "SampleBuffer.h"

namespace lmms
{

ThreadedExportManager::ThreadedExportManager()
{}

ThreadedExportManager::~ThreadedExportManager()
{
stopExporting();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us assume I close LMMS while there are still buffers left to be rendered... Then this here will set m_isThreadRunning to false. Hence, threadedExportMethod will see shouldRun as false and will discard all pending requests - which means data loss. So, why not remove the m_isThreadRunning and just let stopExporting call m_thread->join(), which will wait until all pending requests are finished?

Copy link
Contributor Author

@szeli1 szeli1 Jun 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect things to be lost when I start to export something and then close the application. However nothing indicates that the exporting was finished.

Maybe use the callback function? (To display a Text Float)

}


void ThreadedExportManager::startExporting(const QString& outputLocationAndName, std::shared_ptr<const SampleBuffer> buffer, callbackFn callbackFunction, void* callbackObject)
{
m_readMutex.lock();
m_buffers.push_back(std::make_tuple(outputLocationAndName, buffer, callbackFunction, callbackObject));
m_readMutex.unlock();

if (m_isThreadRunning == false)
{
stopExporting();
m_isThreadRunning = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this variable can be elided? Rather just test for thread being nullptr? In any case, one of the two must be atomic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, setting the variable here seems redundant (and possibly dangerous?) since threadedExportFunction just does that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@szeli1 This comment is resolved, but why did you keep the variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I have refactored the design, to only use 1 boolean. I believe once a thread is finished, the pointer will not point to nullptr but to the finished thread object. That is why stopExporting() is called, so the finished thread object can get deleted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, before I can answer here, we need to discuss my new comment :)

m_thread = new std::thread(&ThreadedExportManager::threadedExportMethod, this, &m_isThreadRunning);
}
}

void ThreadedExportManager::stopExporting()
{
if (m_thread != nullptr)
{
m_isThreadRunning = false;
m_thread->join();
delete m_thread;
m_thread = nullptr;
}
}


void ThreadedExportManager::threadedExportMethod(ThreadedExportManager* thisExporter, std::atomic<bool>* shouldRun)
{
while (*shouldRun == true)
{
std::tuple<QString, std::shared_ptr<const SampleBuffer>, callbackFn, void*> curBuffer = std::make_tuple(QString(""), nullptr, nullptr, nullptr);
thisExporter->m_readMutex.lock();
bool shouldExit = thisExporter->m_buffers.size() <= 0;
if (shouldExit == false)
{
curBuffer = thisExporter->m_buffers[thisExporter->m_buffers.size() - 1];
thisExporter->m_buffers.pop_back();
}
thisExporter->m_readMutex.unlock();
if (shouldExit) { break; }

// important new scope
// can't call back if flacExporter's file is open
{
FlacExporter flacExporter(std::get<1>(curBuffer)->sampleRate(), 24, std::get<0>(curBuffer));
if (flacExporter.getIsSuccesful())
{
flacExporter.writeThisBuffer(std::get<1>(curBuffer)->data(), std::get<1>(curBuffer)->size());
}
}

if (std::get<2>(curBuffer))
{
// calling callback funcion
std::get<2>(curBuffer)(std::get<3>(curBuffer));
}
}
}

} // namespace lmms
Loading
Loading