Audacity 3.2.0
WaveChannelUtilities.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 WaveChannelUtilities.cpp
7
8 Paul Licameli
9
10**********************************************************************/
12#include "PlaybackDirection.h"
13#include "WaveClip.h"
14#include "WaveClipUtilities.h"
15#include "WaveTrack.h"
16
17#include <cmath>
18#include <limits>
19
20std::pair<float, float>
22 double t0, double t1, bool mayThrow)
23{
24 std::pair<float, float> results {
25 // we need these at extremes to make sure we find true min and max
26 std::numeric_limits<float>::max(),
27 std::numeric_limits<float>::lowest(),
28 };
29 bool clipFound = false;
30
31 if (t0 > t1) {
32 if (mayThrow)
34 return results;
35 }
36
37 if (t0 == t1)
38 return results;
39
40 for (const auto &clip: channel.Intervals())
41 {
42 if (t1 >= clip->GetPlayStartTime() && t0 <= clip->GetPlayEndTime())
43 {
44 clipFound = true;
45 auto clipResults = clip->GetMinMax(t0, t1, mayThrow);
46 if (clipResults.first < results.first)
47 results.first = clipResults.first;
48 if (clipResults.second > results.second)
49 results.second = clipResults.second;
50 }
51 }
52
53 if(!clipFound)
54 {
55 results = { 0.f, 0.f }; // sensible defaults if no clips found
56 }
57
58 return results;
59}
60
62 double t0, double t1, bool mayThrow)
63{
64 if (t0 > t1) {
65 if (mayThrow)
67 return 0.f;
68 }
69
70 if (t0 == t1)
71 return 0.f;
72
73 double sumsq = 0.0;
74 double duration = 0;
75
76 for (const auto &clip: channel.Intervals())
77 {
78 // If t1 == clip->GetStartTime() or t0 == clip->GetEndTime(), then the clip
79 // is not inside the selection, so we don't want it.
80 // if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
81 if (t1 >= clip->GetPlayStartTime() && t0 <= clip->GetPlayEndTime())
82 {
83 const auto clipStart = std::max(t0, clip->GetPlayStartTime());
84 const auto clipEnd = std::min(t1, clip->GetPlayEndTime());
85
86 float cliprms = clip->GetRMS(t0, t1, mayThrow);
87
88 sumsq += cliprms * cliprms * (clipEnd - clipStart);
89 duration += (clipEnd - clipStart);
90 }
91 }
92 return duration > 0 ? sqrt(sumsq / duration) : 0.0;
93}
94
95namespace {
96using namespace WaveChannelUtilities;
97
98void RoundToNearestClipSample(const WaveChannel &channel, double& t)
99{
100 const auto clip = GetClipAtTime(channel, t);
101 if (!clip)
102 return;
103 t = clip->SamplesToTime(clip->TimeToSamples(t - clip->GetPlayStartTime())) +
104 clip->GetPlayStartTime();
105}
106} // namespace
107
108std::pair<size_t, size_t>
110 double t, float* buffer, size_t numSideSamples,
111 bool mayThrow)
112{
113 const auto numSamplesReadLeft = GetFloatsFromTime(channel,
114 t, buffer, numSideSamples, mayThrow, PlaybackDirection::backward);
115 const auto numSamplesReadRight = GetFloatsFromTime(channel,
116 t, buffer + numSideSamples, numSideSamples + 1, mayThrow,
118 return { numSideSamples - numSamplesReadLeft,
119 numSideSamples + numSamplesReadRight };
120}
121
122namespace
123{
124template <typename FloatType>
125using BufferCharType = std::conditional_t<
126 std::is_const_v<std::remove_pointer_t<FloatType>>, constSamplePtr,
127 samplePtr>;
128
129template <typename BufferType> struct SampleAccessArgs
130{
133 const size_t len;
134};
135
136template <typename BufferType>
138 const Clip& clip, double startOrEndTime /*absolute*/,
139 BufferType buffer,
140 size_t totalToRead, size_t alreadyRead, bool forward)
141{
142 assert(totalToRead >= alreadyRead);
143 const auto remainingToRead = totalToRead - alreadyRead;
144 const auto sampsInClip = clip.GetVisibleSampleCount();
145 const auto sampsPerSec = clip.GetRate() / clip.GetStretchRatio();
146 if (forward)
147 {
148 const auto startTime =
149 std::max(startOrEndTime - clip.GetPlayStartTime(), 0.);
150 const sampleCount startSamp { std::round(startTime * sampsPerSec) };
151 if (startSamp >= sampsInClip)
152 return { nullptr, sampleCount { 0u }, 0u };
153 const auto len =
154 limitSampleBufferSize(remainingToRead, sampsInClip - startSamp);
155 return { reinterpret_cast<BufferCharType<BufferType>>(
156 buffer + alreadyRead),
157 startSamp, len };
158 }
159 else
160 {
161 const auto endTime = std::min(
162 startOrEndTime - clip.GetPlayStartTime(), clip.GetPlayDuration());
163 const sampleCount endSamp { std::round(endTime * sampsPerSec) };
164 const auto startSamp =
165 std::max(endSamp - remainingToRead, sampleCount { 0 });
166 // `len` cannot be greater than `remainingToRead`, itself a `size_t` ->
167 // safe cast.
168 const auto len = (endSamp - startSamp).as_size_t();
169 if (len == 0 || startSamp >= sampsInClip)
170 return { nullptr, sampleCount { 0u }, 0u };
171 const auto bufferEnd = buffer + remainingToRead;
172 return { reinterpret_cast<BufferCharType<BufferType>>(bufferEnd - len),
173 startSamp, len };
174 }
175}
176} // namespace
177
179 double t, float* buffer, size_t numSamples, bool mayThrow,
180 PlaybackDirection direction)
181{
182 RoundToNearestClipSample(channel, t);
183 auto clip = GetClipAtTime(channel, t);
184 auto numSamplesRead = 0u;
185 const auto forward = direction == PlaybackDirection::forward;
186 const auto clips = SortedClipArray(channel);
187 while (clip)
188 {
189 const auto args = GetSampleAccessArgs(
190 *clip, t, buffer, numSamples, numSamplesRead, forward);
191 if (!clip->GetSamples(
192 args.offsetBuffer, floatSample, args.start, args.len,
193 mayThrow))
194 return 0u;
195 numSamplesRead += args.len;
196 if (numSamplesRead >= numSamples)
197 break;
198 clip = GetAdjacentClip(clips, *clip, direction);
199 }
200 return numSamplesRead;
201}
202
204 double t, float& value, bool mayThrow)
205{
206 const auto clip = GetClipAtTime(channel, t);
207 if (!clip)
208 return false;
209 return GetFloatAtTime(*clip, t, value, mayThrow);
210}
211
213 double t, float& value, bool mayThrow)
214{
215 const size_t iChannel = clip.GetChannelIndex();
217 t - clip.GetPlayStartTime(), iChannel, value, mayThrow);
218 return true;
219}
220
222 double t, const float* buffer, size_t numSideSamples,
223 sampleFormat effectiveFormat)
224{
225 SetFloatsFromTime(channel,
226 t, buffer, numSideSamples, effectiveFormat,
228 SetFloatsFromTime(channel,
229 t, buffer + numSideSamples, numSideSamples + 1, effectiveFormat,
231}
232
234 double t, const float* buffer, size_t numSamples,
235 sampleFormat effectiveFormat, PlaybackDirection direction)
236{
237 RoundToNearestClipSample(channel, t);
238 auto clip = GetClipAtTime(channel, t);
239 auto numSamplesWritten = 0u;
240 const auto forward = direction == PlaybackDirection::forward;
241 const auto clips = SortedClipArray(channel);
242 while (clip)
243 {
244 const auto args = GetSampleAccessArgs(
245 *clip, t, buffer, numSamples, numSamplesWritten, forward);
246 if (args.len > 0u)
247 {
248 clip->SetSamples(
249 args.offsetBuffer, floatSample, args.start, args.len,
250 effectiveFormat);
251 numSamplesWritten += args.len;
252 if (numSamplesWritten >= numSamples)
253 break;
254 }
255 clip = GetAdjacentClip(clips, *clip, direction);
256 }
257}
258
260 double t, float value, sampleFormat effectiveFormat)
261{
262 SetFloatsCenteredAroundTime(channel, t, &value, 0u, effectiveFormat);
263}
264
266 double t0, double t1,
267 const std::function<float(double sampleTime)>& producer,
268 sampleFormat effectiveFormat)
269{
270 if (t0 >= t1)
271 return;
272 const auto sortedClips = SortedClipArray(channel);
273 if (sortedClips.empty())
274 return;
275 t0 = std::max(t0, (*sortedClips.begin())->GetPlayStartTime());
276 t1 = std::min(t1, (*sortedClips.rbegin())->GetPlayEndTime());
277 auto clip = GetClipAtTime(channel, t0);
278 const auto clips = SortedClipArray(channel);
279 while (clip) {
280 const auto clipStartTime = clip->GetPlayStartTime();
281 const auto clipEndTime = clip->GetPlayEndTime();
282 const auto sampsPerSec = clip->GetRate() / clip->GetStretchRatio();
283 const auto roundedT0 =
284 std::round((t0 - clipStartTime) * sampsPerSec) / sampsPerSec +
285 clipStartTime;
286 const auto roundedT1 =
287 std::round((t1 - clipStartTime) * sampsPerSec) / sampsPerSec +
288 clipStartTime;
289 if (clipStartTime > roundedT1)
290 break;
291 const auto tt0 = std::max(clipStartTime, roundedT0);
292 const auto tt1 = std::min(clipEndTime, roundedT1);
293 const size_t numSamples = (tt1 - tt0) * sampsPerSec + .5;
294 std::vector<float> values(numSamples);
295 for (auto i = 0u; i < numSamples; ++i)
296 values[i] = producer(tt0 + clip->SamplesToTime(i));
297 const size_t iChannel = clip->GetChannelIndex();
299 tt0 - clipStartTime, iChannel, values.data(), numSamples,
300 effectiveFormat);
301 clip = GetNextClip(clips, *clip, PlaybackDirection::forward);
302 }
303}
304
305namespace {
309template<typename ClipPointer>
310auto DoGetNextClip(const std::vector<ClipPointer> &clips,
311 const Clip& clip, PlaybackDirection direction) -> ClipPointer
312{
313 assert(IsSortedByPlayStartTime(clips));
314 // Find first place greater than or equal to given clip
315 const auto p = lower_bound(clips.begin(), clips.end(), clip,
316 [](const ClipPointer &pClip, const Clip &clip){
317 return CompareClipsByPlayStartTime(*pClip, clip); });
318 // Fail if given clip is strictly less than that
319 if (p == clips.end() || !*p ||
321 return nullptr;
322 else if (direction == PlaybackDirection::forward)
323 return p == clips.end() - 1 ? nullptr : *(p + 1);
324 else
325 return p == clips.begin() ? nullptr : *(p - 1);
326}
327
328template<typename ClipPointer>
329auto DoGetAdjacentClip(const std::vector<ClipPointer> &clips,
330 const Clip& clip, PlaybackDirection direction) -> ClipPointer
331{
332 const auto neighbour = GetNextClip(clips, clip, direction);
333 if (!neighbour)
334 return nullptr;
335 else if (direction == PlaybackDirection::forward)
336 return std::abs(clip.GetPlayEndTime() - neighbour->GetPlayStartTime()) <
337 1e-9 ?
338 neighbour :
339 nullptr;
340 else
341 return std::abs(clip.GetPlayStartTime() - neighbour->GetPlayEndTime()) <
342 1e-9 ?
343 neighbour :
344 nullptr;
345}
346}
347
348auto
350 const Clip& clip, PlaybackDirection direction) -> ClipConstPointer
351{
352 assert(IsSortedByPlayStartTime(clips));
353 return DoGetAdjacentClip(clips, clip, direction);
354}
355
357 const Clip& clip, PlaybackDirection direction) -> ClipPointer
358{
359 assert(IsSortedByPlayStartTime(clips));
360 return DoGetAdjacentClip(clips, clip, direction);
361}
362
364 const ClipConstPointers &clips,
365 const Clip& clip, PlaybackDirection direction) -> ClipConstPointer
366{
367 assert(IsSortedByPlayStartTime(clips));
368 return DoGetNextClip(clips, clip, direction);
369}
370
372 const Clip& clip, PlaybackDirection direction) -> ClipPointer
373{
374 assert(IsSortedByPlayStartTime(clips));
375 return DoGetNextClip(clips, clip, direction);
376}
377
379 WaveChannel &channel, double time)
380{
381 // Substitute the first channel
382 auto &first = **channel.GetTrack().Channels().begin();
383 if (const auto clip = GetClipAtTime(first, time))
384 return &clip->GetEnvelope();
385 else
386 return nullptr;
387}
388
390 const Clip &x, const Clip &y)
391{
392 return x.GetPlayStartTime() < y.GetPlayStartTime();
393}
394
396 -> ClipPointers
397{
398 auto &&clips = channel.Intervals();
399 ClipPointers result{ clips.begin(), clips.end() };
400 sort(result.begin(), result.end(), CompareClipPointersByPlayStartTime);
401 return result;
402}
403
404
407{
408 auto pointers = SortedClipArray(const_cast<WaveChannel &>(channel));
409 return { pointers.begin(), pointers.end() };
410}
411
413 WaveChannel &channel, double time) -> ClipPointer
414{
415 const auto clips = SortedClipArray(channel);
416 auto p = std::find_if(
417 clips.rbegin(), clips.rend(), [&](const auto& clip) {
418 return clip->WithinPlayRegion(time);
419 });
420 return p != clips.rend() ? *p : nullptr;
421}
422
423
425 const WaveChannel &channel, double time) -> ClipConstPointer
426{
427 return GetClipAtTime(const_cast<WaveChannel&>(channel), time);
428}
429
431 -> ClipPointer
432{
433 ClipPointer result;
434 for (const auto &interval : channel.Intervals())
435 if (interval->WithinPlayRegion(t))
436 return interval;
437 return nullptr;
438}
int min(int a, int b)
const wxChar * values
#define THROW_INCONSISTENCY_EXCEPTION
Throw InconsistencyException, using C++ preprocessor to identify the source code location.
PlaybackDirection
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
constexpr sampleFormat floatSample
Definition: SampleFormat.h:45
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
char * samplePtr
Definition: SampleFormat.h:57
const char * constSamplePtr
Definition: SampleFormat.h:58
Piecewise linear or piecewise exponential function from double to double.
Definition: Envelope.h:72
WaveTrack & GetTrack()
Definition: WaveTrack.h:840
IteratorRange< IntervalIterator< WaveClipChannel > > Intervals()
Definition: WaveTrack.cpp:745
WaveClip & GetClip()
Definition: WaveClip.h:96
sampleCount GetVisibleSampleCount() const override
Definition: WaveClip.cpp:188
size_t GetChannelIndex() const
Definition: WaveClip.h:99
int GetRate() const override
Definition: WaveClip.cpp:193
double GetPlayStartTime() const override
Definition: WaveClip.cpp:198
double GetStretchRatio() const override
Definition: WaveClip.cpp:218
double GetPlayDuration() const
Definition: WaveClip.cpp:208
auto Channels()
Definition: WaveTrack.h:263
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
bool CompareClipPointersByPlayStartTime(const ClipConstPointer x, const ClipConstPointer y)
WAVE_TRACK_API void SetFloatsCenteredAroundTime(WaveChannel &channel, double t, const float *buffer, size_t numSideSamples, sampleFormat effectiveFormat)
Similar to GetFloatsCenteredAroundTime, but for writing. Sets as many samples as it can according to ...
std::vector< ClipPointer > ClipPointers
WAVE_TRACK_API std::pair< float, float > GetMinMax(const WaveChannel &channel, double t0, double t1, bool mayThrow=true)
bool IsSortedByPlayStartTime(const ClipPointers &clips)
WAVE_TRACK_API ClipPointer GetIntervalAtTime(WaveChannel &channel, double t)
std::shared_ptr< const Clip > ClipConstPointer
std::vector< ClipConstPointer > ClipConstPointers
WAVE_TRACK_API bool CompareClipsByPlayStartTime(const Clip &x, const Clip &y)
WAVE_TRACK_API void SetFloatsWithinTimeRange(WaveChannel &channel, double t0, double t1, const std::function< float(double sampleTime)> &producer, sampleFormat effectiveFormat)
Provides a means of setting clip values as a function of time. Included are closest sample to t0 up t...
WAVE_TRACK_API float GetRMS(const WaveChannel &channel, double t0, double t1, bool mayThrow=true)
std::shared_ptr< Clip > ClipPointer
WAVE_TRACK_API std::pair< size_t, size_t > GetFloatsCenteredAroundTime(const WaveChannel &channel, double t, float *buffer, size_t numSideSamples, bool mayThrow)
Gets as many samples as it can, but no more than 2 * numSideSamples + 1, centered around t....
WAVE_TRACK_API void SetFloatsFromTime(WaveChannel &channel, double t, const float *buffer, size_t numSamples, sampleFormat effectiveFormat, PlaybackDirection direction)
Similar to GetFloatsFromTime, but for writing. Sets as many samples as it can according to the same r...
WAVE_TRACK_API size_t GetFloatsFromTime(const WaveChannel &channel, double t, float *buffer, size_t numSamples, bool mayThrow, PlaybackDirection direction)
Helper for GetFloatsCenteredAroundTime. If direction == PlaybackDirection::Backward,...
WAVE_TRACK_API ClipPointer GetClipAtTime(WaveChannel &channel, double time)
WAVE_TRACK_API ClipConstPointer GetAdjacentClip(const ClipConstPointers &clips, const Clip &clip, PlaybackDirection searchDirection)
Similar to GetNextClip, but returns nullptr if the neighbour clip is not adjacent.
WAVE_TRACK_API bool GetFloatAtTime(const WaveChannel &channel, double t, float &value, bool mayThrow)
WAVE_TRACK_API Envelope * GetEnvelopeAtTime(WaveChannel &channel, double time)
WAVE_TRACK_API void SetFloatAtTime(WaveChannel &channel, double t, float value, sampleFormat effectiveFormat)
Sets sample nearest to t to value. Silently fails if GetClipAtTime(t) == nullptr.
WAVE_TRACK_API ClipPointers SortedClipArray(WaveChannel &channel)
Get clips sorted by play start time.
WAVE_TRACK_API ClipConstPointer GetNextClip(const ClipConstPointers &clips, const Clip &clip, PlaybackDirection searchDirection)
Returns clips next to clip in the given direction, or nullptr if there is none.
WAVE_TRACK_API void SetFloatsFromTime(WaveClip &clip, double t, size_t iChannel, const float *buffer, size_t numSamples, sampleFormat effectiveFormat)
Considers buffer as audio starting at TimeToSamples(t) (relative to clip play start time) and with eq...
WAVE_TRACK_API bool GetFloatAtTime(const WaveClip &clip, double t, size_t iChannel, float &value, bool mayThrow)
auto DoGetAdjacentClip(const std::vector< ClipPointer > &clips, const Clip &clip, PlaybackDirection direction) -> ClipPointer
SampleAccessArgs< BufferType > GetSampleAccessArgs(const Clip &clip, double startOrEndTime, BufferType buffer, size_t totalToRead, size_t alreadyRead, bool forward)
std::conditional_t< std::is_const_v< std::remove_pointer_t< FloatType > >, constSamplePtr, samplePtr > BufferCharType
void RoundToNearestClipSample(const WaveChannel &channel, double &t)
auto DoGetNextClip(const std::vector< ClipPointer > &clips, const Clip &clip, PlaybackDirection direction) -> ClipPointer
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
__finl float_x4 __vecc sqrt(const float_x4 &a)