2 * 2-channel UHJ Encoder
4 * Copyright (c) Chris Robinson <chris.kcat@gmail.com>
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
37 #include "alnumbers.h"
39 #include "opthelpers.h"
40 #include "phase_shifter.h"
45 #include "win_main_utf8.h"
50 struct SndFileDeleter
{
51 void operator()(SNDFILE
*sndfile
) { sf_close(sndfile
); }
53 using SndFilePtr
= std::unique_ptr
<SNDFILE
,SndFileDeleter
>;
56 using uint
= unsigned int;
58 constexpr uint BufferLineSize
{1024};
60 using FloatBufferLine
= std::array
<float,BufferLineSize
>;
61 using FloatBufferSpan
= al::span
<float,BufferLineSize
>;
65 constexpr static size_t sFilterDelay
{1024};
67 /* Delays and processing storage for the unfiltered signal. */
68 alignas(16) std::array
<float,BufferLineSize
+sFilterDelay
> mW
{};
69 alignas(16) std::array
<float,BufferLineSize
+sFilterDelay
> mX
{};
70 alignas(16) std::array
<float,BufferLineSize
+sFilterDelay
> mY
{};
71 alignas(16) std::array
<float,BufferLineSize
+sFilterDelay
> mZ
{};
73 alignas(16) std::array
<float,BufferLineSize
> mS
{};
74 alignas(16) std::array
<float,BufferLineSize
> mD
{};
75 alignas(16) std::array
<float,BufferLineSize
> mT
{};
77 /* History for the FIR filter. */
78 alignas(16) std::array
<float,sFilterDelay
*2 - 1> mWXHistory1
{};
79 alignas(16) std::array
<float,sFilterDelay
*2 - 1> mWXHistory2
{};
81 alignas(16) std::array
<float,BufferLineSize
+ sFilterDelay
*2> mTemp
{};
83 void encode(const al::span
<FloatBufferLine
> OutSamples
,
84 const al::span
<FloatBufferLine
,4> InSamples
, const size_t SamplesToDo
);
86 DEF_NEWDEL(UhjEncoder
)
89 const PhaseShifterT
<UhjEncoder::sFilterDelay
*2> PShift
{};
92 /* Encoding UHJ from B-Format is done as:
94 * S = 0.9396926*W + 0.1855740*X
95 * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y
99 * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y
102 * where j is a wide-band +90 degree phase shift. T is excluded from 2-channel
103 * output, and Q is excluded from 2- and 3-channel output.
105 void UhjEncoder::encode(const al::span
<FloatBufferLine
> OutSamples
,
106 const al::span
<FloatBufferLine
,4> InSamples
, const size_t SamplesToDo
)
108 const float *RESTRICT winput
{al::assume_aligned
<16>(InSamples
[0].data())};
109 const float *RESTRICT xinput
{al::assume_aligned
<16>(InSamples
[1].data())};
110 const float *RESTRICT yinput
{al::assume_aligned
<16>(InSamples
[2].data())};
111 const float *RESTRICT zinput
{al::assume_aligned
<16>(InSamples
[3].data())};
113 /* Combine the previously delayed input signal with the new input. */
114 std::copy_n(winput
, SamplesToDo
, mW
.begin()+sFilterDelay
);
115 std::copy_n(xinput
, SamplesToDo
, mX
.begin()+sFilterDelay
);
116 std::copy_n(yinput
, SamplesToDo
, mY
.begin()+sFilterDelay
);
117 std::copy_n(zinput
, SamplesToDo
, mZ
.begin()+sFilterDelay
);
119 /* S = 0.9396926*W + 0.1855740*X */
120 for(size_t i
{0};i
< SamplesToDo
;++i
)
121 mS
[i
] = 0.9396926f
*mW
[i
] + 0.1855740f
*mX
[i
];
123 /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */
124 auto tmpiter
= std::copy(mWXHistory1
.cbegin(), mWXHistory1
.cend(), mTemp
.begin());
125 std::transform(winput
, winput
+SamplesToDo
, xinput
, tmpiter
,
126 [](const float w
, const float x
) noexcept
-> float
127 { return -0.3420201f
*w
+ 0.5098604f
*x
; });
128 std::copy_n(mTemp
.cbegin()+SamplesToDo
, mWXHistory1
.size(), mWXHistory1
.begin());
129 PShift
.process({mD
.data(), SamplesToDo
}, mTemp
.data());
131 /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */
132 for(size_t i
{0};i
< SamplesToDo
;++i
)
133 mD
[i
] = mD
[i
] + 0.6554516f
*mY
[i
];
135 /* Left = (S + D)/2.0 */
136 float *RESTRICT left
{al::assume_aligned
<16>(OutSamples
[0].data())};
137 for(size_t i
{0};i
< SamplesToDo
;i
++)
138 left
[i
] = (mS
[i
] + mD
[i
]) * 0.5f
;
139 /* Right = (S - D)/2.0 */
140 float *RESTRICT right
{al::assume_aligned
<16>(OutSamples
[1].data())};
141 for(size_t i
{0};i
< SamplesToDo
;i
++)
142 right
[i
] = (mS
[i
] - mD
[i
]) * 0.5f
;
144 if(OutSamples
.size() > 2)
146 /* Precompute j(-0.1432*W + 0.6512*X) and store in mT. */
147 tmpiter
= std::copy(mWXHistory2
.cbegin(), mWXHistory2
.cend(), mTemp
.begin());
148 std::transform(winput
, winput
+SamplesToDo
, xinput
, tmpiter
,
149 [](const float w
, const float x
) noexcept
-> float
150 { return -0.1432f
*w
+ 0.6512f
*x
; });
151 std::copy_n(mTemp
.cbegin()+SamplesToDo
, mWXHistory2
.size(), mWXHistory2
.begin());
152 PShift
.process({mT
.data(), SamplesToDo
}, mTemp
.data());
154 /* T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y */
155 float *RESTRICT t
{al::assume_aligned
<16>(OutSamples
[2].data())};
156 for(size_t i
{0};i
< SamplesToDo
;i
++)
157 t
[i
] = mT
[i
] - 0.7071068f
*mY
[i
];
159 if(OutSamples
.size() > 3)
162 float *RESTRICT q
{al::assume_aligned
<16>(OutSamples
[3].data())};
163 for(size_t i
{0};i
< SamplesToDo
;i
++)
164 q
[i
] = 0.9772f
*mZ
[i
];
167 /* Copy the future samples to the front for next time. */
168 std::copy(mW
.cbegin()+SamplesToDo
, mW
.cbegin()+SamplesToDo
+sFilterDelay
, mW
.begin());
169 std::copy(mX
.cbegin()+SamplesToDo
, mX
.cbegin()+SamplesToDo
+sFilterDelay
, mX
.begin());
170 std::copy(mY
.cbegin()+SamplesToDo
, mY
.cbegin()+SamplesToDo
+sFilterDelay
, mY
.begin());
171 std::copy(mZ
.cbegin()+SamplesToDo
, mZ
.cbegin()+SamplesToDo
+sFilterDelay
, mZ
.begin());
181 /* Azimuth is counter-clockwise. */
182 constexpr SpeakerPos StereoMap
[2]{
183 { SF_CHANNEL_MAP_LEFT
, 30.0f
, 0.0f
},
184 { SF_CHANNEL_MAP_RIGHT
, -30.0f
, 0.0f
},
186 { SF_CHANNEL_MAP_LEFT
, 45.0f
, 0.0f
},
187 { SF_CHANNEL_MAP_RIGHT
, -45.0f
, 0.0f
},
188 { SF_CHANNEL_MAP_REAR_LEFT
, 135.0f
, 0.0f
},
189 { SF_CHANNEL_MAP_REAR_RIGHT
, -135.0f
, 0.0f
},
191 { SF_CHANNEL_MAP_LEFT
, 30.0f
, 0.0f
},
192 { SF_CHANNEL_MAP_RIGHT
, -30.0f
, 0.0f
},
193 { SF_CHANNEL_MAP_CENTER
, 0.0f
, 0.0f
},
194 { SF_CHANNEL_MAP_LFE
, 0.0f
, 0.0f
},
195 { SF_CHANNEL_MAP_SIDE_LEFT
, 110.0f
, 0.0f
},
196 { SF_CHANNEL_MAP_SIDE_RIGHT
, -110.0f
, 0.0f
},
198 { SF_CHANNEL_MAP_LEFT
, 30.0f
, 0.0f
},
199 { SF_CHANNEL_MAP_RIGHT
, -30.0f
, 0.0f
},
200 { SF_CHANNEL_MAP_CENTER
, 0.0f
, 0.0f
},
201 { SF_CHANNEL_MAP_LFE
, 0.0f
, 0.0f
},
202 { SF_CHANNEL_MAP_REAR_LEFT
, 110.0f
, 0.0f
},
203 { SF_CHANNEL_MAP_REAR_RIGHT
, -110.0f
, 0.0f
},
205 { SF_CHANNEL_MAP_LEFT
, 30.0f
, 0.0f
},
206 { SF_CHANNEL_MAP_RIGHT
, -30.0f
, 0.0f
},
207 { SF_CHANNEL_MAP_CENTER
, 0.0f
, 0.0f
},
208 { SF_CHANNEL_MAP_LFE
, 0.0f
, 0.0f
},
209 { SF_CHANNEL_MAP_REAR_LEFT
, 150.0f
, 0.0f
},
210 { SF_CHANNEL_MAP_REAR_RIGHT
, -150.0f
, 0.0f
},
211 { SF_CHANNEL_MAP_SIDE_LEFT
, 90.0f
, 0.0f
},
212 { SF_CHANNEL_MAP_SIDE_RIGHT
, -90.0f
, 0.0f
},
214 { SF_CHANNEL_MAP_LEFT
, 30.0f
, 0.0f
},
215 { SF_CHANNEL_MAP_RIGHT
, -30.0f
, 0.0f
},
216 { SF_CHANNEL_MAP_CENTER
, 0.0f
, 0.0f
},
217 { SF_CHANNEL_MAP_LFE
, 0.0f
, 0.0f
},
218 { SF_CHANNEL_MAP_REAR_LEFT
, 150.0f
, 0.0f
},
219 { SF_CHANNEL_MAP_REAR_RIGHT
, -150.0f
, 0.0f
},
220 { SF_CHANNEL_MAP_SIDE_LEFT
, 90.0f
, 0.0f
},
221 { SF_CHANNEL_MAP_SIDE_RIGHT
, -90.0f
, 0.0f
},
222 { SF_CHANNEL_MAP_TOP_FRONT_LEFT
, 45.0f
, 35.0f
},
223 { SF_CHANNEL_MAP_TOP_FRONT_RIGHT
, -45.0f
, 35.0f
},
224 { SF_CHANNEL_MAP_TOP_REAR_LEFT
, 135.0f
, 35.0f
},
225 { SF_CHANNEL_MAP_TOP_REAR_RIGHT
, -135.0f
, 35.0f
},
228 constexpr auto GenCoeffs(double x
/*+front*/, double y
/*+left*/, double z
/*+up*/) noexcept
230 /* Coefficients are +3dB of FuMa. */
231 return std::array
<float,4>{{
233 static_cast<float>(al::numbers::sqrt2
* x
),
234 static_cast<float>(al::numbers::sqrt2
* y
),
235 static_cast<float>(al::numbers::sqrt2
* z
)
242 int main(int argc
, char **argv
)
244 if(argc
< 2 || std::strcmp(argv
[1], "-h") == 0 || std::strcmp(argv
[1], "--help") == 0)
246 printf("Usage: %s <infile...>\n\n", argv
[0]);
251 size_t num_files
{0}, num_encoded
{0};
252 for(int fidx
{1};fidx
< argc
;++fidx
)
254 if(strcmp(argv
[fidx
], "-bhj") == 0)
259 if(strcmp(argv
[fidx
], "-thj") == 0)
264 if(strcmp(argv
[fidx
], "-phj") == 0)
271 std::string outname
{argv
[fidx
]};
272 size_t lastslash
{outname
.find_last_of('/')};
273 if(lastslash
!= std::string::npos
)
274 outname
.erase(0, lastslash
+1);
275 size_t extpos
{outname
.find_last_of('.')};
276 if(extpos
!= std::string::npos
)
277 outname
.resize(extpos
);
278 outname
+= ".uhj.flac";
281 SndFilePtr infile
{sf_open(argv
[fidx
], SFM_READ
, &ininfo
)};
284 fprintf(stderr
, "Failed to open %s\n", argv
[fidx
]);
287 printf("Converting %s to %s...\n", argv
[fidx
], outname
.c_str());
289 /* Work out the channel map, preferably using the actual channel map
290 * from the file/format, but falling back to assuming WFX order.
292 al::span
<const SpeakerPos
> spkrs
;
293 auto chanmap
= std::vector
<int>(static_cast<uint
>(ininfo
.channels
), SF_CHANNEL_MAP_INVALID
);
294 if(sf_command(infile
.get(), SFC_GET_CHANNEL_MAP_INFO
, chanmap
.data(),
295 ininfo
.channels
*int{sizeof(int)}) == SF_TRUE
)
297 static const std::array
<int,2> stereomap
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
}};
298 static const std::array
<int,4> quadmap
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
,
299 SF_CHANNEL_MAP_REAR_LEFT
, SF_CHANNEL_MAP_REAR_RIGHT
}};
300 static const std::array
<int,6> x51map
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
,
301 SF_CHANNEL_MAP_CENTER
, SF_CHANNEL_MAP_LFE
,
302 SF_CHANNEL_MAP_SIDE_LEFT
, SF_CHANNEL_MAP_SIDE_RIGHT
}};
303 static const std::array
<int,6> x51rearmap
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
,
304 SF_CHANNEL_MAP_CENTER
, SF_CHANNEL_MAP_LFE
,
305 SF_CHANNEL_MAP_REAR_LEFT
, SF_CHANNEL_MAP_REAR_RIGHT
}};
306 static const std::array
<int,8> x71map
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
,
307 SF_CHANNEL_MAP_CENTER
, SF_CHANNEL_MAP_LFE
,
308 SF_CHANNEL_MAP_REAR_LEFT
, SF_CHANNEL_MAP_REAR_RIGHT
,
309 SF_CHANNEL_MAP_SIDE_LEFT
, SF_CHANNEL_MAP_SIDE_RIGHT
}};
310 static const std::array
<int,12> x714map
{{SF_CHANNEL_MAP_LEFT
, SF_CHANNEL_MAP_RIGHT
,
311 SF_CHANNEL_MAP_CENTER
, SF_CHANNEL_MAP_LFE
,
312 SF_CHANNEL_MAP_REAR_LEFT
, SF_CHANNEL_MAP_REAR_RIGHT
,
313 SF_CHANNEL_MAP_SIDE_LEFT
, SF_CHANNEL_MAP_SIDE_RIGHT
,
314 SF_CHANNEL_MAP_TOP_FRONT_LEFT
, SF_CHANNEL_MAP_TOP_FRONT_RIGHT
,
315 SF_CHANNEL_MAP_TOP_REAR_LEFT
, SF_CHANNEL_MAP_TOP_REAR_RIGHT
}};
316 static const std::array
<int,3> ambi2dmap
{{SF_CHANNEL_MAP_AMBISONIC_B_W
,
317 SF_CHANNEL_MAP_AMBISONIC_B_X
, SF_CHANNEL_MAP_AMBISONIC_B_Y
}};
318 static const std::array
<int,4> ambi3dmap
{{SF_CHANNEL_MAP_AMBISONIC_B_W
,
319 SF_CHANNEL_MAP_AMBISONIC_B_X
, SF_CHANNEL_MAP_AMBISONIC_B_Y
,
320 SF_CHANNEL_MAP_AMBISONIC_B_Z
}};
322 auto match_chanmap
= [](const al::span
<int> a
, const al::span
<const int> b
) -> bool
324 if(a
.size() != b
.size())
326 for(const int id
: a
)
328 if(std::find(b
.begin(), b
.end(), id
) != b
.end())
333 if(match_chanmap(chanmap
, stereomap
))
335 else if(match_chanmap(chanmap
, quadmap
))
337 else if(match_chanmap(chanmap
, x51map
))
339 else if(match_chanmap(chanmap
, x51rearmap
))
341 else if(match_chanmap(chanmap
, x71map
))
343 else if(match_chanmap(chanmap
, x714map
))
345 else if(match_chanmap(chanmap
, ambi2dmap
) || match_chanmap(chanmap
, ambi3dmap
))
354 mapstr
= std::to_string(chanmap
[0]);
355 for(int idx
: al::span
<int>{chanmap
}.subspan
<1>())
358 mapstr
+= std::to_string(idx
);
361 fprintf(stderr
, " ... %zu channels not supported (map: %s)\n", chanmap
.size(),
366 else if(ininfo
.channels
== 2)
368 fprintf(stderr
, " ... assuming WFX order stereo\n");
370 chanmap
[0] = SF_CHANNEL_MAP_FRONT_LEFT
;
371 chanmap
[1] = SF_CHANNEL_MAP_FRONT_RIGHT
;
373 else if(ininfo
.channels
== 6)
375 fprintf(stderr
, " ... assuming WFX order 5.1\n");
377 chanmap
[0] = SF_CHANNEL_MAP_FRONT_LEFT
;
378 chanmap
[1] = SF_CHANNEL_MAP_FRONT_RIGHT
;
379 chanmap
[2] = SF_CHANNEL_MAP_FRONT_CENTER
;
380 chanmap
[3] = SF_CHANNEL_MAP_LFE
;
381 chanmap
[4] = SF_CHANNEL_MAP_SIDE_LEFT
;
382 chanmap
[5] = SF_CHANNEL_MAP_SIDE_RIGHT
;
384 else if(ininfo
.channels
== 8)
386 fprintf(stderr
, " ... assuming WFX order 7.1\n");
388 chanmap
[0] = SF_CHANNEL_MAP_FRONT_LEFT
;
389 chanmap
[1] = SF_CHANNEL_MAP_FRONT_RIGHT
;
390 chanmap
[2] = SF_CHANNEL_MAP_FRONT_CENTER
;
391 chanmap
[3] = SF_CHANNEL_MAP_LFE
;
392 chanmap
[4] = SF_CHANNEL_MAP_REAR_LEFT
;
393 chanmap
[5] = SF_CHANNEL_MAP_REAR_RIGHT
;
394 chanmap
[6] = SF_CHANNEL_MAP_SIDE_LEFT
;
395 chanmap
[7] = SF_CHANNEL_MAP_SIDE_RIGHT
;
399 fprintf(stderr
, " ... unmapped %d-channel audio not supported\n", ininfo
.channels
);
404 outinfo
.frames
= ininfo
.frames
;
405 outinfo
.samplerate
= ininfo
.samplerate
;
406 outinfo
.channels
= static_cast<int>(uhjchans
);
407 outinfo
.format
= SF_FORMAT_PCM_24
| SF_FORMAT_FLAC
;
408 SndFilePtr outfile
{sf_open(outname
.c_str(), SFM_WRITE
, &outinfo
)};
411 fprintf(stderr
, " ... failed to create %s\n", outname
.c_str());
415 auto encoder
= std::make_unique
<UhjEncoder
>();
416 auto splbuf
= al::vector
<FloatBufferLine
, 16>(static_cast<uint
>(9+ininfo
.channels
)+uhjchans
);
417 auto ambmem
= al::span
<FloatBufferLine
,4>{splbuf
.data(), 4};
418 auto encmem
= al::span
<FloatBufferLine
,4>{&splbuf
[4], 4};
419 auto srcmem
= al::span
<float,BufferLineSize
>{splbuf
[8].data(), BufferLineSize
};
420 auto outmem
= al::span
<float>{splbuf
[9].data(), BufferLineSize
*uhjchans
};
422 /* A number of initial samples need to be skipped to cut the lead-in
423 * from the all-pass filter delay. The same number of samples need to
424 * be fed through the encoder after reaching the end of the input file
425 * to ensure none of the original input is lost.
427 size_t total_wrote
{0};
428 size_t LeadIn
{UhjEncoder::sFilterDelay
};
429 sf_count_t LeadOut
{UhjEncoder::sFilterDelay
};
430 while(LeadIn
> 0 || LeadOut
> 0)
432 auto inmem
= outmem
.data() + outmem
.size();
433 auto sgot
= sf_readf_float(infile
.get(), inmem
, BufferLineSize
);
435 sgot
= std::max
<sf_count_t
>(sgot
, 0);
436 if(sgot
< BufferLineSize
)
438 const sf_count_t remaining
{std::min(BufferLineSize
- sgot
, LeadOut
)};
439 std::fill_n(inmem
+ sgot
*ininfo
.channels
, remaining
*ininfo
.channels
, 0.0f
);
441 LeadOut
-= remaining
;
444 for(auto&& buf
: ambmem
)
447 auto got
= static_cast<size_t>(sgot
);
450 /* B-Format is already in the correct order. It just needs a
453 static constexpr float scale
{al::numbers::sqrt2_v
<float>};
454 const size_t chans
{std::min
<size_t>(static_cast<uint
>(ininfo
.channels
), 4u)};
455 for(size_t c
{0};c
< chans
;++c
)
457 for(size_t i
{0};i
< got
;++i
)
458 ambmem
[c
][i
] = inmem
[i
*static_cast<uint
>(ininfo
.channels
)] * scale
;
462 else for(const int chanid
: chanmap
)
464 /* Skip LFE. Or mix directly into W? Or W+X? */
465 if(chanid
== SF_CHANNEL_MAP_LFE
)
471 const auto spkr
= std::find_if(spkrs
.cbegin(), spkrs
.cend(),
472 [chanid
](const SpeakerPos
&pos
){return pos
.mChannelID
== chanid
;});
473 if(spkr
== spkrs
.cend())
475 fprintf(stderr
, " ... failed to find channel ID %d\n", chanid
);
479 for(size_t i
{0};i
< got
;++i
)
480 srcmem
[i
] = inmem
[i
* static_cast<uint
>(ininfo
.channels
)];
483 static constexpr auto Deg2Rad
= al::numbers::pi
/ 180.0;
484 const auto coeffs
= GenCoeffs(
485 std::cos(spkr
->mAzimuth
*Deg2Rad
) * std::cos(spkr
->mElevation
*Deg2Rad
),
486 std::sin(spkr
->mAzimuth
*Deg2Rad
) * std::cos(spkr
->mElevation
*Deg2Rad
),
487 std::sin(spkr
->mElevation
*Deg2Rad
));
488 for(size_t c
{0};c
< 4;++c
)
490 for(size_t i
{0};i
< got
;++i
)
491 ambmem
[c
][i
] += srcmem
[i
] * coeffs
[c
];
495 encoder
->encode(encmem
.subspan(0, uhjchans
), ambmem
, got
);
503 for(size_t c
{0};c
< uhjchans
;++c
)
505 constexpr float max_val
{8388607.0f
/ 8388608.0f
};
506 auto clamp
= [](float v
, float mn
, float mx
) noexcept
507 { return std::min(std::max(v
, mn
), mx
); };
508 for(size_t i
{0};i
< got
;++i
)
509 outmem
[i
*uhjchans
+ c
] = clamp(encmem
[c
][LeadIn
+i
], -1.0f
, max_val
);
513 sf_count_t wrote
{sf_writef_float(outfile
.get(), outmem
.data(),
514 static_cast<sf_count_t
>(got
))};
516 fprintf(stderr
, " ... failed to write samples: %d\n", sf_error(outfile
.get()));
518 total_wrote
+= static_cast<size_t>(wrote
);
520 printf(" ... wrote %zu samples (%" PRId64
").\n", total_wrote
, int64_t{ininfo
.frames
});
524 fprintf(stderr
, "Failed to encode any input files\n");
525 else if(num_encoded
< num_files
)
526 fprintf(stderr
, "Encoded %zu of %zu files\n", num_encoded
, num_files
);
528 printf("Encoded %s%zu file%s\n", (num_encoded
> 1) ? "all " : "", num_encoded
,
529 (num_encoded
== 1) ? "" : "s");