hydrogen 1.2.6
DiskWriterDriver.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22#include <unistd.h>
23#include <algorithm>
24
27#include <core/EventQueue.h>
29#include <core/Hydrogen.h>
30#include <core/Basics/Pattern.h>
32#include <core/Basics/Sample.h>
34
35#include <pthread.h>
36#include <cassert>
37
38#if defined(WIN32) || _DOXYGEN_
39#include <windows.h>
40/*
41 * In Windows the unistd function sleep( seconds ) is not available.
42 * Treat sleep( SECONDS ) as a macro that uses SleepEx.
43 * Convert seconds to milliseconds for the first argument of SleepEx.
44 * Use false for the second argument of SleepEx.
45 * This way SleepEx always returns 0, after the specified time has passed.
46 */
47#define sleep( SECONDS ) SleepEx( SECONDS * 1000, false )
48#endif
49
50namespace H2Core
51{
52
54
55void* diskWriterDriver_thread( void* param )
56{
57
58 DiskWriterDriver *pDriver = ( DiskWriterDriver* )param;
59
61
62 auto pAudioEngine = Hydrogen::get_instance()->getAudioEngine();
63
64 ___INFOLOG( "DiskWriterDriver thread started" );
65
66 const auto format = Filesystem::AudioFormatFromSuffix( pDriver->m_sFilename );
67
68 SF_INFO soundInfo;
69 soundInfo.samplerate = pDriver->m_nSampleRate;
70 soundInfo.channels = 2;
71
72 // default format
73 int sfformat = SF_FORMAT_WAV; //wav format (default)
74 int bits = SF_FORMAT_PCM_16; //16 bit PCM (default)
75
76 // Determine audio format based on the provided file suffix.
77 if ( format == Filesystem::AudioFormat::Aiff ||
80 sfformat = SF_FORMAT_AIFF;
81 }
82#ifdef H2CORE_HAVE_FLAC_SUPPORT
83 else if ( format == Filesystem::AudioFormat::Flac ) {
84 sfformat = SF_FORMAT_FLAC;
85 }
86#endif
87 else if ( format == Filesystem::AudioFormat::Wav ) {
88 sfformat = SF_FORMAT_WAV;
89 }
90 else if ( format == Filesystem::AudioFormat::Au ) {
91 sfformat = SF_FORMAT_AU;
92 }
93 else if ( format == Filesystem::AudioFormat::Caf ) {
94 sfformat = SF_FORMAT_CAF;
95 }
96 else if ( format == Filesystem::AudioFormat::W64 ) {
97 sfformat = SF_FORMAT_W64;
98 }
99#ifdef H2CORE_HAVE_FLAC_SUPPORT
100 else if ( format == Filesystem::AudioFormat::Ogg ) {
101 sfformat = SF_FORMAT_OGG;
102 bits = SF_FORMAT_VORBIS;
103 }
104#endif
105#ifdef H2CORE_HAVE_OPUS_SUPPORT
106 else if ( format == Filesystem::AudioFormat::Opus ) {
107 sfformat = SF_FORMAT_OGG;
108 bits = SF_FORMAT_OPUS;
109 }
110#endif
111 else if ( format == Filesystem::AudioFormat::Voc ) {
112 sfformat = SF_FORMAT_VOC;
113 }
114#ifdef H2CORE_HAVE_MP3_SUPPORT
115 else if ( format == Filesystem::AudioFormat::Mp3 ) {
116 sfformat = SF_FORMAT_MPEG;
117 bits = SF_FORMAT_MPEG_LAYER_III;
118 }
119#endif
120 else {
121 ___ERRORLOG( QString( "Unsupported file extension [%1] using libsndfile [%2]" )
122 .arg( pDriver->m_sFilename ).arg( sf_version_string() ) );
123 pDriver->m_bDoneWriting = true;
124 pDriver->m_bWritingFailed = true;
126 pthread_exit( nullptr );
127 return nullptr;
128
129 }
130
131 // Instead of making audio export fail on non-supported parameter
132 // combinations, we tailor this test and UI to only allow valid ones. It
133 // would be bad UX to provide an invalid option.
134
135 if ( format != Filesystem::AudioFormat::Ogg &&
137 format != Filesystem::AudioFormat::Mp3 ) {
138 // Handle sample depth
139 if ( pDriver->m_nSampleDepth == 8 ) {
140 // WAV and other raw PCM formats are handled differently.
141 if ( format == Filesystem::AudioFormat::Voc ||
143 format == Filesystem::AudioFormat::Wav ) {
144 bits = SF_FORMAT_PCM_U8; //Unsigned 8 bit data needed for Microsoft WAV format
145 } else {
146 bits = SF_FORMAT_PCM_S8; //Signed 8 bit data works with aiff
147 }
148 }
149 else if ( pDriver->m_nSampleDepth == 16 ) {
150 bits = SF_FORMAT_PCM_16; //Signed 16 bit data
151 }
152 else if ( pDriver->m_nSampleDepth == 24 ) {
153 bits = SF_FORMAT_PCM_24; //Signed 24 bit data
154 }
155 else if ( pDriver->m_nSampleDepth == 32 ) {
156 bits = SF_FORMAT_PCM_32;
157 }
158 }
159
160 soundInfo.format = sfformat|bits;
161
162 if ( !sf_format_check( &soundInfo ) ) {
163 ___ERRORLOG( QString( "Error while checking format using libsndfile [%1]" )
164 .arg( sf_version_string() ) );
165 pDriver->m_bDoneWriting = true;
166 pDriver->m_bWritingFailed = true;
168 pthread_exit( nullptr );
169 return nullptr;
170 }
171
172#ifdef WIN32
173 // On Windows we use a special version of sf_open to ensure we get all
174 // characters of the filename entered in the GUI right. No matter which
175 // encoding was used locally.
176 // We have to terminate the string using a null character ourselves.
177 QString sPaddedPath = pDriver->m_sFilename.append( '\0' );
178 wchar_t* encodedFilename = new wchar_t[ sPaddedPath.size() ];
179
180 sPaddedPath.toWCharArray( encodedFilename );
181
182 SNDFILE* pSndfile = sf_wchar_open( encodedFilename, SFM_WRITE,
183 &soundInfo );
184 delete encodedFilename;
185#else
186 SNDFILE* pSndfile = sf_open( pDriver->m_sFilename.toLocal8Bit(), SFM_WRITE,
187 &soundInfo );
188#endif
189
190 if ( pSndfile == nullptr ) {
191 ___ERRORLOG( QString( "Unable to open file [%1] with format [%2] using libsndfile [%3]: %4" )
192 .arg( pDriver->m_sFilename )
193 .arg( Sample::sndfileFormatToQString( soundInfo.format ) )
194 .arg( sf_version_string() )
195 .arg( sf_strerror( pSndfile ) ) );
196 pDriver->m_bDoneWriting = true;
197 pDriver->m_bWritingFailed = true;
199 pthread_exit( nullptr );
200 return nullptr;
201 }
202
203 // Perform some per-file settings.
204#ifdef H2CORE_HAVE_MP3_SUPPORT
205 if ( format == Filesystem::AudioFormat::Mp3 ) {
206 int nBitrateMode = SF_BITRATE_MODE_VARIABLE;
207 if ( sf_command( pSndfile, SFC_SET_BITRATE_MODE, &nBitrateMode,
208 sizeof(int) ) != SF_TRUE ) {
209 ___WARNINGLOG( QString( "Unable to set variable bitrate for MP3 encoding: %1" )
210 .arg( sf_strerror( pSndfile ) ) );
211 }
212 }
213#endif
214
215#ifdef H2CORE_HAVE_FLAC_SUPPORT
216 // FLAC (and OGG/Vorbis) is the oldest format supporting this setting.
217 if ( format == Filesystem::AudioFormat::Mp3 ||
221 if ( sf_command( pSndfile, SFC_SET_COMPRESSION_LEVEL,
222 &pDriver->m_fCompressionLevel,
223 sizeof(double) ) != SF_TRUE ) {
224 ___WARNINGLOG( QString( "Unable to set compression level [%1]: %2" )
225 .arg( pDriver->m_fCompressionLevel )
226 .arg( sf_strerror( pSndfile ) ) );
227 }
228 }
229#endif
230
231 float *pData = new float[ pDriver->m_nBufferSize * 2 ]; // always stereo
232
233 float *pData_L = pDriver->m_pOut_L;
234 float *pData_R = pDriver->m_pOut_R;
235
236
237 Hydrogen* pHydrogen = Hydrogen::get_instance();
238 auto pSong = pHydrogen->getSong();
239 auto pSampler = pHydrogen->getAudioEngine()->getSampler();
240
241 // always rolling, no user interaction
242 pAudioEngine->play();
243
244 std::vector<PatternList*> *pPatternColumns = pSong->getPatternGroupVector();
245 int nColumns = pPatternColumns->size();
246
247 int nPatternSize, nBufferWriteLength;
248 float fBpm;
249 float fTicksize = 0;
250 int nMaxNumberOfSilentFrames = 200;
251 for ( int patternPosition = 0; patternPosition < nColumns; ++patternPosition ) {
252
253 PatternList *pColumn = ( *pPatternColumns )[ patternPosition ];
254 if ( pColumn->size() != 0 ) {
255 nPatternSize = pColumn->longest_pattern_length();
256 } else {
257 nPatternSize = MAX_NOTES;
258 }
259
260 fBpm = AudioEngine::getBpmAtColumn( patternPosition );
261 fTicksize = AudioEngine::computeTickSize( pDriver->m_nSampleRate, fBpm,
262 pSong->getResolution() );
263
264 //here we have the pattern length in frames dependent from bpm and samplerate
265 int nPatternLengthInFrames = fTicksize * nPatternSize;
266 int nFrameNumber = 0;
267 int nLastRun = 0;
268 int nSuccessiveZeros = 0;
269 while ( ( patternPosition < nColumns - 1 && // render all
270 // frames in
271 // pattern
272 nFrameNumber < nPatternLengthInFrames ) ||
273 ( patternPosition == nColumns - 1 && // render till
274 // all notes are
275 // processed
276 ( nFrameNumber < nPatternLengthInFrames ||
277 pSampler->isRenderingNotes() ) ) ) {
278
279 int nUsedBuffer = pDriver->m_nBufferSize;
280
281 // This will calculate the size from -last- (end of
282 // pattern) used frame buffer, which is mostly smaller
283 // than pDriver->m_nBufferSize. But it only applies for
284 // all patterns except of the last one. The latter we will
285 // let ring until there is no further audio to process.
286 if( patternPosition < nColumns - 1 &&
287 nPatternLengthInFrames - nFrameNumber < pDriver->m_nBufferSize ){
288 nLastRun = nPatternLengthInFrames - nFrameNumber;
289 nUsedBuffer = nLastRun;
290 };
291
292 int ret = pDriver->m_processCallback( nUsedBuffer, nullptr );
293
294 // In case the DiskWriter couldn't acquire the lock of the AudioEngine.
295 while( ret == 2 ) {
296 ret = pDriver->m_processCallback( nUsedBuffer, nullptr );
297 }
298
299 if ( patternPosition == nColumns - 1 &&
300 nPatternLengthInFrames - nFrameNumber < nUsedBuffer ) {
301 // The next buffer at least partially exceeds the song
302 // size in ticks. As soon as it does we start to count
303 // zeros in both audio channels. The moment we
304 // encounter more than X we will stop the audio
305 // export. Just waiting for the Sampler to finish
306 // rendering is not sufficient because the Sample
307 // itself can be zero padded at the end causing the
308 // resulting .wav file to be inconsistent in terms of
309 // length depending on the buffer sized use during
310 // export.
311 //
312 // We are at the last pattern and just waited for the
313 // Sampler to finish rendering all notes (at an
314 // arbitrary point within the buffer).
315 nBufferWriteLength = 0;
316
317 int nSilentFrames = 0;
318 for ( int ii = 0; ii < nUsedBuffer; ++ii ) {
319 ++nBufferWriteLength;
320
321 if ( std::abs( pData_L[ii] ) == 0 &&
322 std::abs( pData_R[ii] ) == 0 ) {
323 ++nSuccessiveZeros;
324 }
325
326 if ( nSuccessiveZeros == nMaxNumberOfSilentFrames ) {
327 break;
328 }
329 }
330 } else {
331 nBufferWriteLength = nUsedBuffer;
332 }
333
334 nFrameNumber += nBufferWriteLength;
335
336 for ( unsigned ii = 0; ii < nBufferWriteLength; ii++ ) {
337 if( pData_L[ ii ] > 1 ) {
338 pData[ ii * 2 ] = 1;
339 } else if( pData_L[ ii ] < -1 ) {
340 pData[ ii * 2 ] = -1;
341 } else {
342 pData[ ii * 2 ] = pData_L[ ii ];
343 }
344
345 if( pData_R[ ii ] > 1 ){
346 pData[ ii * 2 + 1 ] = 1;
347 } else if ( pData_R[ ii ] < -1 ) {
348 pData[ ii * 2 + 1 ] = -1;
349 } else {
350 pData[ ii * 2 + 1 ] = pData_R[ ii ];
351 }
352 }
353
354 const int res = sf_writef_float( pSndfile, pData, nBufferWriteLength );
355 if ( res != ( int )nBufferWriteLength ) {
356 ___ERRORLOG( QString( "Error during sf_write_float using [%1]. Floats written: [%2], target: [%3]. %4" )
357 .arg( sf_version_string() ).arg( res )
358 .arg( nBufferWriteLength )
359 .arg( sf_strerror( nullptr ) ) );
360 pDriver->m_bWritingFailed = true;
361 }
362
363 // Sampler is still rendering notes put we seem to have
364 // reached the zero padding at the end of the
365 // corresponding samples.
366 if ( nSuccessiveZeros == nMaxNumberOfSilentFrames ) {
367 break;
368 }
369 }
370
371 // this progress bar method is not exact but ok enough to give users a usable visible progress feedback
372 int nPercent = static_cast<int>( ( float )(patternPosition +1) /
373 ( float )nColumns * 100.0 );
374 if ( nPercent < 100 ) {
376 }
377 }
378
379 // Explicitly mark export as finished.
381
382 delete[] pData;
383 pData = nullptr;
384
385 pDriver->m_bDoneWriting = true;
386
387 sf_close( pSndfile );
388
389 ___INFOLOG( "DiskWriterDriver thread end" );
390
391 pthread_exit( nullptr );
392 return nullptr;
393}
394
395
396
398 : AudioOutput()
399 , m_nSampleRate( 4800 )
400 , m_nSampleDepth( 32 )
401 , m_processCallback( processCallback )
402 , m_nBufferSize( 1024 )
403 , m_pOut_L( nullptr )
404 , m_pOut_R( nullptr )
405 , m_bDoneWriting( false )
406 , m_bWritingFailed( false )
407 , m_fCompressionLevel( 0.0 ) {
408}
409
410
411
414
415
416
417int DiskWriterDriver::init( unsigned nBufferSize )
418{
419 INFOLOG( QString( "Init, buffer size: %1" ).arg( nBufferSize ) );
420
421 m_nBufferSize = nBufferSize;
422
423 m_pOut_L = new float[ m_nBufferSize ];
424 m_pOut_R = new float[ m_nBufferSize ];
425
426 return 0;
427}
428
430{
431 return 0;
432}
433
435{
436 INFOLOG( "" );
437
438 pthread_attr_t attr;
439 pthread_attr_init( &attr );
440
441 pthread_create( &diskWriterDriverThread, &attr, diskWriterDriver_thread, this );
442}
443
446{
447 INFOLOG( "" );
448
449 pthread_join( diskWriterDriverThread, nullptr );
450
451 delete[] m_pOut_L;
452 m_pOut_L = nullptr;
453
454 delete[] m_pOut_R;
455 m_pOut_R = nullptr;
456
457}
458
460{
461 return m_nSampleRate;
462}
463
464void DiskWriterDriver::setCompressionLevel( double fCompressionLevel ) {
465 if ( fCompressionLevel > 1.0 || fCompressionLevel < 0.0 ) {
466 ERRORLOG( QString( "Provided compression level [%1] out of bound [0.0, 1.0]. Assigning nearest possible value." )
467 .arg( fCompressionLevel ) );
468 fCompressionLevel = std::clamp( fCompressionLevel, 0.0, 1.0 );
469 }
470
471 m_fCompressionLevel = fCompressionLevel;
472}
473};
#define ___WARNINGLOG(x)
Definition Object.h:259
#define INFOLOG(x)
Definition Object.h:240
#define ERRORLOG(x)
Definition Object.h:242
#define ___INFOLOG(x)
Definition Object.h:258
#define ___ERRORLOG(x)
Definition Object.h:260
static float getBpmAtColumn(int nColumn)
static float computeTickSize(const int nSampleRate, const float fBpm, const int nResolution)
Calculates the number of frames that make up a tick.
Sampler * getSampler() const
Driver for export audio to disk.
virtual void disconnect() override
disconnect
void setCompressionLevel(double fCompressionLevel)
virtual int init(unsigned nBufferSize) override
DiskWriterDriver(audioProcessCallback processCallback)
virtual int connect() override
audioProcessCallback m_processCallback
virtual unsigned getSampleRate() override
double m_fCompressionLevel
A value between 0.0 (maximum quality) and 1.0 (maximum compression).
static EventQueue * get_instance()
Returns a pointer to the current EventQueue singleton stored in __instance.
Definition EventQueue.h:224
void push_event(const EventType type, const int nValue)
Queues the next event into the EventQueue.
static AudioFormat AudioFormatFromSuffix(const QString &sFile, bool bSilent=false)
Determines the audio format of the provided filename or path based on its suffix.
Hydrogen Audio Engine.
Definition Hydrogen.h:54
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:123
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
AudioEngine * getAudioEngine() const
Definition Hydrogen.h:663
PatternList is a collection of patterns.
Definition PatternList.h:43
int longest_pattern_length(bool bIncludeVirtuals=true) const
Get the length of the longest pattern in the list.
int size() const
returns the numbers of patterns
static QString sndfileFormatToQString(int nFormat)
Definition Sample.cpp:836
#define MAX_NOTES
Maximum number of notes.
Definition config.dox:79
int(* audioProcessCallback)(uint32_t, void *)
Definition AudioOutput.h:32
@ EVENT_PROGRESS
Definition EventQueue.h:98
pthread_t diskWriterDriverThread
void * diskWriterDriver_thread(void *param)