99
1010#define _USE_MATH_DEFINES
1111
12+ // =============================================================================
13+ // TEMPLATE UTILITIES
14+ // =============================================================================
15+
16+ /* * * @brief Helper to create a non-deducible context.
17+ * This forces the compiler to deduce TYPE from other arguments (like buffers)
18+ * and then simply convert the sample rate arguments to that TYPE.
19+ */
20+ template <typename T>
21+ struct identity {
22+ using type = T;
23+ };
24+ template <typename T>
25+ using non_deduced = typename identity<T>::type;
26+
1227// =============================================================================
1328// SFINAE TRAITS
1429// =============================================================================
@@ -47,8 +62,8 @@ namespace resinc_traits {
4762 struct is_multi_channel <
4863 T,
4964 std::enable_if_t <has_size_and_data<T>::value &&
50- std::is_arithmetic_v <std::decay_t <
51- decltype (std::declval<T>()[0][0] )>>>> :
65+ has_size_and_data <std::decay_t <
66+ decltype (std::declval<T>()[0])>>::value >> :
5267 std::true_type {};
5368
5469 /* * @brief Detects JUCE-compatible AudioBuffer classes (has
@@ -58,10 +73,17 @@ namespace resinc_traits {
5873 template <typename T>
5974 struct is_juce_type <
6075 T,
61- std::void_t <decltype (std::declval<T>().getNumChannels()),
62- decltype (std::declval<T>().getNumSamples()),
63- decltype(std::declval<T>().getReadPointer(0 )),
64- decltype(std::declval<T>().getWritePointer(0 ))>> :
76+ std::void_t <
77+ // Method Existence
78+ decltype (std::declval<const T>().getNumChannels()),
79+ decltype(std::declval<const T>().getNumSamples()),
80+ decltype(std::declval<const T>().getReadPointer(0 )),
81+ decltype(std::declval<T>().getWritePointer(0 )),
82+ decltype(std::declval<T>().clear()),
83+ std::enable_if_t<std::is_integral_v<
84+ decltype(std::declval<T>().getNumChannels())>>,
85+ std::enable_if_t<std::is_pointer_v<
86+ decltype(std::declval<T>().getReadPointer(0 ))>>>> :
6587 std::true_type {};
6688} // namespace resinc_traits
6789
@@ -152,6 +174,25 @@ namespace internal {
152174 (SINC_RADIUS + 1 ) * OVERSAMPLE_FACTOR * 2>&
153175 window);
154176 };
177+
178+ /* *
179+ * @brief Core implementation for stateless one-shot resampling.
180+ * * This helper performs a full sample rate conversion by locally
181+ * configuring a Sinc filter and Kaiser window. Unlike the class-based
182+ * resampler, this function treats the boundaries of the input buffer as
183+ * silence (zero-padding).
184+ * *
185+ * * Performance Note: This function re-calculates the filter table on every
186+ * call. For repeated block-based processing, use the Resampler class
187+ * instead.
188+ */
189+ template <typename TYPE, int SINC_RADIUS, int RESOLUTION = 256 >
190+ int resample_helper (const TYPE* const * ptrToInBuffers,
191+ TYPE* const * ptrToOutBuffers,
192+ int numChannels,
193+ int numSamples,
194+ TYPE sourceSampleRate,
195+ TYPE targetSampleRate);
155196} // namespace internal
156197
157198// =============================================================================
@@ -420,6 +461,70 @@ class Resampler {
420461 int numSamples);
421462};
422463
464+ // =============================================================================
465+ // STATELESS RESAMPLING FUNCTIONS
466+ // =============================================================================
467+
468+ /* *
469+ * @brief Resamples a JUCE-style AudioBuffer without internal history.
470+ * * This is a one-shot function that ignores previous or future audio blocks.
471+ * Ideal for offline processing or UI assets.
472+ * * @tparam TYPE Sample type (float, double).
473+ * @tparam SINC_RADIUS Sinc kernel radius.
474+ * @tparam RESOLUTION Phase lookup table resolution.
475+ * @return The number of output samples produced.
476+ */
477+ template <typename TYPE, int SINC_RADIUS, int RESOLUTION = 256 , typename T>
478+ typename std::enable_if_t <resinc_traits::is_juce_type<std::decay_t <T>>::value,
479+ int >
480+ resample (T&& input,
481+ non_deduced<std::remove_reference_t <T>>& output,
482+ non_deduced<TYPE> sourceSampleRate,
483+ non_deduced<TYPE> targetSampleRate);
484+
485+ /* *
486+ * @brief Resamples a single-channel container (e.g., std::vector<float>)
487+ * without internal history.
488+ */
489+ template <typename TYPE, int SINC_RADIUS, int RESOLUTION = 256 , typename T>
490+ typename std::
491+ enable_if_t <resinc_traits::is_single_channel<std::decay_t <T>>::value, int >
492+ resample (T&& input,
493+ non_deduced<std::remove_reference_t <T>>& output,
494+ non_deduced<TYPE> sourceSampleRate,
495+ non_deduced<TYPE> targetSampleRate);
496+
497+ /* *
498+ * @brief Resamples a multi-channel container (e.g., vector of vectors) without
499+ * internal history.
500+ */
501+ template <typename TYPE, int SINC_RADIUS, int RESOLUTION = 256 , typename T>
502+ typename std::
503+ enable_if_t <resinc_traits::is_multi_channel<std::decay_t <T>>::value, int >
504+ resample (T&& input,
505+ non_deduced<std::remove_reference_t <T>>& output,
506+ non_deduced<TYPE> sourceSampleRate,
507+ non_deduced<TYPE> targetSampleRate);
508+
509+ /* *
510+ * @brief Resamples from raw pointers without internal history (Base
511+ * Implementation).
512+ * * @param ptrToInBuffers Array of pointers to input channel data.
513+ * @param ptrToOutBuffers Array of pointers to output channel data.
514+ * @param numChannels Number of channels to process.
515+ * @param numSamples Number of input samples.
516+ * @param sourceSampleRate The sample rate of the input data.
517+ * @param targetSampleRate The desired output sample rate.
518+ * @return The number of output samples produced.
519+ */
520+ template <typename TYPE, int SINC_RADIUS, int RESOLUTION = 256 >
521+ int resample (const TYPE* const * ptrToInBuffers,
522+ TYPE* const * ptrToOutBuffers,
523+ int numChannels,
524+ int numSamples,
525+ non_deduced<TYPE> sourceSampleRate,
526+ non_deduced<TYPE> targetSampleRate);
527+
423528// =============================================================================
424529// IMPLEMENTATIONS: CircularBuffer
425530// =============================================================================
@@ -986,3 +1091,135 @@ int Resampler<TYPE, SINC_RADIUS, RESOLUTION>::resample_helper(
9861091 (current_phase + (outputCount / oversampleFactor)) - numSamples;
9871092 return outputCount;
9881093}
1094+
1095+ // =============================================================================
1096+ // IMPLEMENTATIONS: resample
1097+ // =============================================================================
1098+ template <typename TYPE, int SINC_RADIUS, int RESOLUTION, typename T>
1099+ typename std::enable_if_t <resinc_traits::is_juce_type<std::decay_t <T>>::value,
1100+ int >
1101+ resample (T&& input,
1102+ non_deduced<std::remove_reference_t <T>>& output,
1103+ non_deduced<TYPE> sourceSampleRate,
1104+ non_deduced<TYPE> targetSampleRate) {
1105+ return internal::resample_helper<TYPE, SINC_RADIUS, RESOLUTION>(
1106+ input.getArrayOfReadPointers (),
1107+ output.getArrayOfWritePointers (),
1108+ input.getNumChannels (),
1109+ output.getNumSamples (),
1110+ sourceSampleRate,
1111+ targetSampleRate);
1112+ }
1113+
1114+ template <typename TYPE, int SINC_RADIUS, int RESOLUTION, typename T>
1115+ typename std::
1116+ enable_if_t <resinc_traits::is_single_channel<std::decay_t <T>>::value, int >
1117+ resample (T&& input,
1118+ non_deduced<std::remove_reference_t <T>>& output,
1119+ non_deduced<TYPE> sourceSampleRate,
1120+ non_deduced<TYPE> targetSampleRate) {
1121+ const TYPE* ptr = input.data ();
1122+ TYPE* outPtr = output.data ();
1123+ return internal::resample_helper<TYPE, SINC_RADIUS, RESOLUTION>(
1124+ &ptr,
1125+ &outPtr,
1126+ 1 ,
1127+ static_cast <int >(input.size ()),
1128+ sourceSampleRate,
1129+ targetSampleRate);
1130+ }
1131+
1132+ template <typename TYPE, int SINC_RADIUS, int RESOLUTION, typename T>
1133+ typename std::
1134+ enable_if_t <resinc_traits::is_multi_channel<std::decay_t <T>>::value, int >
1135+ resample (T&& input,
1136+ non_deduced<std::remove_reference_t <T>>& output,
1137+ non_deduced<TYPE> sourceSampleRate,
1138+ non_deduced<TYPE> targetSampleRate) {
1139+ int channels = static_cast <int >(input.size ());
1140+ if (channels == 0 ) return 0 ;
1141+ std::vector<const TYPE*> inPtrs (channels);
1142+ std::vector<TYPE*> outPtrs (channels);
1143+ for (int i = 0 ; i < channels; i++) {
1144+ inPtrs[i] = input[i].data ();
1145+ outPtrs[i] = output[i].data ();
1146+ }
1147+ return internal::resample_helper<TYPE, SINC_RADIUS, RESOLUTION>(
1148+ inPtrs.data (),
1149+ outPtrs.data (),
1150+ channels,
1151+ static_cast <int >(input[0 ].size ()),
1152+ sourceSampleRate,
1153+ targetSampleRate);
1154+ }
1155+
1156+ template <typename TYPE, int SINC_RADIUS, int RESOLUTION>
1157+ int resample (const TYPE* const * ptrToInBuffers,
1158+ TYPE* const * ptrToOutBuffers,
1159+ int numChannels,
1160+ int numSamples,
1161+ non_deduced<TYPE> sourceSampleRate,
1162+ non_deduced<TYPE> targetSampleRate) {
1163+ return internal::resample_helper<TYPE, SINC_RADIUS, RESOLUTION>(
1164+ ptrToInBuffers,
1165+ ptrToOutBuffers,
1166+ numChannels,
1167+ numSamples,
1168+ sourceSampleRate,
1169+ targetSampleRate);
1170+ }
1171+
1172+ // =============================================================================
1173+ // IMPLEMENTATIONS: Helpers
1174+ // =============================================================================
1175+
1176+ template <typename TYPE, int SINC_RADIUS, int RESOLUTION>
1177+ int internal::resample_helper (const TYPE* const * ptrToInBuffers,
1178+ TYPE* const * ptrToOutBuffers,
1179+ int numChannels,
1180+ int numSamples,
1181+ TYPE sourceSampleRate,
1182+ TYPE targetSampleRate) {
1183+ if (numChannels <= 0 || numSamples <= 0 ) return 0 ;
1184+
1185+ internal::Sinc<TYPE, RESOLUTION, SINC_RADIUS> sinc;
1186+ TYPE cutoff = std::min (sourceSampleRate, targetSampleRate) / TYPE (2.0 );
1187+ sinc.configure_with_cutoff (cutoff, sourceSampleRate);
1188+ sinc.applyWindow (
1189+ Window::Kaiser<TYPE, (SINC_RADIUS + 1 ) * RESOLUTION * 2 > {5.0 });
1190+
1191+ const TYPE invOversampleFactor = sourceSampleRate / targetSampleRate;
1192+ const TYPE oversampleFactor = targetSampleRate / sourceSampleRate;
1193+ const int outputCount = static_cast <int >(numSamples * oversampleFactor);
1194+ const TYPE gainScale = (oversampleFactor < TYPE (1.0 )) ?
1195+ static_cast <TYPE>(oversampleFactor) :
1196+ TYPE (1.0 );
1197+
1198+ for (int k = 0 ; k < outputCount; ++k) {
1199+ const double virtualIndex =
1200+ static_cast <double >(k) * invOversampleFactor;
1201+ const int index = static_cast <int >(virtualIndex);
1202+ const int delta = static_cast <int >((virtualIndex - index) * RESOLUTION);
1203+
1204+ // --- Skip Zeros: Calculate valid n-range ---
1205+ // Constraint 1: n >= -SINC_RADIUS && n <= SINC_RADIUS
1206+ // Constraint 2: 0 <= (index - n) < numSamples
1207+ // => n <= index
1208+ // => n > index - numSamples
1209+ const int n_start = std::max (-SINC_RADIUS, index - numSamples + 1 );
1210+ const int n_end = std::min (SINC_RADIUS, index);
1211+
1212+ for (int channel = 0 ; channel < numChannels; channel++) {
1213+ TYPE acc = 0 ;
1214+ const TYPE* in = ptrToInBuffers[channel];
1215+
1216+ // Hot loop: No branches, minimal iterations at boundaries
1217+ for (int n = n_start; n <= n_end; n++) {
1218+ acc += sinc (n, delta) * in[index - n];
1219+ }
1220+
1221+ ptrToOutBuffers[channel][k] = acc * gainScale;
1222+ }
1223+ }
1224+ return outputCount;
1225+ }
0 commit comments