tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_AudioFileUtils.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14juce::AudioFormatReader* AudioFileUtils::createReaderFor (Engine& engine, const juce::File& file)
15{
16 return engine.getAudioFileFormatManager().readFormatManager.createReaderFor (file);
17}
18
19juce::AudioFormatReader* AudioFileUtils::createReaderFindingFormat (Engine& engine, const juce::File& file, juce::AudioFormat*& format)
20{
21 auto& manager = engine.getAudioFileFormatManager().readFormatManager;
22
23 for (auto af : manager)
24 {
25 if (af->canHandleFile (file))
26 {
27 if (auto in = file.createInputStream())
28 {
29 if (auto r = af->createReaderFor (in.release(), true))
30 {
31 format = af;
32 return r;
33 }
34 }
35 }
36 }
37
38 return {};
39}
40
41juce::MemoryMappedAudioFormatReader* AudioFileUtils::createMemoryMappedReader (Engine& engine, const juce::File& file, juce::AudioFormat*& format)
42{
43 auto& manager = engine.getAudioFileFormatManager().readFormatManager;
44
45 for (auto af : manager)
46 {
47 if (af->canHandleFile (file))
48 {
49 if (auto r = af->createMemoryMappedReader (file))
50 {
51 format = af;
52 return r;
53 }
54 }
55 }
56
57 return {};
58}
59
60std::unique_ptr<AudioFileUtils::MappedFileAndReader> AudioFileUtils::createMappedFileAndReaderFor (Engine& engine, const juce::File& file)
61{
63 {
64 auto& fileFormatManager = engine.getAudioFileFormatManager();
66 auto mis = std::make_unique<juce::MemoryInputStream> (mf->getData(), mf->getSize(), false);
67
68 // Try using the file extension first as this will be quicker
69 if (auto audioFormat = fileFormatManager.getFormatFromFileName (file))
70 if (reader = std::unique_ptr<juce::AudioFormatReader> (audioFormat->createReaderFor (mis.get(), false)); reader)
71 mis.release();
72
73 // If that doesn't work, fall back to stream
74 if (! reader)
75 reader = std::unique_ptr<juce::AudioFormatReader> (fileFormatManager.readFormatManager.createReaderFor (std::move (mis)));
76
77 if (reader)
78 {
80 result->mappedFile = std::move (mf);
81 result->reader = std::move (reader);
82 return result;
83 }
84
85 return {};
86 }
87
88 return {};
89}
90
91juce::AudioFormatWriter* AudioFileUtils::createWriterFor (juce::AudioFormat* format, const juce::File& file,
92 double sampleRate, unsigned int numChannels, int bitsPerSample,
93 const juce::StringPairArray& metadata, int quality)
94{
95 std::unique_ptr<juce::FileOutputStream> out (file.createOutputStream());
96
97 if (out != nullptr)
98 {
99 if (auto writer = format->createWriterFor (out.get(), sampleRate,
100 numChannels, bitsPerSample,
101 metadata, quality))
102 {
103 out.release();
104 return writer;
105 }
106 }
107
108 return {};
109}
110
111juce::AudioFormatWriter* AudioFileUtils::createWriterFor (Engine& engine,
112 const juce::File& file, double sampleRate,
113 unsigned int numChannels, int bitsPerSample,
114 const juce::StringPairArray& metadata, int quality)
115{
116 if (auto format = engine.getAudioFileFormatManager().getFormatFromFileName (file))
117 return createWriterFor (format, file, sampleRate, numChannels, bitsPerSample, metadata, quality);
118
119 return {};
120}
121
122SampleRange AudioFileUtils::scanForNonZeroSamples (Engine& engine, const juce::File& file, float maxZeroLevelDb)
123{
124 std::unique_ptr<juce::AudioFormatReader> reader (createReaderFor (engine, file));
125
126 if (reader == nullptr)
127 return {};
128
129 auto numChans = (int) reader->numChannels;
130
131 if (numChans == 0 || reader->lengthInSamples == 0)
132 return {};
133
134 const float floatMaxZeroLevel = 2.0f * dbToGain (maxZeroLevelDb);
135 const int intMaxZeroLevel = 1 + (int) (std::numeric_limits<int>::max() * (double) floatMaxZeroLevel);
136 const int sampsPerBlock = 32768;
137
139 chans.calloc ((size_t) numChans + 2);
140
141 juce::HeapBlock<int> buffer ((size_t) numChans * sampsPerBlock);
142
143 for (int i = 0; i < numChans; ++i)
144 chans[i] = buffer + i * sampsPerBlock;
145
146 SampleCount firstNonZero = 0, lastNonZero = 0, n = 0;
147 bool needFirst = true;
148
149 while (n < reader->lengthInSamples)
150 {
151 for (int j = numChans; --j >= 0;)
152 std::memset (chans[j], 0, sizeof (int) * sampsPerBlock);
153
154 reader->read (chans, numChans, n, sampsPerBlock, false);
155
156 for (int j = numChans; --j >= 0;)
157 {
158 if (reader->usesFloatingPointData)
159 {
160 const float* const chan = (const float*) chans[j];
161
162 for (int i = 0; i < sampsPerBlock; ++i)
163 {
164 if (std::abs (chan[i]) > floatMaxZeroLevel)
165 {
166 if (needFirst)
167 {
168 firstNonZero = n + i;
169 needFirst = false;
170 }
171
172 lastNonZero = std::max (lastNonZero, n + i);
173 }
174 }
175 }
176 else
177 {
178 const int* const chan = chans[j];
179
180 for (int i = 0; i < sampsPerBlock; ++i)
181 {
182 if (std::abs (chan[i]) > intMaxZeroLevel)
183 {
184 if (needFirst)
185 {
186 firstNonZero = n + i;
187 needFirst = false;
188 }
189
190 lastNonZero = std::max (lastNonZero, n + i);
191 }
192 }
193 }
194 }
195
196 n += sampsPerBlock;
197 }
198
199 return { firstNonZero, lastNonZero };
200}
201
202static SampleCount copySection (Engine& e, std::unique_ptr<juce::AudioFormatReader>& reader,
203 const juce::File& sourceFile, const juce::File& destFile,
204 SampleRange range)
205{
206 if (range.contains ({ 0, reader->lengthInSamples })
207 && sourceFile.getFileExtension() == destFile.getFileExtension())
208 {
209 reader = nullptr;
210
211 if (sourceFile.copyFileTo (destFile))
212 return range.getLength();
213
214 return -1;
215 }
216
217 std::unique_ptr<juce::AudioFormatWriter> writer (AudioFileUtils::createWriterFor (e, destFile, reader->sampleRate,
218 reader->numChannels,
219 (int) reader->bitsPerSample,
220 reader->metadataValues,
221 0));
222
223 if (writer != nullptr
224 && writer->writeFromAudioReader (*reader, range.getStart(), range.getLength()))
225 return range.getLength();
226
227 return -1;
228}
229
230SampleCount AudioFileUtils::copySectionToNewFile (Engine& e, const juce::File& sourceFile,
231 const juce::File& destFile, SampleRange range)
232{
233 if (range.isEmpty())
234 return -1;
235
236 std::unique_ptr<juce::AudioFormatReader> reader (createReaderFor (e, sourceFile));
237
238 if (reader != nullptr)
239 return copySection (e, reader, sourceFile, destFile, range);
240
241 return -1;
242}
243
244SampleCount AudioFileUtils::copySectionToNewFile (Engine& e,
245 const juce::File& sourceFile,
246 const juce::File& destFile,
247 TimeRange range)
248{
249 if (range.isEmpty())
250 return -1;
251
252 std::unique_ptr<juce::AudioFormatReader> reader (createReaderFor (e, sourceFile));
253
254 if (reader != nullptr)
255 return copySection (e, reader, sourceFile, destFile,
256 { (SampleCount) tracktion::toSamples (range.getStart(), reader->sampleRate),
257 (SampleCount) tracktion::toSamples (range.getEnd(), reader->sampleRate) });
258
259 return -1;
260}
261
262SampleRange AudioFileUtils::copyNonSilentSectionToNewFile (Engine& e,
263 const juce::File& sourceFile,
264 const juce::File& destFile,
265 float maxZeroLevelDb)
266{
267 auto range = scanForNonZeroSamples (e, sourceFile, maxZeroLevelDb);
268
269 if (copySectionToNewFile (e, sourceFile, destFile, range) >= 0)
270 return range;
271
272 return {};
273}
274
275SampleRange AudioFileUtils::trimSilence (Engine& e, const juce::File& file, float maxZeroLevelDb)
276{
277 if (file.hasWriteAccess())
278 {
279 juce::TemporaryFile tempFile (file);
280 auto range = copyNonSilentSectionToNewFile (e, file, tempFile.getFile(), maxZeroLevelDb);
281
282 if (! range.isEmpty())
283 if (tempFile.overwriteTargetFileWithTemporary())
284 return range;
285 }
286
287 return {};
288}
289
291 const juce::File& source, const juce::File& destination,
292 std::atomic<float>& progress, juce::ThreadPoolJob* job, bool canCreateWavIntermediate)
293{
295 juce::AudioFormat* format;
296 const std::unique_ptr<juce::AudioFormatReader> reader (AudioFileUtils::createReaderFindingFormat (engine, source, format));
297
298 if (reader == nullptr || format == nullptr)
299 return false;
300
301 // Compressed formats don't like the random access required to reverse so make a wav copy first
302 if (format->isCompressed() && canCreateWavIntermediate)
303 {
304 auto f = juce::File::createTempFile (".wav");
305 juce::TemporaryFile tempFile ({}, f);
306
307 {
308 const std::unique_ptr<juce::FileOutputStream> out (tempFile.getFile().createOutputStream());
309
310 if (out == nullptr || (! convertToFormat<juce::WavAudioFormat> (engine, source, *out, 0, {})))
311 return false;
312 }
313
314 return reverse (engine, tempFile.getFile(), destination, progress, job, false);
315 }
316
317 // need to strip AIFF metadata to write to wav files
318 if (reader->metadataValues.getValue ("MetaDataSource", "None") == "AIFF")
319 reader->metadataValues.clear();
320
321 AudioFileWriter writer (AudioFile (engine, destination), engine.getAudioFileFormatManager().getWavFormat(),
322 (int) reader->numChannels, reader->sampleRate,
323 std::max (16, (int) reader->bitsPerSample),
324 reader->metadataValues, 0);
325
326 if (auto af = writer.file.getFormat())
327 {
328 // This is likely to mess things up if you don't supply a file with the correct extension
329 jassert (af->getFileExtensions().contains (destination.getFileExtension()));
330 (void) af;
331 }
332
333 if (! writer.isOpen())
334 return false;
335
336 SampleCount sourceSample = 0;
337 SampleCount samplesToDo = reader->lengthInSamples;
338 const int bufferSize = 65536;
339 auto sampleNum = samplesToDo;
340
341 const int numChans = (int) reader->numChannels;
342 juce::AudioBuffer<float> buffer (numChans, bufferSize);
343 juce::HeapBlock<int*> buffers ((size_t) numChans);
344 bool shouldExit = false;
345
346 while (! shouldExit)
347 {
348 auto numThisTime = (int) std::min (samplesToDo, (SampleCount) bufferSize);
349
350 if (numThisTime <= 0)
351 return true;
352
353 for (int i = numChans; --i >= 0;)
354 buffers[i] = (int*) buffer.getWritePointer (i);
355
356 sampleNum -= numThisTime;
357 reader->read (buffers, numChans, sampleNum, numThisTime, true);
358
359 for (int i = numThisTime / 2; --i >= 0;)
360 {
361 const int other = (numThisTime - i) - 1;
362
363 for (int j = numChans; --j >= 0;)
364 {
365 if (buffers[j] != nullptr)
366 {
367 const int temp = buffers[j][i];
368 buffers[j][i] = buffers[j][other];
369 buffers[j][other] = temp;
370 }
371 }
372 }
373
374 if (! writer.appendBuffer ((const int**) buffers.getData(), numThisTime))
375 break;
376
377 samplesToDo -= numThisTime;
378 sourceSample += numThisTime;
379
380 progress = juce::jlimit (0.0f, 1.0f, (float) (sourceSample / (double) reader->lengthInSamples));
381
382 if (job != nullptr)
383 shouldExit = job->shouldExit();
384 }
385
386 return false;
387}
388
389void AudioFileUtils::addBWAVStartToMetadata (juce::StringPairArray& metadata, SampleCount time)
390{
391 metadata.addArray (juce::WavAudioFormat::createBWAVMetadata ({}, "tracktion",
393 time, {}));
394}
395
396SampleCount AudioFileUtils::getFileLengthSamples (Engine& e, const juce::File& file)
397{
398 std::unique_ptr<juce::AudioFormatReader> reader (createReaderFor (e, file));
399
400 if (reader != nullptr)
401 return reader->lengthInSamples;
402
403 TRACKTION_LOG_ERROR ("Couldn't read file: " + file.getFileName());
404 return 0;
405}
406
407static bool isWavFile (const juce::File& file)
408{
410 afm.registerFormat (new juce::WavAudioFormat(), true);
411
413 return reader != nullptr;
414}
415
416void AudioFileUtils::applyBWAVStartTime (const juce::File& file, SampleCount time)
417{
418 if (isWavFile (file))
419 {
420 int pos = 0;
421
422 {
423 juce::FileInputStream fi (file);
424
425 if (fi.openedOk())
426 {
427 for (int i = 0; i < 2048; ++i)
428 {
429 char n[5] = { 0 };
430 fi.setPosition (i);
431 fi.read (n, 4);
432
433 for (int j = 0; j < 4; ++j)
434 n[j] &= 127; // to avoid ASCII char assertions
435
436 if (juce::String (n).equalsIgnoreCase ("bext"))
437 pos = i + 8;
438 }
439 }
440 }
441
442 if (pos > 0)
443 {
444 pos += 256 + 32 + 32 + 10 + 8;
445
446 juce::FileOutputStream fo (file);
447
448 if (fo.openedOk())
449 {
450 fo.setPosition (pos);
451
452 fo.writeInt ((int) (uint32_t) (time & 0xffffffff));
453 fo.writeInt ((int) (uint32_t) (time >> 32));
454 }
455 }
456 }
457}
458
459}} // namespace tracktion { inline namespace engine
Type * getWritePointer(int channelNumber) noexcept
AudioFormatReader * createReaderFor(const File &audioFile)
void registerFormat(AudioFormat *newFormat, bool makeThisTheDefaultFormat)
String getFileExtension() const
bool copyFileTo(const File &targetLocation) const
static File createTempFile(StringRef fileNameEnding)
ElementType * getData() const noexcept
void calloc(SizeType newNumElements, const size_t elementSize=sizeof(ElementType))
bool shouldExit() const noexcept
static Time JUCE_CALLTYPE getCurrentTime() noexcept
static StringPairArray createBWAVMetadata(const String &description, const String &originator, const String &originatorRef, Time dateAndTime, int64 timeReferenceSamples, const String &codingHistory)
Smart wrapper for writing to an audio file.
bool appendBuffer(juce::AudioBuffer< float > &buffer, int numSamples)
Appends an AudioBuffer to the file.
bool isOpen() const noexcept
Returns true if the file is open and ready to write to.
The Engine is the central class for all tracktion sessions.
AudioFileFormatManager & getAudioFileFormatManager() const
Returns the AudioFileFormatManager that maintains a list of available audio file formats.
T format(T... args)
T is_pointer_v
#define jassert(expression)
typedef int
typedef double
T max(T... args)
T memset(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
static bool reverse(Engine &, const juce::File &source, const juce::File &destination, std::atomic< float > &progress, juce::ThreadPoolJob *job=nullptr, bool canCreateWavIntermediate=true)
Reverses a file updating a progress value and checking the exit status of a given job.
time
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.